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
218 lines
9.0 KiB
Gherkin
218 lines
9.0 KiB
Gherkin
Feature: Large-project hierarchical decomposition
|
|
As a plan orchestrator
|
|
I want to decompose large file sets into bounded subplans
|
|
So that each subplan can be executed independently within token budgets
|
|
|
|
Background:
|
|
Given a decomposition service
|
|
|
|
# --- decomposition depth -----------------------------------------------
|
|
|
|
Scenario: Decomposition produces multiple levels for a deep project
|
|
Given a project with 2000 files across 5 directory levels
|
|
When I decompose with max_depth 4
|
|
Then the decomposition result should have max_depth_reached >= 1
|
|
And every leaf node should have at most 50 files
|
|
|
|
Scenario: Decomposition respects max_depth limit
|
|
Given a project with 2000 files across 5 directory levels
|
|
When I decompose with max_depth 2
|
|
Then the decomposition result should have max_depth_reached <= 2
|
|
|
|
# --- bounded context ---------------------------------------------------
|
|
|
|
Scenario: Leaf nodes respect max_files_per_subplan
|
|
Given a project with 1500 files in a single directory
|
|
When I decompose with max_files_per_subplan 500
|
|
Then every leaf node should have at most 500 files
|
|
|
|
Scenario: Leaf nodes respect max_tokens_per_subplan
|
|
Given a project with files totalling 500000 estimated tokens
|
|
When I decompose with max_tokens_per_subplan 100000
|
|
Then every leaf node should have estimated tokens at most 100000
|
|
|
|
# --- language clustering -----------------------------------------------
|
|
|
|
Scenario: Files are clustered by language
|
|
Given a project with 100 Python files and 100 TypeScript files
|
|
When I decompose with language clustering
|
|
Then at least two clusters should have different dominant languages
|
|
|
|
# --- directory clustering ----------------------------------------------
|
|
|
|
Scenario: Files are clustered by directory
|
|
Given a project with files in src/api and src/web directories
|
|
When I decompose with directory clustering
|
|
Then at least two clusters should have different directory prefixes
|
|
|
|
# --- dependency closure ------------------------------------------------
|
|
|
|
Scenario: Compute transitive closure of root files
|
|
Given a dependency graph with chain A -> B -> C -> D
|
|
When I compute closure from A with no cutoff
|
|
Then the closure should contain A, B, C, D
|
|
|
|
Scenario: Bounded closure respects cutoff
|
|
Given a dependency graph with chain A -> B -> C -> D
|
|
When I compute closure from A with cutoff 2
|
|
Then the closure should contain at most 2 files
|
|
|
|
# --- DAG ordering ------------------------------------------------------
|
|
|
|
Scenario: Topological sort produces valid execution order
|
|
Given a dependency graph with edges A->B, A->C, B->D, C->D
|
|
When I compute the topological sort
|
|
Then A should appear before B and C
|
|
And B and C should appear before D
|
|
|
|
# --- cycle detection ---------------------------------------------------
|
|
|
|
Scenario: Detect cycles in dependency graph
|
|
Given a dependency graph with cycle A -> B -> C -> A
|
|
When I check for cycles
|
|
Then at least one cycle should be detected
|
|
|
|
Scenario: No cycles in acyclic graph
|
|
Given a dependency graph with chain A -> B -> C -> D
|
|
When I check for cycles
|
|
Then no cycles should be detected
|
|
|
|
# --- fallback strategy -------------------------------------------------
|
|
|
|
Scenario: Fallback when heuristics produce a single cluster
|
|
Given a project with 100 files all in the same directory with same extension
|
|
When I decompose with max_files_per_subplan 200
|
|
Then the decomposition should produce a single root node
|
|
|
|
# --- threshold guard ---------------------------------------------------
|
|
|
|
Scenario: Skip decomposition when below threshold
|
|
Given a project with 5 files
|
|
When I decompose with min_files_per_subplan 10
|
|
Then the decomposition should produce a single root node
|
|
And the metrics should contain skipped equal to 1
|
|
|
|
# --- config keys -------------------------------------------------------
|
|
|
|
Scenario: Default config has expected values
|
|
Given default decomposition config
|
|
Then max_depth should be 4
|
|
And max_files_per_subplan should be 500
|
|
And max_tokens_per_subplan should be 100000
|
|
And min_files_per_subplan should be 10
|
|
|
|
Scenario: Invalid config values are rejected
|
|
When I create a config with max_depth 0
|
|
Then a decomposition ValueError should be raised
|
|
|
|
# --- metrics -----------------------------------------------------------
|
|
|
|
Scenario: Decomposition result contains metrics
|
|
Given a project with 2000 files across 5 directory levels
|
|
When I decompose with default config
|
|
Then metrics should contain total_nodes
|
|
And metrics should contain leaf_nodes
|
|
And metrics should contain max_depth
|
|
|
|
# --- decision recording ------------------------------------------------
|
|
|
|
Scenario: Decisions are recorded in DecisionService
|
|
Given a decomposition service with a mock decision service
|
|
And a project with 2000 files across 5 directory levels
|
|
When I decompose and record decisions for plan "01HXBBBBBBBBBBBBBBBBBBBBBB"
|
|
Then at least one strategy_choice decision should be recorded
|
|
And at least one subplan_spawn decision should be recorded
|
|
|
|
# --- deterministic ordering --------------------------------------------
|
|
|
|
Scenario: Deterministic sort produces stable ordering
|
|
Given two identical file sets presented in different order
|
|
When I decompose both
|
|
Then both decomposition results should have identical node ordering
|
|
|
|
# --- memoization -------------------------------------------------------
|
|
|
|
Scenario: Closure computation is memoised for repeated queries
|
|
Given a dependency graph with chain A -> B -> C -> D
|
|
When I compute closure from A twice
|
|
Then the second computation should return the same result
|
|
|
|
# --- additional config validation ----------------------------------------
|
|
|
|
Scenario: Config rejects max_files_per_subplan of zero
|
|
When I create a config with max_files_per_subplan 0
|
|
Then a decomposition ValueError should be raised
|
|
|
|
Scenario: Config rejects max_tokens_per_subplan of zero
|
|
When I create a config with max_tokens_per_subplan 0
|
|
Then a decomposition ValueError should be raised
|
|
|
|
Scenario: Config rejects min_files_per_subplan of zero
|
|
When I create a config with min_files_per_subplan 0
|
|
Then a decomposition ValueError should be raised
|
|
|
|
# --- estimate_tokens edge cases ------------------------------------------
|
|
|
|
Scenario: Estimate tokens for nonexistent file returns zero
|
|
When I estimate tokens for a nonexistent file
|
|
Then the token estimate should be 0
|
|
|
|
# --- cluster_by_size with token_map --------------------------------------
|
|
|
|
Scenario: Cluster by size uses provided token_map
|
|
Given a set of files with known token counts
|
|
When I cluster by size with a token_map and max_tokens 100
|
|
Then the clusters should respect the token_map values
|
|
|
|
# --- closure trimming with multiple roots --------------------------------
|
|
|
|
Scenario: Closure trims deterministically when result exceeds cutoff
|
|
Given a dependency graph with two roots each reaching 3 nodes
|
|
When I compute closure from both roots with cutoff 3
|
|
Then the closure should contain at most 3 files
|
|
|
|
# --- clear cache ---------------------------------------------------------
|
|
|
|
Scenario: Clear cache drops memoised results
|
|
Given a dependency graph with chain A -> B -> C -> D
|
|
When I compute closure from A and then clear the cache
|
|
Then computing closure from A again should still work
|
|
|
|
# --- service wrapper methods ---------------------------------------------
|
|
|
|
Scenario: Service compute_dependency_order delegates to closure computer
|
|
Given a dependency graph with edges A->B, A->C, B->D, C->D
|
|
When I compute dependency order through the service
|
|
Then A should appear before B and C in the service order
|
|
And B and C should appear before D in the service order
|
|
|
|
Scenario: Service compute_closure delegates to closure computer
|
|
Given a dependency graph with chain A -> B -> C -> D
|
|
When I compute closure through the service from A
|
|
Then the service closure should contain A, B, C, D
|
|
|
|
# --- decision_service property -------------------------------------------
|
|
|
|
Scenario: Decision service property returns injected service
|
|
Given a decomposition service with a mock decision service
|
|
Then the decision service property should return the mock
|
|
|
|
Scenario: Decision service property returns None when not injected
|
|
Then the decision service property should return None
|
|
|
|
# --- empty file edge cases -----------------------------------------------
|
|
|
|
Scenario: Dominant extension of empty list returns empty string
|
|
When I compute dominant extension of an empty list
|
|
Then the dominant extension should be empty
|
|
|
|
Scenario: Common prefix of empty list returns empty string
|
|
When I compute common prefix of an empty list
|
|
Then the common prefix should be empty
|
|
|
|
# --- directory key short path --------------------------------------------
|
|
|
|
Scenario: Directory key handles short paths
|
|
When I compute directory key for a single-component path
|
|
Then the directory key should be empty string
|