mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
811a3aa447ed3aea7b3645092f1aff7ed1c243eb
18 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1150d32aee |
Add Zed integration (#2780)
* feat: add Zed integration * fix: update integrations stats grid to 31 for consistency * fix: address Copilot review feedback - Remove non-actionable --skills flag from ZedIntegration (Zed is always skills-based, like Agy) - Align zed_skill_mode predicate with ai_skills for consistency across init output and hook rendering - Consolidate claude/cursor/zed slash-skill return blocks in _render_hook_invocation to reduce duplication - Override test_options_include_skills_flag for Zed (no --skills flag) * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: address Copilot review round 2 - Make zed_skill_mode unconditional in hook rendering (Zed is always skills-based, no --skills option) - Add test_init_persists_ai_skills_for_zed that exercises the actual CLI init path and verifies HookExecutor renders /speckit-plan without manual init-options manipulation * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: address copilot review feedback for zed integration - Update integration count from 31 to 33 in docs/index.md (32 integrations + Generic) - Make zed_skill_mode unconditional to match extensions.py behavior - Consolidate slash-skill integrations into a set for consistency - Move os import to module level in test_integration_zed.py * fix: refine slash-skill logic and ai-skills validation - Fix slash-skill integrations: Claude/Cursor require ai_skills=true; Zed/Agy/Devin are always skills - Allow --ai-skills with --integration (not just --ai) to fix validation error * fix: remove unused variables and update ai-skills help text - Add agy_skill_mode and devin_skill_mode variables to fix F841 lint error - Use all skill mode variables in the slash-skill conditional check - Update --ai-skills help text to reflect it works with --integration too * fix: add trae_skill_mode to hook invocation for consistency Trae is a SkillsIntegration like Zed/Agy/Devin, so it should also be treated as always-skills-based in hook invocation rendering. * fix: make Agy always skills-based for consistency AgyIntegration is a SkillsIntegration subclass with no --skills option, so it should be treated as always skills-based (like Zed, Devin, Trae). This aligns init.py skill mode detection with extensions.py hook rendering. * fix: gate agy_skill_mode and refactor _render_hook_invocation to use sets Addressed Copilot review comments: - Restored _is_skills_integration guard on agy_skill_mode in init.py to be defensive about runtime integration type. - Refactored _render_hook_invocation() in extensions.py to use always_slash/conditional_slash frozensets instead of individual per-agent booleans, eliminating unused variables (F841) and making it harder for conditions to drift between integrations. - Centralized slash-skill determination so adding a new unconditional slash-skill integration is a one-key addition. * fix: address latest Copilot review comments - Added copilot to CONDITIONAL_SLASH_AGENTS for consistent hook invocation rendering with init.py - Moved always_slash/conditional_slash frozensets to module scope to avoid per-call reallocation - Replaced manual os.chdir() with monkeypatch.chdir() in test - Overrode test_options_include_skills_flag for Zed (no --skills) * fix: address latest Copilot review comments - Removed redundant local import yaml in _register_extension_skills (yaml is already imported at module scope) - Split --ai-skills usage hint into two separate print statements for better readability - Changed integrations count from '33' to '30+' to avoid future drift * fix: re-add _is_skills_integration definition lost in merge The _is_skills_integration variable was accidentally dropped during the web UI merge resolution of upstream/main's removal of legacy --ai flags. Re-added the definition via isinstance(resolved_integration, SkillsIntegration) check so that skill-mode booleans work correctly. * fix: gate zed_skill_mode on _is_skills_integration for consistency Aligns zed_skill_mode with the other skills-based agents (codex, claude, cursor-agent, copilot) which all use _is_skills_integration gating. Since ZedIntegration extends SkillsIntegration, behavior is unchanged. * fix: remove unused claude_skill_mode and cursor_skill_mode locals in _render_hook_invocation These variables became unused after the refactor to ALWAYS_SLASH_AGENTS / CONDITIONAL_SLASH_AGENTS sets. Claude and Cursor-Agent are now handled by the CONDITIONAL_SLASH_AGENTS path, so the separate boolean locals are dead code. Fixes ruff F841 and addresses Copilot review feedback that was repeated across multiple review rounds. * fix: align agy/trae invocation format in init next-steps with hook rendering and build_command_invocation - Moved agy and trae from '-<name>' (dollar/Codex format) to '/speckit-<name>' (slash format) in _display_cmd() to match: - HookExecutor._render_hook_invocation() (ALWAYS_SLASH_AGENTS for trae, CONDITIONAL_SLASH_AGENTS for agy) - SkillsIntegration.build_command_invocation() (default: /speckit-<name>) - The '$' prefix is specific to Codex; all other skills agents use '/'. * fix: address Copilot review comments on hook invocation consistency - Add is_slash_skills_agent() helper to extensions.py to centralize the agent-to-invocation-format mapping, reducing drift risk between HookExecutor._render_hook_invocation() and init.py _display_cmd() - Use the shared helper in both locations; init.py now imports and delegates to is_slash_skills_agent() instead of maintaining its own per-agent boolean matrix - Fix test_hooks_render_skill_invocation to use ai_skills=False, proving Zed renders /speckit-<name> unconditionally - Add parameterized TestSlashSkillsSets covering all agents in ALWAYS_SLASH_AGENTS and CONDITIONAL_SLASH_AGENTS with ai_skills both true and false * fix: address Copilot review comments on type safety and test API - Make is_slash_skills_agent() accept str | None to match its call sites (init_options.get("ai") can return None) - Refactor TestSlashSkillsSets to use public execute_hook() API instead of private _render_hook_invocation() method * fix: address Copilot review comments on typing and naming clarity - Add from __future__ import annotations to extensions.py so PEP 604 unions (str | None) are safe regardless of Python version - Add clarifying _ai_skills_enabled local variable in init.py's _display_cmd() to make the semantic meaning explicit when passing it to is_slash_skills_agent() * fix: move invocation-style logic into shared _invocation_style module - Extract ALWAYS_SLASH_AGENTS, CONDITIONAL_SLASH_AGENTS, and is_slash_skills_agent() from extensions.py into new _invocation_style.py module, eliminating the awkward init.py -> extensions.py import dependency for invocation-style decision logic - Both HookExecutor._render_hook_invocation() and init.py _display_cmd() now import from the shared module instead of one subsystem importing from the other - Revert /SKILL.md change: the leading slash is semantically significant (path component vs filename suffix) * fix: add None guard before i.options() in test_options_include_skills_flag get_integration() returns IntegrationBase | None, so i.options() is a type error without a None check. * fix: override test_options_include_skills_flag for Zed (always skills, no --skills flag) Zed is always skills-based and doesn't expose a --skills option. Override the inherited base test to assert --skills is absent. * fix: rename test and skip inherited test_options_include_skills_flag for Zed - Skip inherited test_options_include_skills_flag (not applicable — Zed is always skills-based with no --skills flag) - Add test_options_do_not_include_skills_flag with correct name matching the assertion (--skills is absent) * fix: add defensive non-string check in is_slash_skills_agent Reject non-string values for selected_ai to prevent TypeError from set membership checks when persisted init-options contain corrupted data (e.g. list or dict instead of string). --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
5ae7ff53d0 |
fix: skip recovered files during refresh_managed overwrite check (#2918) (#2919)
_is_managed() in install_shared_infra now consults manifest.is_recovered() before treating a hash-matching file as managed. Files marked recovered (pre-existing on disk, not installed by Spec Kit) are no longer overwritten by integration use/switch even when their hash matches the manifest entry. This closes the gap documented in the manifest API: callers using refresh_managed MUST check is_recovered first. Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
f65d9f9382 |
feat(integration): add status reporting (#2674)
* feat(integration): add status reporting * docs(integration): include status in query command docstring * fix(integration): handle Windows extended-length paths in status containment On Windows, os.readlink() (and sometimes Path.resolve()) return paths with the \\?\ extended-length prefix. Comparing such a target against a plain project root via Path.relative_to() spuriously fails, so an in-project dangling symlink was classified as `invalid` instead of `missing` — failing test_status_treats_dangling_symlink_as_missing and the windows-style variant on the Windows CI runners. Centralize the containment check in _is_within_project() and strip the \\?\ / \\?\UNC\ prefix from both sides before relative_to(). Add portable regression tests for the prefix-stripping helper and the containment contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * test(integration): restore top-level pytest import after rebase A three-way merge / rebase onto main silently dropped the module-level `import pytest` from test_integration_subcommand.py: main reorganized the import block without it (using only a local `import pytest as _pytest`), while this branch added top-level fixtures and `pytest.skip`/`pytest.raises` usage. The overlapping import-hunk edits resolved by dropping the import, breaking collection with `NameError: name 'pytest' is not defined` on every runner. Re-add the import in the third-party group. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(integration): fix Windows UNC path assertion in status helper test `test_strip_extended_length_prefix_normalizes_windows_paths` compared the str() form of the helper's output against a hand-built string. On Windows, pathlib renders a UNC root with a trailing separator (`\\server\share\`), so the exact string match failed there (`\\server\share\` != `\\server\share`) even though `_strip_extended_length_prefix` behaves correctly — the trailing separator is irrelevant to the `relative_to` containment check it feeds. Compare Path objects (semantic equality) instead of exact strings so the assertion holds on both POSIX and Windows. No production code change needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(integration): make shared-manifest remediation specify --integration The fallback `_manifest_suggestion` for the shared `speckit` manifest (used when no usable default integration is recorded) suggested `specify init --here --force`, which can trigger interactive integration selection. For CI/agent consumers of `integration status`, surface an explicit `--integration <key>` placeholder, matching the file's existing `<key>` suggestion style. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
927f54feea |
feat: make git extension opt-in and remove --no-git at v0.10.0 (#2873)
* feat(init)!: make git extension opt-in and remove --no-git at v0.10.0 - Remove --no-git parameter from specify init command - Remove git extension auto-installation from init flow - Git repository initialization (git init) still runs when git is available - Remove --no-git from all test invocations across the test suite - Update docs to reflect opt-in git extension behavior - Replace TestGitExtensionAutoInstall with TestGitExtensionOptIn tests BREAKING CHANGE: specify init no longer auto-installs the git extension. Use `specify extension add git` to install it explicitly. The --no-git flag has been removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(scripts): remove git operations from core scripts Git functionality is now entirely managed by the git extension. Core scripts only handle directory-based feature creation and numbering. - Remove has_git(), check_feature_branch(), git branch creation from core - Simplify number detection to use only spec directory scanning - Remove HAS_GIT output from get_feature_paths() - Remove git remote fetching and branch querying - Keep BRANCH_NAME output key for backward compatibility Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: remove all git operations from core - Remove is_git_repo() and init_git_repo() dead code from _utils.py - Remove --branch-numbering from init command - Remove git from 'specify check' (now extension-only) - Update docs: git is optional prerequisite, check command description - Fix tests to reflect no-git-in-core reality (fallback to main) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(scripts): remove directory scanning and branch fallback from core Core scripts now resolve feature context exclusively from: 1. SPECIFY_FEATURE env var (set by git extension) 2. .specify/feature.json (persisted by specify command) Removed find_feature_dir_by_prefix() and directory scanning heuristics — these are the git extension's responsibility. Scripts error clearly when no feature context is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: introduce feature_numbering, deprecate branch_numbering in init-options - specify command template now reads feature_numbering (preferred) with fallback to branch_numbering (deprecated) from init-options.json - Git extension reads git-config.yml > feature_numbering > branch_numbering - init now writes feature_numbering: sequential to init-options.json - Deprecation warning emitted when branch_numbering is used as fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove trailing whitespace in common.ps1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(scripts): persist SPECIFY_FEATURE_DIRECTORY env var to feature.json When SPECIFY_FEATURE_DIRECTORY is set, get_feature_paths() now writes the value to .specify/feature.json so future sessions without the env var can still resolve the feature directory. The write is idempotent — it skips when the file already contains the same value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address review feedback — error messages and docs - Update error messages in common.sh and common.ps1 to reference SPECIFY_FEATURE_DIRECTORY instead of SPECIFY_FEATURE (which no longer resolves feature directories) - Fix get_current_branch comment (returns empty string, not error) - Update upgrade.md to reference SPECIFY_FEATURE_DIRECTORY with correct example paths - Update local-development.md troubleshooting: replace stale 'Git step skipped' row with actionable git extension guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(scripts): harden feature.json persistence - Use json_escape in printf fallback when jq is unavailable (common.sh) - Replace utf8NoBOM encoding with UTF8Encoding($false) for PowerShell 5.1 compatibility (common.ps1) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(scripts): remove dead feature_json_matches_feature_dir functions These guards are no longer needed since the branch-name validation they protected against has been removed from check-prerequisites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(git-ext): rename create-new-feature to create-new-feature-branch The git extension's script only creates the git branch — rename it to reflect that responsibility. The core create-new-feature.sh/.ps1 handles feature directory creation and feature.json persistence. Also includes fixes from review feedback: - common.sh: _persist_feature_json uses json_escape fallback - common.ps1: Save-FeatureJson uses UTF8Encoding for PS 5.1 compat - common.ps1: case-sensitive path stripping on non-Windows - create-new-feature.sh/ps1: output both SPECIFY_FEATURE and SPECIFY_FEATURE_DIRECTORY - setup-tasks.sh: fix stale 'Validate branch' comment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(tests): update references to renamed git extension scripts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(tests): remove duplicate EXT_CREATE_FEATURE assignments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
67fecd357a |
chore(tests): fix ruff lint violations in tests/ (#2827)
Clear pre-existing lint debt flagged by repo-wide `ruff check` (the lint config only scopes src/, so tests/ had drifted). No behavior change. - F401/F541: drop unused imports and redundant f-string prefixes (autofix) - E741: rename ambiguous `l` to `ln` in comprehensions - E702: split semicolon-joined statements onto separate lines - F841: drop unused bindings while keeping the side-effecting calls (_minimal_feature, install_from_directory) Full suite: 3344 passed, 40 skipped. ruff check (repo-wide): clean. |
||
|
|
d79a514b30 |
fix: remove unsupported mode: frontmatter from Copilot skills mode (fixes #2799) (#2819)
VS Code Copilot Agent Skills do not support the `mode:` frontmatter field. The generated SKILL.md files included `mode: speckit.<stem>` injected by CopilotIntegration.post_process_skill_content(), which had no effect in VS Code and could cause confusion. Simplify post_process_skill_content to delegate directly to _CopilotSkillsHelper without injecting mode:. Update tests to assert mode: is absent from generated skill frontmatter. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ee17b04784 |
refactor(integrations): co-locate integration commands in integrations/ domain dir (PR-5/8) (#2720)
* refactor(integrations): co-locate integration commands in integrations/ domain dir
- Remove commands/ stubs (handlers will live in domain dirs)
- Move all integration CLI handlers out of __init__.py into integrations/
- Split into focused modules under integrations/:
_helpers.py (340 lines) — domain helpers
_install_commands.py (306 lines) — install / uninstall
_migrate_commands.py (487 lines) — switch / upgrade
_query_commands.py (442 lines) — list / use / search / info / catalog
_commands.py (34 lines) — app objects + register()
- __init__.py reduced by ~1400 lines; integration block replaced with register() call
- Fix patch paths in tests to new module locations
* fix(integrations): restore original integration list output in refactor
Preserve the CLI Required column, post-table default/installed summary,
and no-installed guidance that were dropped during the no-behavior-change
refactor of integration list into _query_commands.py.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(integrations): restore _clear/_update_init_options public imports
The refactor that split integration commands moved
_clear_init_options_for_integration and _update_init_options_for_integration
into integrations/_helpers.py, but tests still import them from the top-level
specify_cli package, causing ImportError. Re-export them with explicit aliases
at the end of __init__.py to preserve the public import surface.
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
||
|
|
44aac9f6e4 |
feat: add native Cline integration (#2508)
* test: strip ansi to make asserts work * feat: add native Cline integration |
||
|
|
50da3a0f77 |
Extract agent context updates into bundled agent-context extension (#2546)
* Initial plan * Extract agent context updates into bundled agent-context extension * 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> * 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> * fix: address review comments on agent-context extension - bash: parse init-options.json with a single python3 invocation instead of three separate read_json_field calls, for parity with the PowerShell ConvertFrom-Json approach and to avoid divergent error semantics - bash: use parameter expansion to strip PROJECT_ROOT prefix from plan path instead of sed interpolation, avoiding special-character fragility - powershell: limit Get-ChildItem to -Depth 1 so plan.md discovery matches the bash glob specs/*/plan.md (one level deep) — fixes cross-platform inconsistency with nested plan.md files - powershell: replace Substring+Length relative-path with [System.IO.Path]::GetRelativePath for robustness across case/PSDrive differences - __init__.py: move agent-context extension install to after save_init_options so init-options.json is present when hooks run - __init__.py: seed context_markers in init-options only when context_file is truthy; avoids noise for integrations without a context file - integrations/base.py: narrow blanket except Exception in _resolve_context_markers to ImportError / (OSError, ValueError) so unexpected bugs surface instead of being silently swallowed * fix: gate context_markers in _update_init_options_for_integration on context_file Apply the same gating logic used during `specify init`: only write context_markers to init-options.json when the integration actually has a context_file set. When switching to an integration without a context file the stale markers are removed, keeping the two init paths consistent. * fix: move context_file/context_markers from init-options.json to agent-context extension config * Potential fix for pull request finding 'Unused global variable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * fix: clarify local import comment in agents.py * Fix remaining agent-context review findings * Fix follow-up agent-context review issues * Address review feedback: narrow except, improve PyYAML messaging, surface config-written note * Fix double-space in PyYAML install hint message * Potential fix for pull request finding 'Empty except' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Potential fix for pull request finding 'Empty except' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Address latest agent-context review feedback * Harden bash config parse output handling * Clarify ImportError-only fallback comment * Apply review feedback: drop dead try/except, guard ext-config creation, explicit ConvertFrom-Yaml check * Remove redundant $Options = $null in PS1 catch block * Add constitution directives, deprecation warning, agent-context auto-install, and init flow fix - Add constitution-loading directive to specify, clarify, tasks, checklist, taskstoissues commands - Add deprecation warning (v0.12.0) in upsert_context_section() - Auto-install agent-context extension during specify init - Move context_file from init-options.json to agent-context extension config - Add tests: deprecation warning, corrupt config, constitution directives - Update file inventories across all integration tests * Address review: fix init ordering, test coverage, and hermes inventory - Move agent-context extension install after init-options.json is saved so skill registration can read ai_skills + integration key - Write extension config after install (avoids template overwriting context_file) - Fix test_defaults_when_markers_field_missing to truly test missing markers key - Update hermes tests to allow extension-installed agent-context skill * Address review: chmod ordering, preserve markers, PS1 Python check, YAML key order - Move ensure_executable_scripts after agent-context extension install so extension scripts get execute bits set - Use preserve_markers=True on reinit to keep user-customized markers - Add Python 3 version check in PowerShell fallback (matching bash behavior) - Add sort_keys=False to yaml.safe_dump for stable config output * Address review: path traversal guards and docstring fix - Reject absolute paths and '..' segments in context_file in both bash and PowerShell scripts to prevent writes outside the project root - Fix docstring in _update_init_options_for_integration to accurately describe marker preservation behavior * Address review: strict enabled check, docstring, segment-level path traversal - Use 'is not False' for enabled check so only literal False disables - Update upsert_context_section docstring to mention disabled-extension return - Fix path traversal guards to check actual path segments, not substrings (allows filenames like 'notes..md' while rejecting '../' traversal) * Address review: UnicodeError handling, missing extension warning - Add UnicodeError to exception tuples in _load_agent_context_config and _resolve_context_markers so garbled UTF-8 config files fall back to defaults - Emit error (with reinstall command) instead of silent skip when bundled agent-context extension is not found during init * Address review: bash backslash traversal guard, wheel packaging - Reject backslash separators and Windows drive-letter paths in bash context_file validation (prevents traversal on Git-Bash/Windows) - Add extensions/agent-context to pyproject.toml force-include so the bundled extension is included in wheel builds * Address review: write extension config before init-options.json - Reorder writes in _update_init_options_for_integration so the agent-context extension config is updated first; if it fails, init-options.json remains consistent with the previous state --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> |
||
|
|
57a518a583 |
Fix shared script command hints for integration separators (#2627)
* fix shared script command refs for integration separators * Fix integration use shared infra refresh hint * Clarify shared infrastructure force wording --------- Co-authored-by: root <1647273252@qq.com> Co-authored-by: root <kinsonnee@gmail.com> |
||
|
|
c7e0cacaff |
fix: PS 5.1 compat — replace non-ASCII chars in shipped PowerShell scripts (#2709)
* Initial plan * fix: replace non-ASCII chars in PS1 files, add encoding regression tests, fix ANSI stripping in tests, update docs --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
dca81b90de | fix init-options speckit version refresh (#2647) | ||
|
|
c87081a50a | fix(integration): clarify multi-install guidance (#2549) | ||
|
|
9732a4d092 |
fix(opencode): use commands/ directory (plural) to match OpenCode docs (#2453)
* fix(opencode): use commands/ directory (plural) to match OpenCode docs OpenCode documentation (https://opencode.ai/docs/commands/) uses .opencode/commands/ (plural) as the canonical command directory. The OpenCode runtime supports both .opencode/command/ and .opencode/commands/ via a {command,commands} glob, but the singular form was the original convention and is now outdated. Update the OpenCode integration to write to .opencode/commands/ instead of .opencode/command/, aligning with the documented standard and the OpenSpec fix (Fission-AI/OpenSpec#748). Signed-off-by: Marcus Burghardt <maburgha@redhat.com> Assisted-by: OpenCode (claude-opus-4-6) * feat(registrar): add legacy_dir fallback for backward-compatible directory migration Add _resolve_agent_dir() to CommandRegistrar that checks a legacy_dir fallback when the canonical directory does not exist. When legacy_dir is found, a deprecation warning directs users to run "specify integration upgrade" to migrate. The OpenCode integration declares legacy_dir: ".opencode/command" so that extension and preset registration, as well as command cleanup, continue working for projects that have not yet migrated to .opencode/commands/. The legacy_dir mechanism is opt-in: integrations that do not declare it get no fallback and no behavioral change. Add end-to-end test verifying that "specify integration upgrade opencode" migrates commands from legacy .opencode/command/ to canonical .opencode/commands/ and removes stale files. Signed-off-by: Marcus Burghardt <maburgha@redhat.com> Assisted-by: OpenCode (claude-opus-4-6) * fix(registrar): address PR review feedback on legacy_dir handling - Fix deprecation warning formatting: quote paths and remove trailing '/.' that produced confusing '.opencode/commands/.' output - Eliminate duplicate warnings: pass pre-resolved directory to register_commands() via _resolved_dir parameter so _resolve_agent_dir() is only called once per agent - Fix unregister_commands() to clean both canonical and legacy dirs when both exist, preventing orphaned command files after upgrade - Add test_unregister_cleans_legacy_when_both_dirs_exist regression test and tighten warning count assertion to exactly 1 Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Marcus Burghardt <maburgha@redhat.com> --------- Signed-off-by: Marcus Burghardt <maburgha@redhat.com> |
||
|
|
947b4398c7 |
fix(integration): refresh shared infra on integration switch (#2375)
* fix(integration): refresh shared infra on integration switch * fix(integration): address Copilot review on switch shared-infra refresh - Clarify install_shared_infra docstring: force overwrites regular files but always preserves symlinks (safe-destination check refuses to follow). - Print refresh_hint only for preserved_user_files; skipped_files keeps the generic remediation. Avoids misleading guidance when files were merely skipped (not detected as customized). - Catch ValueError from the safe-destination check and bucket the path under a new symlinked_files warning instead of aborting the switch. - Restore templates/constitution-template.md to upstream (drop accidental leading blank lines). * fix(integration): narrow symlink bucketing to dedicated exception Address Copilot feedback on shared_infra.py:305 — _safe_dest_or_bucket caught any ValueError as 'symlinked', which masked genuine safety errors (path escape, parent-not-a-directory). - Introduce SymlinkedSharedPathError(ValueError) raised only by the symlink-specific branches in _ensure_safe_shared_*(). - _safe_dest_or_bucket() now catches only SymlinkedSharedPathError; other ValueErrors propagate so the operation aborts with the real cause instead of being silently bucketed. - Wrap top-level dest_scripts/dest_variant/dest_templates mkdir calls in the same bucket helper so a symlinked .specify/scripts or .specify/templates is preserved with a warning rather than aborting the switch (matches the documented 'preserve customizations' behavior). - Update tests to expect the new bucket+warn behavior for leaf-level symlinked destinations. * fix(integration): tailor shared-infra warnings and rename preflight test Address Copilot review on PR #2375: - skipped_files hint now uses refresh_hint when refresh_managed=True so integration switch suggests --refresh-shared-infra instead of the generic init/upgrade flags. - symlinked-files warning header says "path(s)" rather than "file(s)" since symlinked directories (e.g. .specify/scripts/bash) are also bucketed there. - Rename test_shared_infra_install_preflights_before_writing to test_shared_infra_install_buckets_unsafe_destinations_and_continues to match the new bucket-and-continue semantics. * test: rename symlink bucketing tests to reflect bucket-and-continue behavior The two file-bucketing tests at line 300/320 were named *_refuses_*, but the new behavior buckets symlinked file destinations with a warning while safe destinations in the same install still complete. Rename to *_buckets_* and update docstrings to match. The remaining *_refuses_* tests (line 342/362/381) genuinely raise on symlinked dirs/manifests and keep their names. --------- Co-authored-by: Quratulain-bilal <quratulain.bilal@users.noreply.github.com> |
||
|
|
38fd1f6cc2 |
Support controlled multi-install for safe AI agent integrations (#2389)
* support controlled multi-install integrations * fix: harden multi-install integration state * refactor: isolate integration runtime helpers * fix: address copilot review feedback * fix: address follow-up copilot feedback * fix: tighten integration switch semantics * fix: address final copilot review feedback * fix: harden integration manifest read errors * fix: refuse symlinked shared infra paths * test: filter expected self-test preset warning * test: address copilot review nits * refactor: centralize safe shared infra writes * fix: use no-follow writes for shared infra * fix: keep default integration atomic on template refresh * fix: harden shared infra error paths * fix: preflight shared infra and future state schemas * fix: support nested shared scripts during preflight * test: tolerate wrapped schema error output * fix: use safe default mode for shared text writes * fix: use posix paths in shared skip output * fix: share project guard for integration use * fix: centralize spec-kit project guards * fix: use posix project paths in cli output * fix: harden shared manifest and upgrade refresh |
||
|
|
5edc9a5358 |
fix: migrate extension commands on integration switch (#2404)
* fix: migrate extension commands on integration switch When switching integrations (e.g. kimi → opencode), extension commands were not re-registered for the new agent, leaving the new agent without extension support and orphaning files in the old agent's directory. Changes: - Add ExtensionManager.unregister_agent_artifacts() to clean up old agent extension files and registry entries during switch - Add ExtensionManager.register_enabled_extensions_for_agent() to re-register all enabled extensions for the new agent - Wire both into integration_switch() after uninstall/install phases - Handle skills mode (Copilot --skills) correctly - Add tests for kimi→opencode→claude migration, Copilot skills mode, and disabled extension handling Fixes extension commands not appearing after integration switch. * Update src/specify_cli/extensions.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+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> |