Files
github-spec-kit/src/specify_cli/integration_runtime.py
Pascal THUET 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
2026-05-01 11:54:41 -05:00

91 lines
2.7 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,
) -> 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)
current["invoke_separator"] = integration.effective_invoke_separator(parsed_options)
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,
) -> str:
"""Resolve the invocation separator for stored/default integration state."""
if parsed_options is not None:
return integration.effective_invoke_separator(parsed_options)
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)
return integration.effective_invoke_separator(None)