|
2 | 2 |
|
3 | 3 | import json |
4 | 4 | import os |
| 5 | +import shutil |
5 | 6 |
|
6 | 7 | from typer.testing import CliRunner |
7 | 8 |
|
@@ -48,6 +49,34 @@ def _write_invalid_manifest(project, key): |
48 | 49 | return manifest |
49 | 50 |
|
50 | 51 |
|
| 52 | +def _remove_agent_context_extension(project): |
| 53 | + ext_dir = project / ".specify" / "extensions" / "agent-context" |
| 54 | + if ext_dir.exists(): |
| 55 | + shutil.rmtree(ext_dir) |
| 56 | + |
| 57 | + registry = project / ".specify" / "extensions" / ".registry" |
| 58 | + if registry.exists(): |
| 59 | + data = json.loads(registry.read_text(encoding="utf-8")) |
| 60 | + data.get("extensions", {}).pop("agent-context", None) |
| 61 | + registry.write_text(json.dumps(data), encoding="utf-8") |
| 62 | + |
| 63 | + |
| 64 | +def _assert_agent_context_installed(project, context_file): |
| 65 | + ext_dir = project / ".specify" / "extensions" / "agent-context" |
| 66 | + assert (ext_dir / "extension.yml").is_file() |
| 67 | + assert (ext_dir / "commands" / "speckit.agent-context.update.md").is_file() |
| 68 | + assert (ext_dir / "scripts" / "bash" / "update-agent-context.sh").is_file() |
| 69 | + |
| 70 | + registry = project / ".specify" / "extensions" / ".registry" |
| 71 | + data = json.loads(registry.read_text(encoding="utf-8")) |
| 72 | + assert "agent-context" in data["extensions"] |
| 73 | + |
| 74 | + from specify_cli import _load_agent_context_config |
| 75 | + |
| 76 | + cfg = _load_agent_context_config(project) |
| 77 | + assert cfg["context_file"] == context_file |
| 78 | + |
| 79 | + |
51 | 80 | def _integration_list_row_cells(output: str, key: str) -> list[str]: |
52 | 81 | plain = strip_ansi(output) |
53 | 82 | row = next(line for line in plain.splitlines() if line.startswith(f"│ {key}")) |
@@ -191,6 +220,21 @@ def test_install_already_installed_non_default_guides_use(self, tmp_path): |
191 | 220 | assert "specify integration upgrade codex" in normalized |
192 | 221 | assert "specify integration uninstall codex" not in normalized |
193 | 222 |
|
| 223 | + def test_install_backfills_agent_context_extension_when_missing(self, tmp_path): |
| 224 | + project = _init_project(tmp_path, "copilot") |
| 225 | + _remove_agent_context_extension(project) |
| 226 | + (project / ".specify" / "integration.json").unlink() |
| 227 | + (project / ".specify" / "integrations" / "copilot.manifest.json").unlink() |
| 228 | + shutil.rmtree(project / ".github") |
| 229 | + |
| 230 | + result = _run_in_project(project, [ |
| 231 | + "integration", "install", "copilot", |
| 232 | + "--script", "sh", |
| 233 | + ]) |
| 234 | + |
| 235 | + assert result.exit_code == 0, result.output |
| 236 | + _assert_agent_context_installed(project, ".github/copilot-instructions.md") |
| 237 | + |
194 | 238 | def test_install_different_when_one_exists(self, tmp_path): |
195 | 239 | project = _init_project(tmp_path, "copilot") |
196 | 240 | old_cwd = os.getcwd() |
@@ -1155,6 +1199,18 @@ def test_switch_from_nothing(self, tmp_path): |
1155 | 1199 | data = json.loads((project / ".specify" / "integration.json").read_text(encoding="utf-8")) |
1156 | 1200 | assert data["integration"] == "claude" |
1157 | 1201 |
|
| 1202 | + def test_switch_backfills_agent_context_extension_when_missing(self, tmp_path): |
| 1203 | + project = _init_project(tmp_path, "claude") |
| 1204 | + _remove_agent_context_extension(project) |
| 1205 | + |
| 1206 | + result = _run_in_project(project, [ |
| 1207 | + "integration", "switch", "copilot", |
| 1208 | + "--script", "sh", |
| 1209 | + ]) |
| 1210 | + |
| 1211 | + assert result.exit_code == 0, result.output |
| 1212 | + _assert_agent_context_installed(project, ".github/copilot-instructions.md") |
| 1213 | + |
1158 | 1214 | def test_failed_switch_keeps_fallback_metadata_consistent(self, tmp_path): |
1159 | 1215 | project = _init_project(tmp_path, "claude") |
1160 | 1216 | old_cwd = os.getcwd() |
@@ -1308,6 +1364,19 @@ def test_upgrade_default_refreshes_shared_script_refs_for_option_separator_chang |
1308 | 1364 | assert "/speckit.specify" not in managed_content |
1309 | 1365 | assert customized_script.read_text(encoding="utf-8") == customized_before |
1310 | 1366 |
|
| 1367 | + def test_upgrade_backfills_agent_context_extension_when_missing(self, tmp_path): |
| 1368 | + project = _init_project(tmp_path, "copilot") |
| 1369 | + _remove_agent_context_extension(project) |
| 1370 | + |
| 1371 | + result = _run_in_project(project, [ |
| 1372 | + "integration", "upgrade", "copilot", |
| 1373 | + "--script", "sh", |
| 1374 | + "--force", |
| 1375 | + ]) |
| 1376 | + |
| 1377 | + assert result.exit_code == 0, result.output |
| 1378 | + _assert_agent_context_installed(project, ".github/copilot-instructions.md") |
| 1379 | + |
1311 | 1380 | def test_upgrade_non_default_keeps_default_template_invocations(self, tmp_path): |
1312 | 1381 | project = _init_project(tmp_path, "gemini") |
1313 | 1382 | template = project / ".specify" / "templates" / "plan-template.md" |
|
0 commit comments