Files
github-spec-kit/src/specify_cli/integration_runtime.py
Ali jawwad 93dbf6d575 fix(integrations): recompute invoke_separator from retained parsed_options (#3664)
with_integration_setting recomputed invoke_separator from the raw
parsed_options argument. When only script_type changes (parsed_options and
raw_options both None), the previously-stored parsed_options are retained on
the setting, but the separator was derived from the None argument — dropping
an options-dependent separator (e.g. Copilot --skills -> "-") back to the
default ".", desynchronizing invoke_separator from the stored options.

Derive the separator from current.get("parsed_options") — the options
actually stored after the update — so it stays consistent in every branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:44:55 -05:00

102 lines
3.3 KiB
Python

"""Runtime helpers for integration commands."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from .integration_state import integration_setting, integration_settings
ParseOptions = Callable[[Any, str], dict[str, Any] | None]
def resolve_integration_options(
integration: Any,
state: dict[str, Any],
key: str,
raw_options: str | None,
*,
parse_options: ParseOptions,
) -> tuple[str | None, dict[str, Any] | None]:
"""Resolve raw and parsed options for an integration operation."""
if raw_options is not None:
return raw_options, parse_options(integration, raw_options)
setting = integration_setting(state, key)
stored_raw = setting.get("raw_options")
if not isinstance(stored_raw, str):
stored_raw = None
stored_parsed = setting.get("parsed_options")
if isinstance(stored_parsed, dict):
return stored_raw, stored_parsed or None
if stored_raw:
return stored_raw, parse_options(integration, stored_raw)
return None, None
def with_integration_setting(
state: dict[str, Any],
key: str,
integration: Any,
*,
script_type: str | None = None,
raw_options: str | None = None,
parsed_options: dict[str, Any] | None = None,
project_root: Any = None,
) -> dict[str, dict[str, Any]]:
"""Return integration settings with *key* updated."""
settings = integration_settings(state)
current = dict(settings.get(key, {}))
if script_type:
current["script"] = script_type
if raw_options is not None:
current["raw_options"] = raw_options
elif "raw_options" in current and not current.get("raw_options"):
current.pop("raw_options", None)
if parsed_options is not None:
current["parsed_options"] = parsed_options
elif raw_options is not None:
current.pop("parsed_options", None)
# Recompute the separator from the options actually STORED on ``current``
# after the update, not the raw ``parsed_options`` argument. When only
# ``script_type`` changes (``parsed_options`` and ``raw_options`` both
# None), the previously-stored ``parsed_options`` are retained above, so
# deriving the separator from the argument (None) would drop an
# options-dependent separator (e.g. Copilot ``--skills`` -> "-") back to
# the default ".".
current["invoke_separator"] = integration.effective_invoke_separator(
current.get("parsed_options"), project_root
)
settings[key] = current
return settings
def invoke_separator_for_integration(
integration: Any,
state: dict[str, Any],
key: str,
parsed_options: dict[str, Any] | None = None,
project_root: Any = None,
) -> str:
"""Resolve the invocation separator for stored/default integration state."""
if parsed_options is not None:
return integration.effective_invoke_separator(parsed_options, project_root)
setting = integration_setting(state, key)
stored_separator = setting.get("invoke_separator")
if isinstance(stored_separator, str) and stored_separator:
return stored_separator
stored_parsed = setting.get("parsed_options")
if isinstance(stored_parsed, dict):
return integration.effective_invoke_separator(stored_parsed, project_root)
return integration.effective_invoke_separator(None, project_root)