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
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
130 lines
4.4 KiB
Python
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
|