Files
cleveragents-core/features/steps/project_context_cov3_steps.py
freemo e05a577090
CI / lint (pull_request) Successful in 19s
CI / quality (pull_request) Successful in 32s
CI / typecheck (pull_request) Successful in 55s
CI / security (pull_request) Successful in 1m2s
CI / build (pull_request) Successful in 24s
CI / helm (pull_request) Successful in 23s
CI / unit_tests (pull_request) Failing after 6m27s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 17m25s
CI / integration_tests (pull_request) Successful in 23m0s
CI / coverage (pull_request) Successful in 10m52s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 58m22s
fix(acms): align DEFAULT_SKELETON_RATIO default value with spec
Align all DEFAULT_SKELETON_RATIO / _DEFAULT_SKELETON_RATIO constants
to the spec-required value of 0.15 (docs/specification.md line 35294).

Previously three modules used divergent defaults:
- skeleton_compressor.py: 0.3 (2x the spec value)
- depth_breadth_projection.py: 0.2 (33% above spec)
- project_context.py: 0.2 (33% above spec)

This caused child plans to receive 2-3x more skeleton context than
intended, and produced inconsistent behaviour depending on whether
skeleton compression was invoked via the CLI or the service layer.

Changes:
- Set DEFAULT_SKELETON_RATIO = 0.15 in skeleton_compressor.py
- Set DEFAULT_SKELETON_RATIO = 0.15 in depth_breadth_projection.py
- Set _DEFAULT_SKELETON_RATIO = 0.15 in project_context.py
- Update Behave feature tests to assert the 0.15 default value
- Add new scenario to project_context_cov3.feature asserting
  _DEFAULT_SKELETON_RATIO == 0.15 in _default_acms_config()

ISSUES CLOSED: #2909
2026-04-05 08:22:36 +00:00

453 lines
15 KiB
Python

"""Step definitions for ``project_context_cov3.feature``.
Targets uncovered lines 156, 175, 350, 674-675, 678, 864-869 in
``src/cleveragents/cli/commands/project_context.py``.
Uses the ``pccov3`` prefix on all step text to avoid Behave
AmbiguousStep collisions with existing step files.
"""
from __future__ import annotations
import json as _json
from io import StringIO
from typing import Any
from unittest.mock import MagicMock, patch
from behave import given, then, when # type: ignore[import-untyped]
from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session, sessionmaker
# ------------------------------------------------------------------
# Session wrapper - prevents close() from destroying the shared
# in-memory session; rollback() resets dirty state between calls.
# ------------------------------------------------------------------
class _SafeSession:
"""Thin wrapper: ``close()`` → ``rollback()`` instead of destroy."""
def __init__(self, real: Session) -> None:
object.__setattr__(self, "_real", real)
def close(self) -> None:
real: Session = object.__getattribute__(self, "_real")
real.rollback()
def __getattr__(self, name: str) -> Any:
return getattr(object.__getattribute__(self, "_real"), name)
def __setattr__(self, name: str, value: Any) -> None:
setattr(object.__getattribute__(self, "_real"), name, value)
def _make_session_factory(context: Any) -> tuple[Any, Any]:
from cleveragents.infrastructure.database.models import Base
engine = create_engine(
"sqlite:///:memory:",
echo=False,
connect_args={"check_same_thread": False},
)
Base.metadata.create_all(engine)
real = sessionmaker(
bind=engine, expire_on_commit=False, autoflush=True, autocommit=False
)()
wrapper = _SafeSession(real)
return engine, lambda: wrapper
# ------------------------------------------------------------------
# Mock container + runner
# ------------------------------------------------------------------
def _mock_container(context: Any) -> MagicMock:
from cleveragents.application.services.context_tiers import ContextTierService
mc = MagicMock()
mc.namespaced_project_repo.return_value = context.pccov3_project_repo
mc.session_factory.return_value = context.pccov3_session_factory
if not hasattr(context, "pccov3_tier_service"):
context.pccov3_tier_service = ContextTierService()
mc.context_tier_service.return_value = context.pccov3_tier_service
return mc
def _run(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
"""Execute a CLI function with a mocked DI container."""
import typer
from rich.console import Console as RichConsole
mc = _mock_container(context)
buf = StringIO()
test_console = RichConsole(
file=buf,
no_color=True,
highlight=False,
force_terminal=False,
width=500,
soft_wrap=True,
)
def _test_format_output(data: Any, format_type: Any) -> Any:
import sys as _sys
from io import StringIO as _SIO
from cleveragents.cli.formatting import format_output as _fo
_b = _SIO()
_old = _sys.stdout
_sys.stdout = _b
try:
_r = _fo(data, format_type)
finally:
_sys.stdout = _old
return _r or _b.getvalue().rstrip("\n")
with (
patch(
"cleveragents.application.container.get_container",
return_value=mc,
),
patch(
"cleveragents.cli.commands.project_context.console",
test_console,
),
patch(
"cleveragents.cli.commands.project_context.err_console",
test_console,
),
patch(
"cleveragents.cli.commands.project_context.format_output",
_test_format_output,
),
):
try:
func(*args, **kwargs)
context.pccov3_exit_code = 0
except typer.Exit as exc:
context.pccov3_exit_code = exc.exit_code
except typer.Abort:
context.pccov3_exit_code = 1
context.pccov3_output = buf.getvalue()
# ------------------------------------------------------------------
# Helper: seed raw policy blobs into the DB
# ------------------------------------------------------------------
def _seed_policy_blob(context: Any, ns: str, blob: dict) -> None:
"""Write a raw JSON blob into ns_projects.context_policy_json."""
session = context.pccov3_session_factory()
session.execute(
text(
"UPDATE ns_projects SET context_policy_json = :blob "
"WHERE namespaced_name = :ns"
),
{"blob": _json.dumps(blob), "ns": ns},
)
session.commit()
# ------------------------------------------------------------------
# Background steps
# ------------------------------------------------------------------
@given("a pccov3 in-memory database is initialized")
def step_pccov3_init_db(context: Any) -> None:
from cleveragents.infrastructure.database.repositories import (
NamespacedProjectRepository,
)
engine, sf = _make_session_factory(context)
context.pccov3_engine = engine
context.pccov3_session_factory = sf
context.pccov3_project_repo = NamespacedProjectRepository(session_factory=sf)
context.pccov3_output = ""
context.pccov3_exit_code = 0
context.pccov3_error = None
@given('a project "{name}" exists for pccov3')
def step_pccov3_create_project(context: Any, name: str) -> None:
from cleveragents.domain.models.core.project import (
NamespacedProject,
parse_namespaced_name,
)
parsed = parse_namespaced_name(name)
proj = NamespacedProject(name=parsed.name, namespace=parsed.namespace)
context.pccov3_project_repo.create(proj)
# ------------------------------------------------------------------
# Given steps
# ------------------------------------------------------------------
@given('the pccov3 tier service has a fragment with long content for "{project}"')
def step_pccov3_seed_long_content_fragment(context: Any, project: str) -> None:
"""Seed the tier service with a fragment whose content exceeds 100 chars.
This ensures line 350 (content truncation) is exercised in
``_simulate_context_assembly``.
"""
from cleveragents.application.services.context_tiers import ContextTierService
from cleveragents.domain.models.acms.tiers import ContextTier, TieredFragment
svc = ContextTierService()
svc.store(
TieredFragment(
fragment_id="pccov3-long-frag",
content="X" * 200, # 200 chars, well over the 100-char threshold
tier=ContextTier.HOT,
resource_id="res:pccov3-long",
project_name=project,
token_count=50,
access_count=3,
)
)
context.pccov3_tier_service = svc
@given(
'I pccov3 seed execution environment "{env}" and priority "{priority}" for "{project}"'
)
def step_pccov3_seed_ee_and_eep(
context: Any, env: str, priority: str, project: str
) -> None:
"""Seed a policy blob with both execution_environment and execution_env_priority.
This ensures lines 864-869 are exercised when context_show renders
the rich output with both ee and eep present.
"""
from cleveragents.cli.commands.project_context import _default_acms_config
blob = {
"default_view": {
"include_resources": [],
"exclude_resources": [],
"include_paths": [],
"exclude_paths": [],
"max_file_size": None,
"max_total_size": None,
},
"acms_config": _default_acms_config(),
"execution_environment": env,
"execution_env_priority": priority,
}
_seed_policy_blob(context, project, blob)
@given('I pccov3 seed execution environment "{env}" without priority for "{project}"')
def step_pccov3_seed_ee_only(context: Any, env: str, project: str) -> None:
"""Seed a policy blob with execution_environment only (no priority)."""
from cleveragents.cli.commands.project_context import _default_acms_config
blob = {
"default_view": {
"include_resources": [],
"exclude_resources": [],
"include_paths": [],
"exclude_paths": [],
"max_file_size": None,
"max_total_size": None,
},
"acms_config": _default_acms_config(),
"execution_environment": env,
}
_seed_policy_blob(context, project, blob)
@given(
'I pccov3 seed priority "{priority}" without execution environment for "{project}"'
)
def step_pccov3_seed_eep_only(context: Any, priority: str, project: str) -> None:
"""Seed a policy blob with execution_env_priority only (no ee)."""
from cleveragents.cli.commands.project_context import _default_acms_config
blob = {
"default_view": {
"include_resources": [],
"exclude_resources": [],
"include_paths": [],
"exclude_paths": [],
"max_file_size": None,
"max_total_size": None,
},
"acms_config": _default_acms_config(),
"execution_env_priority": priority,
}
_seed_policy_blob(context, project, blob)
# ------------------------------------------------------------------
# When steps
# ------------------------------------------------------------------
@when('I pccov3 read the policy for "{project}"')
def step_pccov3_read_policy(context: Any, project: str) -> None:
"""Directly call _read_policy to exercise line 156."""
from cleveragents.cli.commands.project_context import _read_policy
context.pccov3_policy = _read_policy(context.pccov3_session_factory, project)
@when('I pccov3 read the ACMS config for "{project}"')
def step_pccov3_read_acms(context: Any, project: str) -> None:
"""Directly call _read_acms_config to exercise line 175."""
from cleveragents.cli.commands.project_context import _read_acms_config
context.pccov3_acms = _read_acms_config(context.pccov3_session_factory, project)
@when('I pccov3 run simulate_context_assembly for "{project}"')
def step_pccov3_simulate_assembly(context: Any, project: str) -> None:
"""Run _simulate_context_assembly to exercise line 350 (content truncation)."""
from cleveragents.cli.commands.project_context import (
_default_acms_config,
_simulate_context_assembly,
)
# Patch _get_context_tier_service to return our test tier service
patcher = patch(
"cleveragents.cli.commands.project_context._get_context_tier_service",
return_value=context.pccov3_tier_service,
)
patcher.start()
context.add_cleanup(patcher.stop)
try:
context.pccov3_assembled = _simulate_context_assembly(
project_name=project,
acms_config=_default_acms_config(),
budget_tokens=10000, # large budget so fragment is included
)
except Exception as exc:
context.pccov3_error = exc
@when('I pccov3 run context set on "{project}" with depth-gradient "{gradient}"')
def step_pccov3_set_depth_gradient(context: Any, project: str, gradient: str) -> None:
"""Run context_set with a depth-gradient to exercise lines 674-678."""
from cleveragents.cli.commands.project_context import context_set
_run(
context,
context_set,
project=project,
view="default",
depth_gradient=[gradient],
)
@when('I pccov3 run context show on "{project}" without view')
def step_pccov3_show_no_view(context: Any, project: str) -> None:
"""Run context_show without a view to exercise lines 864-869."""
from cleveragents.cli.commands.project_context import context_show
_run(context, context_show, project=project)
# ------------------------------------------------------------------
# Then steps
# ------------------------------------------------------------------
@then("the pccov3 policy should be a default ProjectContextPolicy")
def step_pccov3_assert_default_policy(context: Any) -> None:
from cleveragents.domain.models.core.context_policy import ProjectContextPolicy
default = ProjectContextPolicy()
assert context.pccov3_policy.default_view == default.default_view, (
f"Expected default view {default.default_view}, "
f"got {context.pccov3_policy.default_view}"
)
@then("the pccov3 policy strategize_view should be None")
def step_pccov3_assert_strat_none(context: Any) -> None:
assert context.pccov3_policy.strategize_view is None, (
f"Expected None, got {context.pccov3_policy.strategize_view}"
)
@then("the pccov3 policy execute_view should be None")
def step_pccov3_assert_exec_none(context: Any) -> None:
assert context.pccov3_policy.execute_view is None, (
f"Expected None, got {context.pccov3_policy.execute_view}"
)
@then("the pccov3 policy apply_view should be None")
def step_pccov3_assert_apply_none(context: Any) -> None:
assert context.pccov3_policy.apply_view is None, (
f"Expected None, got {context.pccov3_policy.apply_view}"
)
@then("the pccov3 ACMS config should equal the default ACMS config")
def step_pccov3_assert_default_acms(context: Any) -> None:
from cleveragents.cli.commands.project_context import _default_acms_config
expected = _default_acms_config()
assert context.pccov3_acms == expected, (
f"Expected {expected}, got {context.pccov3_acms}"
)
@then('the pccov3 assembled fragments should have truncated content ending with "..."')
def step_pccov3_assert_truncated_content(context: Any) -> None:
assert context.pccov3_error is None, f"Unexpected error: {context.pccov3_error}"
assembled = context.pccov3_assembled
assert len(assembled.fragments) > 0, "No fragments in assembled context"
# The fragment content is stored in the ContextFragment's content field.
# In the simulation, content > 100 chars gets truncated to [:100] + "..."
frag = assembled.fragments[0]
assert frag.content.endswith("..."), (
f"Expected content to end with '...', got: {frag.content!r}"
)
# The truncated content should be 103 chars (100 + "...")
assert len(frag.content) == 103, f"Expected 103 chars, got {len(frag.content)}"
@then("the pccov3 command should have exit code {code:d}")
def step_pccov3_assert_exit_code(context: Any, code: int) -> None:
assert context.pccov3_exit_code == code, (
f"Expected exit code {code}, got {context.pccov3_exit_code}. "
f"Output: {context.pccov3_output}"
)
@then("the pccov3 command should succeed")
def step_pccov3_assert_success(context: Any) -> None:
assert context.pccov3_exit_code == 0, (
f"Expected exit 0, got {context.pccov3_exit_code}. "
f"Output: {context.pccov3_output}"
)
@then('the pccov3 output should contain "{text}"')
def step_pccov3_assert_output_contains(context: Any, text: str) -> None:
assert text in context.pccov3_output, (
f"Expected '{text}' in output, got: {context.pccov3_output!r}"
)
@then("the pccov3 ACMS config skeleton_ratio should be {expected:g}")
def step_pccov3_assert_skeleton_ratio(context: Any, expected: float) -> None:
from cleveragents.cli.commands.project_context import _DEFAULT_SKELETON_RATIO
assert context.pccov3_acms["skeleton_ratio"] == expected, (
f"Expected skeleton_ratio {expected}, got {context.pccov3_acms['skeleton_ratio']}"
)
assert expected == _DEFAULT_SKELETON_RATIO, (
f"_DEFAULT_SKELETON_RATIO is {_DEFAULT_SKELETON_RATIO}, expected {expected}"
)