diff --git a/tests/test_paths.py b/tests/test_paths.py index 7612361..8820ba5 100644 --- a/tests/test_paths.py +++ b/tests/test_paths.py @@ -162,3 +162,51 @@ def test_load_config_rejects_mutated_mcp_checksum(monkeypatch, tmp_path): with pytest.raises(ValueError, match="sha256 mismatch"): load_config() + + +def test_load_config_ignores_unenabled_tool_env_requirements(monkeypatch, tmp_path): + repo_root = tmp_path / "repo" + repo_root.mkdir() + (repo_root / "verified_packages.yaml").write_text( + yaml.safe_dump( + { + "packages": [ + { + "ecosystem": "npm", + "package": "@acme/mcp-linear", + "version": "1.2.3", + "sha256": "a" * 64, + } + ] + } + ), + encoding="utf-8", + ) + config_path = repo_root / "config.yaml" + config_path.write_text( + yaml.safe_dump( + { + "tool_registry": { + "verified_packages_file": "verified_packages.yaml", + "mcp_servers": { + "linear_mcp": { + "package": "@acme/mcp-linear", + "version": "1.2.3", + "sha256": "a" * 64, + "env": {"LINEAR_API_KEY": "${LINEAR_API_KEY}"}, + "task_permissions": {"groomer": ["issues:read"]}, + } + }, + }, + "github_projects": {"proj": {"repos": [{"github_repo": "owner/repo"}]}}, + } + ), + encoding="utf-8", + ) + + monkeypatch.setenv("AGENT_OS_CONFIG", str(config_path)) + monkeypatch.delenv("LINEAR_API_KEY", raising=False) + + cfg = load_config() + + assert cfg["_tool_registry_status"]["status"] == "configured" diff --git a/tests/test_queue.py b/tests/test_queue.py index b1136a0..dc35c59 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -2356,6 +2356,102 @@ def test_write_prompt_omits_git_state_section_when_unavailable(tmp_path, monkeyp assert "Objective Alignment" in text +def test_write_prompt_includes_curated_tools_for_opted_in_repo(tmp_path, monkeypatch): + root, worktree = _write_prompt_env( + tmp_path, + monkeypatch, + git_state="abc1234 feat: add widget", + sprint_payload=None, + ) + monkeypatch.setattr( + "orchestrator.queue.load_config", + lambda: { + "tool_registry": { + "mcp_servers": { + "linear_mcp": { + "title": "Linear MCP", + "package": "@acme/mcp-linear", + "version": "1.2.3", + "sha256": "a" * 64, + "env": {"LINEAR_API_KEY": "${LINEAR_API_KEY}"}, + "task_permissions": {"implementation": ["issues:read"]}, + } + } + }, + "github_projects": { + "proj": { + "repos": [ + {"github_repo": "owner/repo", "enabled_tools": ["linear_mcp"]}, + ] + } + }, + }, + ) + + prompt_file = write_prompt( + "task-ctx-curated-tools", + {"task_type": "implementation", "base_branch": "main", "github_repo": "owner/repo"}, + "Implement the feature.", + "claude", + [], + root, + worktree=worktree, + ) + + text = prompt_file.read_text(encoding="utf-8") + assert "## Curated Tools" in text + assert "linear_mcp [mcp]" in text + assert "package=@acme/mcp-linear@1.2.3" in text + assert "perms=issues:read" in text + + +def test_write_prompt_omits_curated_tools_for_repo_without_override(tmp_path, monkeypatch): + root, worktree = _write_prompt_env( + tmp_path, + monkeypatch, + git_state="abc1234 feat: add widget", + sprint_payload=None, + ) + monkeypatch.setattr( + "orchestrator.queue.load_config", + lambda: { + "tool_registry": { + "mcp_servers": { + "linear_mcp": { + "title": "Linear MCP", + "package": "@acme/mcp-linear", + "version": "1.2.3", + "sha256": "a" * 64, + "env": {"LINEAR_API_KEY": "${LINEAR_API_KEY}"}, + "task_permissions": {"implementation": ["issues:read"]}, + } + } + }, + "github_projects": { + "proj": { + "repos": [ + {"github_repo": "owner/repo"}, + ] + } + }, + }, + ) + + prompt_file = write_prompt( + "task-ctx-default-tools", + {"task_type": "implementation", "base_branch": "main", "github_repo": "owner/repo"}, + "Implement the feature.", + "claude", + [], + root, + worktree=worktree, + ) + + text = prompt_file.read_text(encoding="utf-8") + assert "## Curated Tools" not in text + assert "Curated tool registry" not in text + + @pytest.mark.parametrize("agent_name", ["claude", "codex", "deepseek"]) def test_write_prompt_dispatch_context_is_agent_agnostic(tmp_path, monkeypatch, agent_name): """Dispatch Context injection must be identical across claude, codex, and deepseek.