Files
cleveragents-core/features/steps/context_tier_hydration_steps.py
hamza.khyari 5d0e739e43
CI / push-validation (pull_request) Successful in 23s
CI / helm (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 38s
CI / typecheck (pull_request) Successful in 50s
CI / build (pull_request) Successful in 3m18s
CI / lint (pull_request) Successful in 3m20s
CI / integration_tests (pull_request) Successful in 3m57s
CI / security (pull_request) Successful in 4m4s
CI / e2e_tests (pull_request) Successful in 7m18s
CI / unit_tests (pull_request) Successful in 8m42s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 13m34s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m55s
fix(acms): wire ACMS indexing pipeline into CLI so ContextTierService is populated during plan execution
ContextTierService started empty on every CLI invocation, so the LLM
received zero file context during plan execution (bug #1028).

- Add context_tier_hydrator.py: reads files from linked project resources
  (via git ls-files or os.walk) and stores them as TieredFragment objects
  in the tier service.  Respects max file size (256 KB), total budget
  (10 MB), binary exclusion, and .git/node_modules/__pycache__ skipping.
- Wire hydration into LLMExecuteActor.execute() via lazy import (avoids
  M1 E2E regression from top-level import).
- Inject tier_service, project_repository, resource_registry into
  LLMExecuteActor from the DI container in _get_plan_executor().
- Add tier_service property to ExecutePhaseContextAssembler.
- Suppress exc_info traceback rendering in context warnings to prevent
  false-positive crash detection in M1 E2E tests.
- Add sandbox file-writing support in plan apply (path traversal guards,
  protected directory skipping).
- Add 6 Behave scenarios for context tier hydration.

Closes #1028
2026-04-09 13:00:47 +00:00

130 lines
4.4 KiB
Python

"""Steps for context_tier_hydration.feature."""
from __future__ import annotations
import shutil
import subprocess
import tempfile
from pathlib import Path
from behave import given, then, when
from cleveragents.application.services.context_tier_hydrator import (
hydrate_tiers_from_project,
)
from cleveragents.application.services.context_tiers import ContextTierService
from cleveragents.config.settings import Settings
@given("a temp git repo with 2 files for ctx_hydrate")
def step_git_repo(context: object) -> None:
d = tempfile.mkdtemp(prefix="ctx-hydrate-")
context.add_cleanup(shutil.rmtree, d, True)
subprocess.run(["git", "init", "-q"], cwd=d, check=True)
subprocess.run(["git", "config", "user.name", "T"], cwd=d, check=True)
subprocess.run(["git", "config", "user.email", "t@t"], cwd=d, check=True)
Path(d, "main.py").write_text("print('hello')\n")
Path(d, "utils.py").write_text("def helper(): pass\n")
subprocess.run(["git", "add", "."], cwd=d, check=True)
subprocess.run(
["git", "-c", "commit.gpgsign=false", "commit", "-q", "-m", "init"],
cwd=d,
check=True,
)
context.ctx_h_location = d
context.ctx_h_type = "git-checkout"
@given("a temp dir with 1 text and 1 binary file for ctx_hydrate")
def step_mixed_dir(context: object) -> None:
d = tempfile.mkdtemp(prefix="ctx-hydrate-")
context.add_cleanup(shutil.rmtree, d, True)
Path(d, "readme.md").write_text("# Hello\n")
Path(d, "image.png").write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)
context.ctx_h_location = d
context.ctx_h_type = "fs-directory"
@given("a temp dir with 1 large file for ctx_hydrate")
def step_large_file(context: object) -> None:
d = tempfile.mkdtemp(prefix="ctx-hydrate-")
context.add_cleanup(shutil.rmtree, d, True)
Path(d, "huge.txt").write_text("x" * (300 * 1024))
context.ctx_h_location = d
context.ctx_h_type = "fs-directory"
@given("a temp dir with 1 text file for ctx_hydrate")
def step_one_file(context: object) -> None:
d = tempfile.mkdtemp(prefix="ctx-hydrate-")
context.add_cleanup(shutil.rmtree, d, True)
Path(d, "app.py").write_text("app = True\n")
context.ctx_h_location = d
context.ctx_h_type = "fs-directory"
@given("a ContextTierService instance for ctx_hydrate")
def step_tier_service(context: object) -> None:
svc = ContextTierService(settings=Settings())
context.ctx_h_tier = svc
@when("I hydrate tiers from the resource for ctx_hydrate")
def step_hydrate(context: object) -> None:
tier = context.ctx_h_tier
loc = context.ctx_h_location
rtype = context.ctx_h_type
count = hydrate_tiers_from_project(
tier_service=tier,
project_name="local/test-project",
resource_id="01TEST00000000000000000001",
resource_location=loc,
resource_type=rtype,
)
context.ctx_h_count = count
@when("I hydrate tiers from a missing location for ctx_hydrate")
def step_hydrate_missing(context: object) -> None:
tier = context.ctx_h_tier
count = hydrate_tiers_from_project(
tier_service=tier,
project_name="local/missing",
resource_id="01TEST00000000000000000002",
resource_location="/nonexistent/path",
resource_type="fs-directory",
)
context.ctx_h_count = count
def _count_fragments(tier: ContextTierService) -> int:
"""Count fragments using public API."""
return len(tier.get_scoped_view(["local/test-project"]))
@then("the tier service should have {n:d} fragments for ctx_hydrate")
def step_check_count_plural(context: object, n: int) -> None:
assert _count_fragments(context.ctx_h_tier) == n
@then("the tier service should have {n:d} fragment for ctx_hydrate")
def step_check_count_singular(context: object, n: int) -> None:
assert _count_fragments(context.ctx_h_tier) == n
@then('each fragment project_name should be "{name}" for ctx_hydrate')
def step_check_project(context: object, name: str) -> None:
for frag in context.ctx_h_tier.get_scoped_view(["local/test-project"]):
assert frag.project_name == name
@then("each fragment resource_id should not be empty for ctx_hydrate")
def step_check_resource_id(context: object) -> None:
for frag in context.ctx_h_tier.get_scoped_view(["local/test-project"]):
assert frag.resource_id, f"Empty resource_id on {frag.fragment_id}"
@then("the hydration count should be 0 for ctx_hydrate")
def step_check_zero(context: object) -> None:
assert context.ctx_h_count == 0