@@ -1371,6 +1371,53 @@ def test_install_skills_mode_secondary_agent_defers_extension_artifacts(self, tm
13711371 project / ".github" / "skills" / "speckit-git-feature" / "SKILL.md"
13721372 ).exists ()
13731373
1374+ def test_extension_add_registers_active_integration_only (self , tmp_path ):
1375+ """``extension add`` registers commands for the active integration only.
1376+
1377+ Maintainer-requested behavior for #2948: with multiple integrations
1378+ installed, ``extension add`` must treat the project as single-active —
1379+ only the current integration gets the new extension's commands.
1380+ Non-active integrations receive them when selected via
1381+ ``integration use`` / ``switch`` (rescaffold).
1382+ """
1383+ project = _init_project (tmp_path , "claude" )
1384+
1385+ result = _run_in_project (project , [
1386+ "integration" , "install" , "codex" ,
1387+ "--script" , "sh" ,
1388+ ])
1389+ assert result .exit_code == 0 , result .output
1390+
1391+ result = _run_in_project (project , ["extension" , "add" , "git" ])
1392+ assert result .exit_code == 0 , f"extension add failed: { result .output } "
1393+
1394+ registry_path = project / ".specify" / "extensions" / ".registry"
1395+ registered = json .loads (registry_path .read_text (encoding = "utf-8" ))[
1396+ "extensions"
1397+ ]["git" ]["registered_commands" ]
1398+ assert "claude" in registered , "active integration gets the extension"
1399+ assert "codex" not in registered , (
1400+ "non-active integration must not be registered on add (#2948)"
1401+ )
1402+ assert (
1403+ project / ".claude" / "skills" / "speckit-git-feature" / "SKILL.md"
1404+ ).exists ()
1405+ assert not (
1406+ project / ".agents" / "skills" / "speckit-git-feature" / "SKILL.md"
1407+ ).exists ()
1408+
1409+ # Selecting the other integration rescaffolds it with the extension.
1410+ result = _run_in_project (project , ["integration" , "use" , "codex" ])
1411+ assert result .exit_code == 0 , result .output
1412+
1413+ registered = json .loads (registry_path .read_text (encoding = "utf-8" ))[
1414+ "extensions"
1415+ ]["git" ]["registered_commands" ]
1416+ assert "codex" in registered , "use registers extensions for the new active agent"
1417+ assert (
1418+ project / ".agents" / "skills" / "speckit-git-feature" / "SKILL.md"
1419+ ).exists ()
1420+
13741421
13751422# ── uninstall ────────────────────────────────────────────────────────
13761423
@@ -2492,13 +2539,13 @@ def test_upgrade_restores_executable_bit_on_shared_scripts(self, tmp_path):
24922539 "shared .sh scripts must be executable after upgrade"
24932540 )
24942541
2495- def test_upgrade_backfills_extension_commands_for_agent (self , tmp_path ):
2496- """Upgrade re-registers enabled extensions for the upgraded agent .
2542+ def test_upgrade_does_not_backfill_non_active_integration (self , tmp_path ):
2543+ """Upgrading a non-active integration must not register extensions for it .
24972544
2498- Regression for #2886: agents installed before extension back-fill
2499- existed (or whose extension artifacts went missing) should regain the
2500- enabled extensions' commands on ``upgrade``, reaching parity with
2501- ``switch`` .
2545+ Maintainer-requested behavior for #2948 (reverses the #2886 upgrade
2546+ back-fill): non-active integrations only receive extension artifacts
2547+ when selected via ``integration use`` / ``switch``. Upgrade of a
2548+ non-active integration refreshes its own files and nothing else .
25022549 """
25032550 project = _init_project (tmp_path , "claude" )
25042551
@@ -2511,34 +2558,52 @@ def test_upgrade_backfills_extension_commands_for_agent(self, tmp_path):
25112558 ])
25122559 assert result .exit_code == 0 , result .output
25132560
2514- # Simulate a project created before the install/upgrade back-fill: drop
2515- # codex's extension registration and its rendered artifacts.
25162561 registry_path = project / ".specify" / "extensions" / ".registry"
2517- registry = json .loads (registry_path .read_text (encoding = "utf-8" ))
2518- registry ["extensions" ]["git" ]["registered_commands" ].pop ("codex" , None )
2519- registry_path .write_text (json .dumps (registry ), encoding = "utf-8" )
2520- agents_skills = project / ".agents" / "skills"
2521- for skill_dir in agents_skills .glob ("speckit-git-*" ):
2522- shutil .rmtree (skill_dir )
2523-
2524- # Precondition: codex is now missing the git extension.
25252562 assert "codex" not in json .loads (registry_path .read_text (encoding = "utf-8" ))[
25262563 "extensions"
25272564 ]["git" ]["registered_commands" ]
2528- assert not (agents_skills / "speckit-git-feature" / "SKILL.md" ).exists ()
25292565
25302566 result = _run_in_project (project , [
25312567 "integration" , "upgrade" , "codex" ,
25322568 "--script" , "sh" ,
25332569 ])
25342570 assert result .exit_code == 0 , result .output
25352571
2536- # Upgrade back-filled the git extension for codex.
25372572 registered = json .loads (registry_path .read_text (encoding = "utf-8" ))[
25382573 "extensions"
25392574 ]["git" ]["registered_commands" ]
2540- assert "codex" in registered , "upgrade should re-register extension commands (#2886)"
2541- assert (agents_skills / "speckit-git-feature" / "SKILL.md" ).exists ()
2575+ assert "codex" not in registered , (
2576+ "upgrade must not back-fill non-active integrations (#2948)"
2577+ )
2578+ assert not (
2579+ project / ".agents" / "skills" / "speckit-git-feature" / "SKILL.md"
2580+ ).exists ()
2581+
2582+ def test_upgrade_active_integration_reregisters_extensions (self , tmp_path ):
2583+ """Upgrading the active integration restores its extension commands.
2584+
2585+ The active integration keeps the re-registration pass on upgrade so
2586+ missing or stale extension command files are recreated (#2948 scopes
2587+ the pass to the active integration; #2886 introduced it).
2588+ """
2589+ project = _init_project (tmp_path , "claude" )
2590+
2591+ result = _run_in_project (project , ["extension" , "add" , "git" ])
2592+ assert result .exit_code == 0 , f"extension add failed: { result .output } "
2593+
2594+ cmd_file = project / ".claude" / "skills" / "speckit-git-feature" / "SKILL.md"
2595+ assert cmd_file .exists (), "precondition: extension command registered"
2596+ cmd_file .unlink ()
2597+
2598+ result = _run_in_project (project , [
2599+ "integration" , "upgrade" , "claude" ,
2600+ "--script" , "sh" ,
2601+ ])
2602+ assert result .exit_code == 0 , result .output
2603+
2604+ assert cmd_file .exists (), (
2605+ "upgrade of the active integration re-registers extension commands"
2606+ )
25422607
25432608 def test_upgrade_non_active_agent_preserves_active_agent_skills (self , tmp_path ):
25442609 """Upgrading a non-active agent must not touch the active agent's skills.
0 commit comments