"""Context-policy verification commands for M5 Robot E2E tests.""" from __future__ import annotations from types import SimpleNamespace from typing import Any, cast from unittest.mock import MagicMock from helper_m5_e2e_support import _create_project, _fail, _setup_db from helpers_common import reset_global_state from cleveragents.application.services.context_phase_analysis import ( analyze_phase_summaries, ) from cleveragents.application.services.context_tiers import ContextTierService from cleveragents.application.services.execute_phase_context_assembler import ( ACMSExecutePhaseContextAssembler, ) from cleveragents.application.services.llm_actors import LLMExecuteActor from cleveragents.application.services.plan_executor import StrategyDecision from cleveragents.cli.commands.project_context import _read_policy, _write_policy from cleveragents.config.settings import Settings from cleveragents.domain.models.acms.tiers import ContextTier, TieredFragment from cleveragents.domain.models.core.context_policy import ( VALID_PHASES, ContextView, ProjectContextPolicy, ) from cleveragents.domain.models.core.project import ContextConfig, NamespacedProject def context_tier_config() -> None: """Verify ContextConfig with hot/warm/cold tier management.""" reset_global_state() default_cfg = ContextConfig() if default_cfg.hot_max_tokens is not None: _fail(f"default hot_max_tokens should be None: {default_cfg.hot_max_tokens}") if default_cfg.warm_max_decisions is not None: _fail( f"default warm_max_decisions should be None: " f"{default_cfg.warm_max_decisions}" ) if default_cfg.cold_max_decisions is not None: _fail( f"default cold_max_decisions should be None: " f"{default_cfg.cold_max_decisions}" ) tier_cfg = ContextConfig( hot_max_tokens=128_000, warm_max_decisions=50, cold_max_decisions=200, max_file_size=2_000_000, max_total_size=100_000_000, ) if tier_cfg.hot_max_tokens != 128_000: _fail(f"hot_max_tokens mismatch: {tier_cfg.hot_max_tokens}") if tier_cfg.warm_max_decisions != 50: _fail(f"warm_max_decisions mismatch: {tier_cfg.warm_max_decisions}") if tier_cfg.cold_max_decisions != 200: _fail(f"cold_max_decisions mismatch: {tier_cfg.cold_max_decisions}") if tier_cfg.max_file_size != 2_000_000: _fail(f"max_file_size mismatch: {tier_cfg.max_file_size}") if tier_cfg.max_total_size != 100_000_000: _fail(f"max_total_size mismatch: {tier_cfg.max_total_size}") proj = NamespacedProject( name="tier-test", namespace="local", context_config=tier_cfg, ) if proj.context_config.hot_max_tokens != 128_000: _fail("project did not preserve hot_max_tokens") if proj.context_config.warm_max_decisions != 50: _fail("project did not preserve warm_max_decisions") if proj.context_config.cold_max_decisions != 200: _fail("project did not preserve cold_max_decisions") print("m5-context-tier-config-ok") def context_policy_set_show() -> None: """Set a context policy via SQL helpers and read it back.""" reset_global_state() proj_repo, _link_repo, _svc, sf = _setup_db() _create_project(proj_repo, "local/policy-test") policy = ProjectContextPolicy( default_view=ContextView( include_resources=["local/repo-*"], exclude_paths=["*.log", "*.tmp"], max_file_size=1_048_576, max_total_size=52_428_800, ), strategize_view=ContextView( include_resources=["local/core-*"], include_paths=["src/**/*.py"], max_file_size=500_000, ), ) _write_policy(sf, "local/policy-test", policy) loaded = _read_policy(sf, "local/policy-test") if loaded.default_view.include_resources != ["local/repo-*"]: _fail( f"default include_resources mismatch: " f"{loaded.default_view.include_resources}" ) if loaded.default_view.exclude_paths != ["*.log", "*.tmp"]: _fail(f"default exclude_paths mismatch: {loaded.default_view.exclude_paths}") if loaded.default_view.max_file_size != 1_048_576: _fail(f"default max_file_size mismatch: {loaded.default_view.max_file_size}") if loaded.strategize_view is None: _fail("strategize_view is None after round-trip") if loaded.strategize_view.include_resources != ["local/core-*"]: _fail( f"strategize include_resources mismatch: " f"{loaded.strategize_view.include_resources}" ) if loaded.strategize_view.include_paths != ["src/**/*.py"]: _fail( f"strategize include_paths mismatch: {loaded.strategize_view.include_paths}" ) if loaded.execute_view is not None: _fail("execute_view should be None") if loaded.apply_view is not None: _fail("apply_view should be None") print("m5-context-policy-set-show-ok") def acms_phase_inheritance() -> None: """Verify ACMS view inheritance: default -> strategize -> execute -> apply.""" reset_global_state() policy = ProjectContextPolicy( default_view=ContextView( include_resources=["*"], max_total_size=50_000_000, ), strategize_view=ContextView( include_resources=["core-*"], max_file_size=1_000_000, ), ) rv_default = policy.resolve_view("default") if rv_default.include_resources != ["*"]: _fail(f"default resolution wrong: {rv_default.include_resources}") if rv_default.max_total_size != 50_000_000: _fail(f"default max_total_size wrong: {rv_default.max_total_size}") rv_strat = policy.resolve_view("strategize") if rv_strat.include_resources != ["core-*"]: _fail(f"strategize resolution wrong: {rv_strat.include_resources}") if rv_strat.max_file_size != 1_000_000: _fail(f"strategize max_file_size wrong: {rv_strat.max_file_size}") rv_exec = policy.resolve_view("execute") if rv_exec.include_resources != ["core-*"]: _fail(f"execute should inherit strategize: {rv_exec.include_resources}") rv_apply = policy.resolve_view("apply") if rv_apply.include_resources != ["core-*"]: _fail(f"apply should inherit via chain: {rv_apply.include_resources}") policy2 = ProjectContextPolicy( default_view=ContextView(include_resources=["*"]), strategize_view=ContextView(include_resources=["core-*"]), execute_view=ContextView( include_resources=["exec-*"], exclude_paths=["tests/**"], ), ) rv_apply2 = policy2.resolve_view("apply") if rv_apply2.include_resources != ["exec-*"]: _fail(f"apply should inherit from execute: {rv_apply2.include_resources}") if rv_apply2.exclude_paths != ["tests/**"]: _fail(f"apply exclude_paths wrong: {rv_apply2.exclude_paths}") print("m5-acms-phase-inheritance-ok") def acms_scoped_context() -> None: """Verify ACMS produces scoped context output per phase.""" reset_global_state() policy = ProjectContextPolicy( default_view=ContextView( include_resources=["*"], max_file_size=2_000_000, max_total_size=100_000_000, ), strategize_view=ContextView( include_resources=["local/large-repo"], include_paths=["src/**/*.py", "docs/**/*.md"], exclude_paths=["**/test_*.py"], max_file_size=1_000_000, ), execute_view=ContextView( include_resources=["local/large-repo"], include_paths=["src/**/*.py"], exclude_paths=["**/test_*.py", "**/conftest.py"], max_file_size=500_000, max_total_size=1_200_000, ), apply_view=ContextView( include_resources=["local/large-repo"], include_paths=["src/**/*.py"], exclude_paths=["docs/**", "**/test_*.py", "**/conftest.py"], max_file_size=250_000, max_total_size=700_000, ), ) for phase in ("default", "strategize", "execute", "apply"): rv = policy.resolve_view(phase) expected_attr = getattr(policy, f"{phase}_view") if expected_attr is None: _fail(f"phase {phase} view should not be None") if rv.include_resources != expected_attr.include_resources: _fail( f"{phase} include_resources mismatch: " f"{rv.include_resources} != {expected_attr.include_resources}" ) strat_view = policy.resolve_view("strategize") exec_view = policy.resolve_view("execute") apply_view = policy.resolve_view("apply") if strat_view.max_file_size is None: _fail("strategize max_file_size is None") if exec_view.max_file_size is None: _fail("execute max_file_size is None") if apply_view.max_file_size is None: _fail("apply max_file_size is None") if not ( strat_view.max_file_size > exec_view.max_file_size > apply_view.max_file_size ): _fail( f"size limits should narrow: " f"{strat_view.max_file_size} > {exec_view.max_file_size} > " f"{apply_view.max_file_size}" ) sample_fragments = [ TieredFragment( fragment_id="f-core", content="x" * 900, tier=ContextTier.HOT, resource_id="local/large-repo", project_name="local/scoped-test", token_count=900, metadata={"path": "src/core.py", "byte_size": 900_000}, ), TieredFragment( fragment_id="f-engine", content="x" * 500, tier=ContextTier.HOT, resource_id="local/large-repo", project_name="local/scoped-test", token_count=500, metadata={"path": "src/engine.py", "byte_size": 450_000}, ), TieredFragment( fragment_id="f-test", content="x" * 120, tier=ContextTier.WARM, resource_id="local/large-repo", project_name="local/scoped-test", token_count=120, metadata={"path": "src/test_core.py", "byte_size": 100_000}, ), TieredFragment( fragment_id="f-doc", content="x" * 180, tier=ContextTier.WARM, resource_id="local/large-repo", project_name="local/scoped-test", token_count=180, metadata={"path": "docs/design.md", "byte_size": 200_000}, ), TieredFragment( fragment_id="f-conftest", content="x" * 90, tier=ContextTier.COLD, resource_id="local/large-repo", project_name="local/scoped-test", token_count=90, metadata={"path": "src/conftest.py", "byte_size": 80_000}, ), TieredFragment( fragment_id="f-huge", content="x" * 1500, tier=ContextTier.COLD, resource_id="local/large-repo", project_name="local/scoped-test", token_count=1500, metadata={"path": "src/huge.py", "byte_size": 1_500_000}, ), ] analysis = analyze_phase_summaries(policy, sample_fragments, budget_tokens=2_000) for phase in ("strategize", "execute", "apply"): phase_row = analysis["phases"][phase] if not phase_row["summary"]: _fail(f"{phase} summary should be non-empty") if "resource_count" not in phase_row or "total_bytes" not in phase_row: _fail(f"{phase} summary missing resource/size metrics") narrowing = analysis["narrowing"] if not narrowing["overall"]: _fail(f"narrowing should hold across phases: {narrowing}") proj_repo, _link_repo, _svc, sf = _setup_db() _create_project(proj_repo, "local/scoped-test") _write_policy(sf, "local/scoped-test", policy) loaded = _read_policy(sf, "local/scoped-test") for phase in ("default", "strategize", "execute", "apply"): rv_loaded = loaded.resolve_view(phase) rv_orig = policy.resolve_view(phase) if rv_loaded.include_resources != rv_orig.include_resources: _fail(f"{phase} round-trip include_resources mismatch") if rv_loaded.max_file_size != rv_orig.max_file_size: _fail(f"{phase} round-trip max_file_size mismatch") print("m5-acms-scoped-context-ok") def context_policy_clear() -> None: """Verify clearing a phase view causes inheritance fallback.""" reset_global_state() policy = ProjectContextPolicy( default_view=ContextView(include_resources=["all-*"]), strategize_view=ContextView(include_resources=["strat-*"]), execute_view=ContextView(include_resources=["exec-*"]), apply_view=ContextView(include_resources=["apply-*"]), ) rv = policy.resolve_view("execute") if rv.include_resources != ["exec-*"]: _fail(f"before clear, execute wrong: {rv.include_resources}") cleared = policy.model_copy(update={"execute_view": None}) rv_after = cleared.resolve_view("execute") if rv_after.include_resources != ["strat-*"]: _fail( f"after clear, execute should inherit strategize: " f"{rv_after.include_resources}" ) rv_apply = cleared.resolve_view("apply") if rv_apply.include_resources != ["apply-*"]: _fail(f"apply should still be its own: {rv_apply.include_resources}") cleared2 = cleared.model_copy(update={"apply_view": None}) rv_apply2 = cleared2.resolve_view("apply") if rv_apply2.include_resources != ["strat-*"]: _fail( f"after clearing apply+execute, apply should inherit strategize: " f"{rv_apply2.include_resources}" ) proj_repo, _link_repo, _svc, sf = _setup_db() _create_project(proj_repo, "local/clear-test") _write_policy(sf, "local/clear-test", cleared2) loaded = _read_policy(sf, "local/clear-test") if loaded.execute_view is not None: _fail("execute_view should be None after round-trip") if loaded.apply_view is not None: _fail("apply_view should be None after round-trip") rv_loaded = loaded.resolve_view("execute") if rv_loaded.include_resources != ["strat-*"]: _fail(f"round-trip execute fallback wrong: {rv_loaded.include_resources}") print("m5-context-policy-clear-ok") def context_view_validation() -> None: """Verify ContextView validation rules.""" reset_global_state() cv = ContextView(max_file_size=1, max_total_size=1) if cv.max_file_size != 1: _fail(f"max_file_size should be 1: {cv.max_file_size}") try: ContextView(max_file_size=0) _fail("max_file_size=0 should be rejected") except ValueError: pass try: ContextView(max_total_size=-1) _fail("max_total_size=-1 should be rejected") except ValueError: pass cv_none = ContextView(max_file_size=None, max_total_size=None) if cv_none.max_file_size is not None: _fail("max_file_size should be None") expected_phases = {"default", "strategize", "execute", "apply"} if expected_phases != VALID_PHASES: _fail(f"VALID_PHASES mismatch: {VALID_PHASES} != {expected_phases}") policy = ProjectContextPolicy() try: policy.resolve_view("invalid-phase") _fail("resolve_view should reject invalid phase") except ValueError: pass print("m5-context-view-validation-ok") def llm_execute_acms_context() -> None: """Verify execute-phase LLM prompt uses ACMS assembled execute context.""" reset_global_state() proj_repo, _link_repo, _svc, sf = _setup_db() _create_project(proj_repo, "local/exec-ctx") policy = ProjectContextPolicy( default_view=ContextView(include_paths=["**/*"]), strategize_view=ContextView(include_paths=["docs/**"]), execute_view=ContextView( include_paths=["src/**"], max_file_size=100_000, max_total_size=100_000, ), ) _write_policy(sf, "local/exec-ctx", policy) tier_service = ContextTierService(settings=Settings()) tier_service.store( TieredFragment( fragment_id="frag-docs", content="documentation fragment", tier=ContextTier.HOT, resource_id="local/large-repo", project_name="local/exec-ctx", token_count=20, metadata={"path": "docs/readme.md", "relevance_score": "0.6"}, ) ) tier_service.store( TieredFragment( fragment_id="frag-src", content="def run():\n return 'ok'", tier=ContextTier.HOT, resource_id="local/large-repo", project_name="local/exec-ctx", token_count=16, metadata={"path": "src/main.py", "relevance_score": "0.9"}, ) ) assembler = ACMSExecutePhaseContextAssembler( context_tier_service=tier_service, project_repository=proj_repo, hot_max_tokens=1024, ) mock_llm = MagicMock() mock_llm.invoke.return_value = SimpleNamespace( content="FILE: src/out.py\n```python\nprint('ok')\n```\n" ) registry = cast(Any, SimpleNamespace(create_llm=MagicMock(return_value=mock_llm))) plan = SimpleNamespace( identity=SimpleNamespace(plan_id="01ARZ3NDEKTSV4RRFFQ69G5FAV"), action_name="local/action", project_links=[SimpleNamespace(project_name="local/exec-ctx")], ) action = SimpleNamespace(execution_actor="openai/gpt-4") lifecycle = SimpleNamespace( get_plan=MagicMock(return_value=plan), get_action=MagicMock(return_value=action), resolve_actor_provider_model=MagicMock(return_value=None), resolve_actor_options=MagicMock(return_value=None), ) actor = LLMExecuteActor( provider_registry=registry, lifecycle_service=lifecycle, context_assembler=assembler, ) actor.execute( plan_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", decisions=[ StrategyDecision( decision_id="d1", step_text="Implement feature", sequence=0, parent_id=None, ) ], stream_callback=None, ) call_args = mock_llm.invoke.call_args if call_args is None: _fail("LLM invoke was not called") prompt = call_args.args[0][0].content if "ACMS Execute-Phase Context" not in prompt: _fail("prompt missing ACMS execute-phase context section") if "src/main.py" not in prompt: _fail("prompt missing execute-view source fragment") if "docs/readme.md" in prompt: _fail("prompt should exclude strategize-only docs fragment in execute view") print("m5-llm-execute-acms-context-ok")