diff --git a/.claude/agents/evaluator.md b/.claude/agents/evaluator.md index 04210af3..1ff870cf 100644 --- a/.claude/agents/evaluator.md +++ b/.claude/agents/evaluator.md @@ -5,6 +5,10 @@ model: sonnet # Evaluation requires nuanced judgment for trade-off analysis and # 2026-04-28: high effort — weighted scoring across multiple dimensions # benefits from extra deliberation budget. effort: high +disallowedTools: + - Edit + - Write + - Agent version: 3.1.1 last_updated: 2026-05-27 --- diff --git a/.claude/agents/monitor.md b/.claude/agents/monitor.md index 50334d40..7a529656 100644 --- a/.claude/agents/monitor.md +++ b/.claude/agents/monitor.md @@ -5,6 +5,9 @@ model: sonnet # Balanced: quality validation requires good reasoning # 2026-04-28: high effort — Monitor's adversarial-review quality scales # with effort more than with raw model strength. effort: high +disallowedTools: + - Edit + - Agent version: 2.10.1 last_updated: 2026-05-27 --- diff --git a/.claude/agents/predictor.md b/.claude/agents/predictor.md index 9cfa5dbb..5be3b431 100644 --- a/.claude/agents/predictor.md +++ b/.claude/agents/predictor.md @@ -2,6 +2,10 @@ name: predictor description: Predicts consequences and dependency impact of changes (MAP) model: sonnet # Impact analysis requires complex reasoning - upgraded from haiku +disallowedTools: + - Edit + - Write + - Agent version: 3.3.1 last_updated: 2026-05-27 --- diff --git a/.claude/agents/research-agent.md b/.claude/agents/research-agent.md index 68419fea..b97ecb3d 100644 --- a/.claude/agents/research-agent.md +++ b/.claude/agents/research-agent.md @@ -5,6 +5,9 @@ description: Heavy codebase reading with compressed output. Use PROACTIVELY befo # benefits more from latency and parallelism than from reasoning depth. # Frees Opus/Sonnet budget for the load-bearing decision agents. model: haiku +disallowedTools: + - Edit + - Agent version: 1.1.0 last_updated: 2026-04-28 --- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6a8d25a0..69b26110 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1357,6 +1357,8 @@ If you forked the skill-backed `/map-efficient` workflow, you must manually inte - Specific, actionable feedback - Checks against project coding standards +**Capability Constraints (frontmatter):** `disallowedTools: [Edit, Agent]`. Monitor is a read-only reviewer — it may not edit files or spawn sub-agents. Write is permitted for `.map/` evidence artifacts (e.g., review bundles). Task-decomposer separately enforces `permissionMode: plan`. + ### 4. Predictor **Responsibility:** Analyze change impact across codebase. @@ -1390,6 +1392,8 @@ If you forked the skill-backed `/map-efficient` workflow, you must manually inte **Model Used:** Sonnet (impact analysis requires complex reasoning) +**Capability Constraints (frontmatter):** `disallowedTools: [Edit, Write, Agent]`. Predictor is analysis-only — it reads and reports, never edits or spawns sub-agents. + ### 5. Evaluator **Responsibility:** Score solution quality on multiple dimensions. @@ -1421,6 +1425,8 @@ If you forked the skill-backed `/map-efficient` workflow, you must manually inte **Model Used:** Sonnet (evaluation requires nuanced judgment) +**Capability Constraints (frontmatter):** `disallowedTools: [Edit, Write, Agent]`. Evaluator is scoring-only — it reads and scores, never edits or spawns sub-agents. + ### 6. Reflector **Responsibility:** Extract lessons from successes and failures. @@ -1519,7 +1525,9 @@ If you forked the skill-backed `/map-efficient` workflow, you must manually inte - Returns confidence score for search completeness - Enables Actor to Read() only necessary files -**Model Used:** Sonnet (requires understanding code semantics) +**Model Used:** Haiku (read-mostly; latency + parallelism > reasoning depth) + +**Capability Constraints (frontmatter):** `disallowedTools: [Edit, Agent]`. ResearchAgent is read-only — it may not edit files or spawn sub-agents. Write is permitted for the MAP-planning research artifact append mode (`Research_Findings_v1_0` block). **Usage Context:** Called by Actor when implementing features that integrate with existing code diff --git a/src/mapify_cli/templates/agents/evaluator.md b/src/mapify_cli/templates/agents/evaluator.md index 04210af3..1ff870cf 100644 --- a/src/mapify_cli/templates/agents/evaluator.md +++ b/src/mapify_cli/templates/agents/evaluator.md @@ -5,6 +5,10 @@ model: sonnet # Evaluation requires nuanced judgment for trade-off analysis and # 2026-04-28: high effort — weighted scoring across multiple dimensions # benefits from extra deliberation budget. effort: high +disallowedTools: + - Edit + - Write + - Agent version: 3.1.1 last_updated: 2026-05-27 --- diff --git a/src/mapify_cli/templates/agents/monitor.md b/src/mapify_cli/templates/agents/monitor.md index 50334d40..7a529656 100644 --- a/src/mapify_cli/templates/agents/monitor.md +++ b/src/mapify_cli/templates/agents/monitor.md @@ -5,6 +5,9 @@ model: sonnet # Balanced: quality validation requires good reasoning # 2026-04-28: high effort — Monitor's adversarial-review quality scales # with effort more than with raw model strength. effort: high +disallowedTools: + - Edit + - Agent version: 2.10.1 last_updated: 2026-05-27 --- diff --git a/src/mapify_cli/templates/agents/predictor.md b/src/mapify_cli/templates/agents/predictor.md index 9cfa5dbb..5be3b431 100644 --- a/src/mapify_cli/templates/agents/predictor.md +++ b/src/mapify_cli/templates/agents/predictor.md @@ -2,6 +2,10 @@ name: predictor description: Predicts consequences and dependency impact of changes (MAP) model: sonnet # Impact analysis requires complex reasoning - upgraded from haiku +disallowedTools: + - Edit + - Write + - Agent version: 3.3.1 last_updated: 2026-05-27 --- diff --git a/src/mapify_cli/templates/agents/research-agent.md b/src/mapify_cli/templates/agents/research-agent.md index 68419fea..b97ecb3d 100644 --- a/src/mapify_cli/templates/agents/research-agent.md +++ b/src/mapify_cli/templates/agents/research-agent.md @@ -5,6 +5,9 @@ description: Heavy codebase reading with compressed output. Use PROACTIVELY befo # benefits more from latency and parallelism than from reasoning depth. # Frees Opus/Sonnet budget for the load-bearing decision agents. model: haiku +disallowedTools: + - Edit + - Agent version: 1.1.0 last_updated: 2026-04-28 --- diff --git a/src/mapify_cli/templates_src/agents/evaluator.md.jinja b/src/mapify_cli/templates_src/agents/evaluator.md.jinja index 04210af3..1ff870cf 100644 --- a/src/mapify_cli/templates_src/agents/evaluator.md.jinja +++ b/src/mapify_cli/templates_src/agents/evaluator.md.jinja @@ -5,6 +5,10 @@ model: sonnet # Evaluation requires nuanced judgment for trade-off analysis and # 2026-04-28: high effort — weighted scoring across multiple dimensions # benefits from extra deliberation budget. effort: high +disallowedTools: + - Edit + - Write + - Agent version: 3.1.1 last_updated: 2026-05-27 --- diff --git a/src/mapify_cli/templates_src/agents/monitor.md.jinja b/src/mapify_cli/templates_src/agents/monitor.md.jinja index 50334d40..7a529656 100644 --- a/src/mapify_cli/templates_src/agents/monitor.md.jinja +++ b/src/mapify_cli/templates_src/agents/monitor.md.jinja @@ -5,6 +5,9 @@ model: sonnet # Balanced: quality validation requires good reasoning # 2026-04-28: high effort — Monitor's adversarial-review quality scales # with effort more than with raw model strength. effort: high +disallowedTools: + - Edit + - Agent version: 2.10.1 last_updated: 2026-05-27 --- diff --git a/src/mapify_cli/templates_src/agents/predictor.md.jinja b/src/mapify_cli/templates_src/agents/predictor.md.jinja index 9cfa5dbb..5be3b431 100644 --- a/src/mapify_cli/templates_src/agents/predictor.md.jinja +++ b/src/mapify_cli/templates_src/agents/predictor.md.jinja @@ -2,6 +2,10 @@ name: predictor description: Predicts consequences and dependency impact of changes (MAP) model: sonnet # Impact analysis requires complex reasoning - upgraded from haiku +disallowedTools: + - Edit + - Write + - Agent version: 3.3.1 last_updated: 2026-05-27 --- diff --git a/src/mapify_cli/templates_src/agents/research-agent.md.jinja b/src/mapify_cli/templates_src/agents/research-agent.md.jinja index 68419fea..b97ecb3d 100644 --- a/src/mapify_cli/templates_src/agents/research-agent.md.jinja +++ b/src/mapify_cli/templates_src/agents/research-agent.md.jinja @@ -5,6 +5,9 @@ description: Heavy codebase reading with compressed output. Use PROACTIVELY befo # benefits more from latency and parallelism than from reasoning depth. # Frees Opus/Sonnet budget for the load-bearing decision agents. model: haiku +disallowedTools: + - Edit + - Agent version: 1.1.0 last_updated: 2026-04-28 --- diff --git a/tests/test_agent_frontmatter.py b/tests/test_agent_frontmatter.py index 638e58ce..c8c86160 100644 --- a/tests/test_agent_frontmatter.py +++ b/tests/test_agent_frontmatter.py @@ -210,3 +210,129 @@ def test_frontmatter_extraction_logic(self): """ match = re.match(r"^---\n(.*?)\n---", malformed, re.DOTALL) assert match is None + + +class TestAgentCapabilityHardening: + """Regression guard for issue #378: disallowedTools frontmatter on non-writer agents. + + Agents that never legitimately edit code must declare disallowedTools at the + harness level, not rely on prompt-text alone. This test pins those contracts + so they cannot be silently removed. + """ + + _REPO_ROOT = Path(__file__).parent.parent + _AGENTS_DIR = _REPO_ROOT / ".claude" / "agents" + + def _load_frontmatter(self, agent_name: str) -> dict: + path = self._AGENTS_DIR / f"{agent_name}.md" + assert path.exists(), f"Agent file not found: {path}" + content = path.read_text(encoding="utf-8") + match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) + assert match, f"No frontmatter in {path}" + return yaml.safe_load(match.group(1)) + + def test_monitor_has_edit_in_disallowed_tools(self): + fm = self._load_frontmatter("monitor") + disallowed = fm.get("disallowedTools", []) + assert "Edit" in disallowed, ( + "monitor must deny Edit — it is a READ-ONLY reviewer" + ) + + def test_monitor_has_agent_in_disallowed_tools(self): + fm = self._load_frontmatter("monitor") + disallowed = fm.get("disallowedTools", []) + assert "Agent" in disallowed, ( + "monitor must deny Agent — it must not spawn sub-agents" + ) + + def test_monitor_write_not_denied(self): + """monitor IS allowed to Write — it writes .map/ evidence artifacts.""" + fm = self._load_frontmatter("monitor") + disallowed = fm.get("disallowedTools", []) + assert "Write" not in disallowed, ( + "monitor needs Write for .map/ evidence artifacts — do not deny it" + ) + + def test_research_agent_has_edit_in_disallowed_tools(self): + fm = self._load_frontmatter("research-agent") + disallowed = fm.get("disallowedTools", []) + assert "Edit" in disallowed, ( + "research-agent must deny Edit — it is a read-only context scanner" + ) + + def test_research_agent_has_agent_in_disallowed_tools(self): + fm = self._load_frontmatter("research-agent") + disallowed = fm.get("disallowedTools", []) + assert "Agent" in disallowed, ( + "research-agent must deny Agent — it must not spawn sub-agents" + ) + + def test_research_agent_write_not_denied(self): + """research-agent IS allowed to Write — MAP-planning integration appends to research artifact.""" + fm = self._load_frontmatter("research-agent") + disallowed = fm.get("disallowedTools", []) + assert "Write" not in disallowed, ( + "research-agent needs Write for MAP-planning research artifact append" + ) + + def test_predictor_has_edit_in_disallowed_tools(self): + fm = self._load_frontmatter("predictor") + disallowed = fm.get("disallowedTools", []) + assert "Edit" in disallowed, ( + "predictor must deny Edit — it is an analysis-only agent" + ) + + def test_predictor_has_write_in_disallowed_tools(self): + fm = self._load_frontmatter("predictor") + disallowed = fm.get("disallowedTools", []) + assert "Write" in disallowed, ( + "predictor must deny Write — it is an analysis-only agent" + ) + + def test_predictor_has_agent_in_disallowed_tools(self): + fm = self._load_frontmatter("predictor") + disallowed = fm.get("disallowedTools", []) + assert "Agent" in disallowed, ( + "predictor must deny Agent — it must not spawn sub-agents" + ) + + def test_evaluator_has_edit_in_disallowed_tools(self): + fm = self._load_frontmatter("evaluator") + disallowed = fm.get("disallowedTools", []) + assert "Edit" in disallowed, ( + "evaluator must deny Edit — it is a scoring-only agent" + ) + + def test_evaluator_has_write_in_disallowed_tools(self): + fm = self._load_frontmatter("evaluator") + disallowed = fm.get("disallowedTools", []) + assert "Write" in disallowed, ( + "evaluator must deny Write — it is a scoring-only agent" + ) + + def test_evaluator_has_agent_in_disallowed_tools(self): + fm = self._load_frontmatter("evaluator") + disallowed = fm.get("disallowedTools", []) + assert "Agent" in disallowed, ( + "evaluator must deny Agent — it must not spawn sub-agents" + ) + + def test_task_decomposer_has_permission_mode_plan(self): + fm = self._load_frontmatter("task-decomposer") + assert fm.get("permissionMode") == "plan", ( + "task-decomposer must keep permissionMode: plan" + ) + + def test_actor_has_no_disallowed_tools(self): + """actor is a legitimate writer and must NOT be capability-restricted.""" + fm = self._load_frontmatter("actor") + assert "disallowedTools" not in fm, ( + "actor is a legitimate writer — do not add disallowedTools" + ) + + def test_final_verifier_has_no_disallowed_tools(self): + """final-verifier is a legitimate writer and must NOT be capability-restricted.""" + fm = self._load_frontmatter("final-verifier") + assert "disallowedTools" not in fm, ( + "final-verifier is a legitimate writer — do not add disallowedTools" + )