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
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
453 lines
15 KiB
Python
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}"
|
|
)
|