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
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
113 lines
3.7 KiB
Python
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
|