Files
cleveragents-core/features/steps/decomposition_test_helpers.py
T
freemo 4232907ab9
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 18s
CI / build (pull_request) Successful in 23s
CI / typecheck (pull_request) Successful in 34s
CI / security (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 3m17s
CI / docker (pull_request) Successful in 40s
CI / integration_tests (pull_request) Successful in 4m3s
CI / coverage (pull_request) Successful in 4m9s
CI / lint (push) Successful in 12s
CI / quality (push) Successful in 16s
CI / build (push) Successful in 15s
CI / security (push) Successful in 29s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m23s
CI / integration_tests (push) Successful in 2m48s
CI / docker (push) Successful in 38s
CI / coverage (push) Successful in 4m16s
CI / benchmark-publish (push) Successful in 16m55s
CI / benchmark-regression (pull_request) Successful in 30m14s
feat(plan): add large-project decomposition and dependency closure
Add hierarchical decomposition with 4+ levels and bounded context per
subplan. Implement decomposition heuristics (max_files_per_subplan,
max_tokens_per_subplan, language/dir clustering). Add dependency closure
computation for large graphs and DAG execution ordering. Add bounded
dependency closure with cutoff thresholds and memoization for 10K+
files. Record decomposition decisions in DecisionService
(strategy_choice + subplan_spawn entries).

New modules:
- decomposition_models.py: DecompositionConfig, DecompositionNode,
  DecompositionResult, DependencyEdge, DependencyGraph
- decomposition_clustering.py: ClusteringStrategy with directory,
  language, and size clustering plus deterministic sort
- decomposition_graph.py: DependencyClosureComputer with bounded
  closure, topological sort, cycle detection, and memoization
- decomposition_service.py: DecompositionService orchestrating
  hierarchy building and decision recording

Settings: planner_max_depth, planner_max_files_per_subplan,
planner_max_tokens_per_subplan, planner_min_files_per_subplan

Closes #205
2026-03-03 21:44:20 +00:00

113 lines
3.7 KiB
Python

"""Shared test helpers for decomposition BDD step files.
Provides factory functions for creating temporary files, dependency
graphs, and other test fixtures used by both the primary and coverage
step implementations.
"""
from __future__ import annotations
import itertools
import os
from cleveragents.application.services.decomposition_models import (
DependencyEdge,
DependencyGraph,
DependencyType,
)
def make_files(tmpdir: str, count: int, levels: int = 1) -> list[str]:
"""Create *count* empty files spread across *levels* directories."""
paths: list[str] = []
for i in range(count):
level = i % levels
parts = [f"level{d}" for d in range(level + 1)]
dirpath = os.path.join(tmpdir, *parts)
os.makedirs(dirpath, exist_ok=True)
fpath = os.path.join(dirpath, f"file_{i:05d}.py")
with open(fpath, "w") as fh:
fh.write("x" * 200) # ~50 tokens
paths.append(fpath)
return sorted(paths)
def make_lang_files(tmpdir: str, py_count: int, ts_count: int) -> list[str]:
"""Create Python and TypeScript files in separate dirs."""
paths: list[str] = []
py_dir = os.path.join(tmpdir, "src", "python")
ts_dir = os.path.join(tmpdir, "src", "typescript")
os.makedirs(py_dir, exist_ok=True)
os.makedirs(ts_dir, exist_ok=True)
for i in range(py_count):
fpath = os.path.join(py_dir, f"mod_{i:04d}.py")
with open(fpath, "w") as fh:
fh.write("x" * 200)
paths.append(fpath)
for i in range(ts_count):
fpath = os.path.join(ts_dir, f"mod_{i:04d}.ts")
with open(fpath, "w") as fh:
fh.write("x" * 200)
paths.append(fpath)
return sorted(paths)
def make_dir_files(tmpdir: str) -> list[str]:
"""Create files in two distinct directories."""
paths: list[str] = []
for sub in ("src/api", "src/web"):
dirpath = os.path.join(tmpdir, sub)
os.makedirs(dirpath, exist_ok=True)
for i in range(50):
fpath = os.path.join(dirpath, f"f_{i:03d}.py")
with open(fpath, "w") as fh:
fh.write("x" * 200)
paths.append(fpath)
return sorted(paths)
def make_same_dir_files(tmpdir: str, count: int) -> list[str]:
"""Create *count* files all in the same directory with same extension."""
dirpath = os.path.join(tmpdir, "flat")
os.makedirs(dirpath, exist_ok=True)
paths: list[str] = []
for i in range(count):
fpath = os.path.join(dirpath, f"f_{i:04d}.py")
with open(fpath, "w") as fh:
fh.write("x" * 200)
paths.append(fpath)
return sorted(paths)
def chain_graph(*names: str) -> DependencyGraph:
"""Build a chain A -> B -> C -> ..."""
g = DependencyGraph()
for n in names:
g.add_node(n)
for src, tgt in itertools.pairwise(names):
g.add_edge(DependencyEdge(src, tgt, DependencyType.IMPORT))
return g
def diamond_graph() -> DependencyGraph:
"""Build A->B, A->C, B->D, C->D."""
g = DependencyGraph()
for n in ("A", "B", "C", "D"):
g.add_node(n)
g.add_edge(DependencyEdge("A", "B", DependencyType.IMPORT))
g.add_edge(DependencyEdge("A", "C", DependencyType.IMPORT))
g.add_edge(DependencyEdge("B", "D", DependencyType.IMPORT))
g.add_edge(DependencyEdge("C", "D", DependencyType.IMPORT))
return g
def cycle_graph() -> DependencyGraph:
"""Build A -> B -> C -> A."""
g = DependencyGraph()
for n in ("A", "B", "C"):
g.add_node(n)
g.add_edge(DependencyEdge("A", "B", DependencyType.IMPORT))
g.add_edge(DependencyEdge("B", "C", DependencyType.IMPORT))
g.add_edge(DependencyEdge("C", "A", DependencyType.IMPORT))
return g