mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
[bug-fix] Fix upgrade-overwrites-copilot-skills: pass force=True to extension skill re-registration after upgrade (#3853)
* Fix upgrade-overwrites-copilot-skills: pass force=True to extension skill re-registration after upgrade Apply the remediation from the bug assessment on issue #3849. _register_extension_skills() had a skip guard that refused to overwrite existing SKILL.md files (protecting user customizations). In the upgrade path, setup() regenerates all core-template SKILL.md files first, then calls register_enabled_extensions_for_agent(). The guard then sees those freshly-written core files as 'existing' and skips every extension, leaving only core template content on disk. Fix: add force: bool = False to _register_extension_skills() and thread it through register_enabled_extensions_for_agent() and _register_extensions_for_agent(). In integration_upgrade(), pass force=True so extension content layers on top of the just-regenerated core files. The force flag is off-by-default so plain extension add still protects user-modified skill files. Refs #3849 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding 'Unused local variable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * test: add end-to-end regression guard for upgrade-overwrites-copilot-skills (#3849) The existing regression tests in TestRegisterExtensionSkillsForceFlag exercise the new force parameter at the helper level, so without the fix they fail only with a TypeError (unknown kwarg) rather than on the user-facing behaviour. Add a command-level test that runs 'specify integration upgrade copilot --skills --force' end-to-end and asserts the installed git extension's SKILL.md is restored (with its extension content, not a bare core-template stub) when the skill directory already exists — the exact skill_dir_preexists path the bug depends on. The test fails on pre-fix source (the skill is never recreated) and passes with the fix, so it is a genuine behavioural regression guard rather than an API-surface check. Refs #3849 Assisted-by: GitHub Copilot (model: claude-opus-4.8, autonomous) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+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>
This commit is contained in:
committed by
GitHub
parent
89126f3a33
commit
8394c8d536
@@ -1324,6 +1324,7 @@ class ExtensionManager:
|
||||
manifest: ExtensionManifest,
|
||||
extension_dir: Path,
|
||||
link_outputs: bool = False,
|
||||
force: bool = False,
|
||||
) -> List[str]:
|
||||
"""Generate SKILL.md files for extension commands as agent skills.
|
||||
|
||||
@@ -1337,6 +1338,11 @@ class ExtensionManager:
|
||||
extension_dir: Installed extension directory.
|
||||
link_outputs: If True, create dev-mode symlinks for rendered
|
||||
skill files when supported by the OS.
|
||||
force: If True, overwrite existing SKILL.md files even when they
|
||||
are not dev-mode symlinks. Use in the upgrade path, where
|
||||
``setup()`` has just freshly regenerated core-template skill
|
||||
files and the skip guard would otherwise prevent extension
|
||||
content from being layered on top.
|
||||
|
||||
Returns:
|
||||
List of skill names that were created (for registry storage).
|
||||
@@ -1424,13 +1430,16 @@ class ExtensionManager:
|
||||
)
|
||||
# Do not overwrite user-customized skills, but allow dev-mode
|
||||
# symlinks that point back to this extension's generated cache
|
||||
# to be refreshed on a subsequent dev install.
|
||||
if not is_expected_dev_symlink:
|
||||
# to be refreshed on a subsequent dev install. In the upgrade
|
||||
# path (force=True) the file was just written by setup(), so
|
||||
# overwriting it with the composed extension content is correct.
|
||||
if not is_expected_dev_symlink and not force:
|
||||
continue
|
||||
elif skill_dir_preexists:
|
||||
elif skill_dir_preexists and not force:
|
||||
# Never add files to a pre-existing user directory. Without a
|
||||
# verifiable SKILL.md ownership marker, rollback/removal cannot
|
||||
# distinguish our output from unrelated user artifacts.
|
||||
# Skipped when force=True (upgrade path).
|
||||
continue
|
||||
|
||||
# Create skill directory; track whether we created it so we can clean
|
||||
@@ -2669,7 +2678,7 @@ class ExtensionManager:
|
||||
if updates:
|
||||
self.registry.update(ext_id, updates)
|
||||
|
||||
def register_enabled_extensions_for_agent(self, agent_name: str) -> None:
|
||||
def register_enabled_extensions_for_agent(self, agent_name: str, *, force: bool = False) -> None:
|
||||
"""Register installed, enabled extensions for ``agent_name``.
|
||||
|
||||
Command-file registration is scoped to the explicit ``agent_name``
|
||||
@@ -2787,7 +2796,7 @@ class ExtensionManager:
|
||||
if agent_name == active_agent:
|
||||
try:
|
||||
registered_skills = self._register_extension_skills(
|
||||
manifest, ext_dir
|
||||
manifest, ext_dir, force=force
|
||||
)
|
||||
except Exception as skills_err:
|
||||
# Skills are a companion artifact. If command registration
|
||||
|
||||
@@ -395,6 +395,7 @@ def _register_extensions_for_agent(
|
||||
agent_key: str,
|
||||
*,
|
||||
continuing: str,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Register all enabled extensions' commands/skills for ``agent_key``.
|
||||
|
||||
@@ -408,6 +409,11 @@ def _register_extensions_for_agent(
|
||||
before registering), so extension *skill* rendering — which is scoped to
|
||||
the active ``ai`` / ``ai_skills`` init-options — matches ``agent_key``.
|
||||
|
||||
When ``force=True``, existing skill files are overwritten even when they
|
||||
are not dev-mode symlinks. Pass ``force=True`` in the upgrade path so that
|
||||
extension content is layered on top of the core-template files that
|
||||
``setup()`` just regenerated (fixes the skip-guard bug for skills mode).
|
||||
|
||||
Best-effort: never aborts the surrounding integration operation. Callers
|
||||
invoke it *after* the use/upgrade/switch transaction has committed so a
|
||||
failure here cannot trigger a rollback.
|
||||
@@ -415,7 +421,7 @@ def _register_extensions_for_agent(
|
||||
_best_effort_extension_op(
|
||||
project_root,
|
||||
agent_key,
|
||||
lambda mgr, key: mgr.register_enabled_extensions_for_agent(key),
|
||||
lambda mgr, key: mgr.register_enabled_extensions_for_agent(key, force=force),
|
||||
phase="register extension artifacts for",
|
||||
continuing=continuing,
|
||||
)
|
||||
|
||||
@@ -886,6 +886,7 @@ def integration_upgrade(
|
||||
_register_extensions_for_agent(
|
||||
project_root,
|
||||
key,
|
||||
force=True,
|
||||
continuing="The integration was upgraded, but installed extensions may need re-registration.",
|
||||
)
|
||||
_register_presets_for_agent(
|
||||
|
||||
Reference in New Issue
Block a user