mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix: address third round of review feedback (multi-integration semantics)
Fixes five deeper active-only registration bugs surfaced by Copilot review
after 2486c08, all in the presets/extensions single-active integration
rule (#2948):
1. presets: _reconcile_composed_commands (run after install/remove)
bypassed the active-only filter entirely, writing composition-winner
command files for every detected non-skill agent via
register_commands_for_non_skill_agents. Added an only_agent param to
that registrar method (mirroring register_commands_for_all_agents)
and threaded it through all 5 reconciliation call sites.
2. presets: `integration use copilot` with --skills (ai_skills: true)
wrote both the static .agent.md command file AND the SKILL.md
mirror for the same override. Mirrored the extension path's
ai_skills guard in both _register_commands and the reconciliation
pass: a command-backed active agent running in skills mode is
excluded from non-skill command registration.
3. presets: registered_skills was a flat list, so switching between
two skill-mode agents (e.g. Claude -> Codex) and then removing the
preset only restored the currently active agent's directory,
permanently orphaning the other. _unregister_skills now restores
every existing skill-mode agent directory instead of only the
active one.
4. extensions: load_init_options() collapses "no file" and "corrupted
file" into the same {}, so the round-2 fail-closed fix didn't
actually distinguish them. Added a shared
resolve_active_agent_for_registration() helper in _init_options.py
that checks file existence separately from parse success, returning
a distinct sentinel for "file absent" vs None for "corrupted or
invalid". extensions/__init__.py now uses this helper.
5. presets: same corruption-collapsing bug in _register_commands's
active_agent resolution. Now uses the same shared helper as (4).
Adds regression tests for all five: reconciliation active-only
filtering, copilot --skills dual-write prevention, multi-skill-agent
switch+remove, and corrupted init-options fail-closed behavior for
both extension add and preset add. Each test was verified to fail
against the pre-fix code and pass with the fix.
Targeted (883) and full (3923 passed, 109 skipped) suites pass; ruff
check clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1490,6 +1490,41 @@ class TestIntegrationInstall:
|
||||
"back-fill every detected agent (#2948)"
|
||||
)
|
||||
|
||||
def test_extension_add_corrupted_init_options_file_fails_closed(self, tmp_path):
|
||||
"""A present-but-unparseable init-options.json must fail closed too,
|
||||
not be treated the same as "no file at all".
|
||||
|
||||
``load_init_options`` returns ``{}`` for a corrupted/unreadable
|
||||
file just like it does for a missing file, so a naive "no active
|
||||
agent recorded" check based on ``load_init_options`` alone can't
|
||||
tell a legacy pre-init-options project (legitimate all-agent
|
||||
fallback) apart from a corrupted-but-present file for a #2948
|
||||
project (must fail closed). Corrupting the file after a normal
|
||||
init must not reintroduce the all-agent fallback.
|
||||
"""
|
||||
project = _init_project(tmp_path, "claude")
|
||||
|
||||
result = _run_in_project(project, [
|
||||
"integration", "install", "codex",
|
||||
"--script", "sh",
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
init_options_path = project / ".specify" / "init-options.json"
|
||||
init_options_path.write_text("{not valid json", encoding="utf-8")
|
||||
|
||||
result = _run_in_project(project, ["extension", "add", "git"])
|
||||
assert result.exit_code == 0, f"extension add failed: {result.output}"
|
||||
|
||||
registry_path = project / ".specify" / "extensions" / ".registry"
|
||||
registered = json.loads(registry_path.read_text(encoding="utf-8"))[
|
||||
"extensions"
|
||||
]["git"]["registered_commands"]
|
||||
assert registered == {}, (
|
||||
"a corrupted init-options.json must fail closed, not be "
|
||||
"treated like a legacy project missing the file entirely (#2948)"
|
||||
)
|
||||
|
||||
|
||||
# ── uninstall ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user