fix(integration): preserve ai_skills on use for skills-mode Copilot (#3550) (#3551)

`specify integration use copilot` against a Copilot install configured with
`--integration-options "--skills"` dropped `"ai_skills": true` from
init-options.json and regenerated extension commands in the legacy
`.agent.md`/`.prompt.md` layout, contradicting `integration.json`'s stored
`parsed_options.skills: true`.

`_update_init_options_for_integration` only inspected `SkillsIntegration` /
the instance `_skills_mode` flag. On the `use` path no `setup()` runs, so the
freshly-resolved Copilot instance has `_skills_mode == False` and the stored
skills intent in `parsed_options` was ignored. Thread the resolved
`parsed_options` through and treat `parsed_options["skills"]` as skills mode.

Adds a regression test that resets the registry singleton's `_skills_mode` to
simulate a fresh process (in-process singleton reuse otherwise masks the bug).

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)


Copilot-Session: 06fb6ae9-f444-4dfd-ab3f-d0669c5d0604

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Manfred Riem
2026-07-15 11:18:54 -05:00
committed by GitHub
parent 5409670c13
commit 2fb18c73cb
2 changed files with 52 additions and 2 deletions

View File

@@ -260,6 +260,7 @@ def _update_init_options_for_integration(
project_root: Path,
integration: Any,
script_type: str | None = None,
parsed_options: dict[str, Any] | None = None,
) -> None:
"""Update init-options.json to reflect *integration* as the active one.
@@ -278,7 +279,17 @@ def _update_init_options_for_integration(
opts["speckit_version"] = _get_speckit_version()
if script_type:
opts["script"] = script_type
if isinstance(integration, SkillsIntegration) or getattr(integration, "_skills_mode", False):
# Skills mode is either intrinsic (SkillsIntegration), set on the instance
# during setup() (_skills_mode), or requested via parsed options (e.g.
# Copilot's --skills, persisted as parsed_options["skills"]). The latter is
# the only signal available on the `use` path, where no setup() runs and a
# fresh integration instance has _skills_mode == False (issue #3550).
skills_mode = (
isinstance(integration, SkillsIntegration)
or getattr(integration, "_skills_mode", False)
or bool((parsed_options or {}).get("skills"))
)
if skills_mode:
opts["ai_skills"] = True
else:
opts.pop("ai_skills", None)
@@ -334,7 +345,9 @@ def _set_default_integration(
) from exc
_write_integration_json(project_root, key, installed_keys, settings)
_update_init_options_for_integration(project_root, integration, script_type=resolved_script)
_update_init_options_for_integration(
project_root, integration, script_type=resolved_script, parsed_options=parsed_options
)
def _set_default_integration_or_exit(*args: Any, **kwargs: Any) -> None: