From 81a584b999d6fb154e2b0cb706f7f464ed0e33da Mon Sep 17 00:00:00 2001 From: CleverThis Date: Tue, 14 Apr 2026 17:45:38 +0000 Subject: [PATCH 1/2] fix(decomposition): make _directory_key relative-path-aware for absolute file paths Updated _directory_key() in decomposition_clustering.py to accept an optional root parameter and, when provided, compute relative paths before deriving the directory key. This allows proper clustering even for absolute file paths. ClusterByDirectory updated to accept and propagate the root parameter to _directory_key(). DecompositionService._build_hierarchy() now determines a common root with _common_prefix() and passes it to cluster_by_directory(), ensuring directory clustering groups paths by their actual directory hierarchy rather than absolute paths. These changes fix ineffective directory clustering for absolute paths in production. ISSUES CLOSED: #9401 --- .../services/decomposition_clustering.py | 48 ++++++++++++++++-- .../services/decomposition_service.py | 4 +- test_reports/summary.txt | 27 ++++++++++ test_reports/test_results.json | 50 +++++++++++++++++++ 4 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 test_reports/summary.txt create mode 100644 test_reports/test_results.json diff --git a/src/cleveragents/application/services/decomposition_clustering.py b/src/cleveragents/application/services/decomposition_clustering.py index d78a207e3..db2554704 100644 --- a/src/cleveragents/application/services/decomposition_clustering.py +++ b/src/cleveragents/application/services/decomposition_clustering.py @@ -54,14 +54,52 @@ def _extension_of(path: str) -> str: return ext.lower() -def _directory_key(path: str, depth: int = 2) -> str: +def _directory_key(path: str, depth: int = 2, root: str | None = None) -> str: """Return the first *depth* path components as a grouping key. For ``"src/cleveragents/foo/bar.py"`` with *depth=2* the key is ``"src/cleveragents"``. + + When *root* is provided, the key is computed relative to the root. + For example, with root ``"/home/user/project"`` and path + ``"/home/user/project/src/api/handler.py"``, the relative path is + ``"src/api/handler.py"`` and the key is ``"src/api"``. + + Args: + path: File path (absolute or relative). + depth: Number of leading path components for the grouping key. + root: Optional root directory. When provided, the key is computed + relative to this root. + + Returns: + The directory key (first *depth* components of the path). """ - parts = path.replace("\\", "/").split("/") - return "/".join(parts[:depth]) if len(parts) > depth else "/".join(parts[:-1]) + # Normalize path separators + normalized = path.replace("\\", "/") + + # If root is provided, compute relative path + if root: + root_normalized = root.replace("\\", "/") + # Ensure root ends with / for proper prefix matching + if not root_normalized.endswith("/"): + root_normalized += "/" + # Remove root prefix if path starts with it + if normalized.startswith(root_normalized): + normalized = normalized[len(root_normalized) :] + + parts = normalized.split("/") + # Filter out empty parts (from leading / in absolute paths) + parts = [p for p in parts if p] + + if not parts: + return "" + + # Return first *depth* components, or all but the last (filename) + if len(parts) > depth: + return "/".join(parts[:depth]) + else: + # For short paths, return all but the last component (filename) + return "/".join(parts[:-1]) if len(parts) > 1 else "" # --------------------------------------------------------------------------- @@ -82,6 +120,7 @@ class ClusteringStrategy: max_per_cluster: int, *, depth: int = 2, + root: str | None = None, ) -> list[list[str]]: """Group *files* by directory prefix. @@ -89,13 +128,14 @@ class ClusteringStrategy: files: File paths to partition. max_per_cluster: Maximum files per cluster. depth: Number of leading path components for the grouping key. + root: Optional root directory for relative path computation. Returns: Ordered list of clusters. """ buckets: dict[str, list[str]] = defaultdict(list) for f in sorted(files): - buckets[_directory_key(f, depth=depth)].append(f) + buckets[_directory_key(f, depth=depth, root=root)].append(f) clusters: list[list[str]] = [] for key in sorted(buckets): diff --git a/src/cleveragents/application/services/decomposition_service.py b/src/cleveragents/application/services/decomposition_service.py index 4e9a39392..d96e5ed8c 100644 --- a/src/cleveragents/application/services/decomposition_service.py +++ b/src/cleveragents/application/services/decomposition_service.py @@ -231,8 +231,10 @@ class DecompositionService: return depth # Cluster using directory strategy first, fall back to language + # Compute common root for relative path computation + common_root = _common_prefix(files) clusters = ClusteringStrategy.cluster_by_directory( - files, config.max_files_per_subplan + files, config.max_files_per_subplan, root=common_root ) strategy = ClusterStrategy.DIRECTORY diff --git a/test_reports/summary.txt b/test_reports/summary.txt new file mode 100644 index 000000000..70e00a651 --- /dev/null +++ b/test_reports/summary.txt @@ -0,0 +1,27 @@ +Test Framework: generic +Total Tests: 3 +Passed: 1 +Failed: 2 + +--- Test Results --- +✗ Output Block 1 +✓ nox > Running session typecheck +✗ Error Output + +--- Failed Tests --- +✗ Output Block 1 + /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py + /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import "langchain_groq" could not be resolved from source (reportMissingModuleSource) + /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import "langchain_together" could not be resolved from source (reportMissingModuleSource) + /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import "langchain_cohere" could not be resolved from source (reportMissingModuleSource) + 0 errors, 3 warnings, 0 informations + +✗ Error Output + nox > Running session typecheck + nox > Creating virtual environment (uv) using python3.13 in .nox/typecheck + nox > uv pip install pyright + nox > uv pip install -e . + nox > pyright + nox > Session typecheck was successful in 35 seconds. + + diff --git a/test_reports/test_results.json b/test_reports/test_results.json new file mode 100644 index 000000000..d64e1e442 --- /dev/null +++ b/test_reports/test_results.json @@ -0,0 +1,50 @@ +{ + "framework": "generic", + "tests": [ + { + "name": "Output Block 1", + "passed": false, + "output": [ + "/tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py", + " /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import \"langchain_groq\" could not be resolved from source (reportMissingModuleSource)", + " /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import \"langchain_together\" could not be resolved from source (reportMissingModuleSource)", + " /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import \"langchain_cohere\" could not be resolved from source (reportMissingModuleSource)", + "0 errors, 3 warnings, 0 informations" + ], + "rawOutput": "/tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import \"langchain_groq\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import \"langchain_together\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import \"langchain_cohere\" could not be resolved from source (reportMissingModuleSource)\n0 errors, 3 warnings, 0 informations" + }, + { + "name": "nox > Running session typecheck", + "passed": true, + "output": [ + "nox > Running session typecheck", + "nox > Creating virtual environment (uv) using python3.13 in .nox/typecheck", + "nox > uv pip install pyright", + "nox > uv pip install -e .", + "nox > pyright ", + "nox > Session typecheck was successful in 35 seconds." + ], + "rawOutput": "nox > Running session typecheck\nnox > Creating virtual environment (uv) using python3.13 in .nox/typecheck\nnox > uv pip install pyright\nnox > uv pip install -e .\nnox > pyright \nnox > Session typecheck was successful in 35 seconds." + }, + { + "name": "Error Output", + "passed": false, + "output": [ + "nox > Running session typecheck", + "nox > Creating virtual environment (uv) using python3.13 in .nox/typecheck", + "nox > uv pip install pyright", + "nox > uv pip install -e .", + "nox > pyright ", + "nox > Session typecheck was successful in 35 seconds.", + "" + ], + "rawOutput": "nox > Running session typecheck\nnox > Creating virtual environment (uv) using python3.13 in .nox/typecheck\nnox > uv pip install pyright\nnox > uv pip install -e .\nnox > pyright \nnox > Session typecheck was successful in 35 seconds.\n" + } + ], + "summary": { + "total": 3, + "passed": 1, + "failed": 2 + }, + "rawOutput": "/tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import \"langchain_groq\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import \"langchain_together\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import \"langchain_cohere\" could not be resolved from source (reportMissingModuleSource)\n0 errors, 3 warnings, 0 informations\n\nnox > Running session typecheck\nnox > Creating virtual environment (uv) using python3.13 in .nox/typecheck\nnox > uv pip install pyright\nnox > uv pip install -e .\nnox > pyright \nnox > Session typecheck was successful in 35 seconds." +} \ No newline at end of file -- 2.52.0 From e261ea5abe91b5676426c7376788df4d316003a0 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Thu, 16 Apr 2026 07:43:02 +0000 Subject: [PATCH 2/2] fix(decomposition): address review feedback for PR #9437 - Remove committed test_reports/ CI artifacts (summary.txt, test_results.json) - Add test_reports/ to .gitignore to prevent future commits - Update CHANGELOG.md with Fixed entry for #9401 under [Unreleased] - Update CONTRIBUTORS.md with credit for directory clustering fix (#9401) - Add BDD test scenarios for absolute path clustering in features/large_project_decomposition.feature: * Directory clustering works correctly with absolute file paths * _directory_key returns correct key for absolute path with root * Directory clustering does not collapse all absolute paths into one bucket - Add corresponding step implementations in features/steps/large_project_decomposition_coverage_steps.py All 38 scenarios pass (including 3 new absolute path scenarios). Lint and typecheck pass. ISSUES CLOSED: #9401 --- .gitignore | 3 + CHANGELOG.md | 1 + CONTRIBUTORS.md | 3 +- features/large_project_decomposition.feature | 16 +++++ ...ge_project_decomposition_coverage_steps.py | 63 +++++++++++++++++++ test_reports/summary.txt | 27 -------- test_reports/test_results.json | 50 --------------- 7 files changed, 85 insertions(+), 78 deletions(-) delete mode 100644 test_reports/summary.txt delete mode 100644 test_reports/test_results.json diff --git a/.gitignore b/.gitignore index 4f40d20e2..e066e2b25 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,6 @@ output.xml report.html .agent-orchestration agents-test + +# Generated test reports (CI artifacts) +test_reports/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 595123305..b92be5a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). failure output significantly easier to spot in CI and local runs. A worker crash (unhandled exception) is detected via an all-zero summary and the captured traceback is always surfaced. +- **Directory Clustering Absolute Path Fix** (#9401): Fixed `DecompositionService._directory_key` to correctly handle absolute file paths by computing relative paths before extracting directory keys. Previously, the function used a fixed depth of 2 path components, causing all absolute paths to collapse into a single bucket (e.g., `/home` for every file on the system), making directory-based clustering completely ineffective. The fix adds an optional `root` parameter to `_directory_key()` and `ClusteringStrategy.cluster_by_directory()`, and updates `DecompositionService._build_hierarchy()` to compute the common root and pass it through, ensuring directory clustering groups paths by their actual directory hierarchy in production use. - **Automation Profile Silent Fallback** (#8232): `_resolve_profile_for_plan` in `PlanLifecycleService` now raises a clear `ValidationError` when a plan's diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 67cfaa955..074e45e6d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,7 +7,6 @@ * Jeffrey Phillips Freeman * Luis Mendes * Rui Hu -* HAL 9000 # Details @@ -22,4 +21,6 @@ Below are some of the specific details of various contributions. * Rui Hu has contributed the v3 actor YAML schema validation fix (#5869): added `ActorConfigSchema` validation to the `agents actor add --config` CLI command, covering cycle detection, required field validation, and enum validation for v3 YAML actor definitions. * HAL 9000 has contributed automated bug fixes, including fix #7488 (store sandbox_path in checkpoint metadata to enable rollback). * This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc. +* HAL 9000 has contributed the directory clustering absolute path fix (#9401): updated `_directory_key()` to accept an optional `root` parameter for relative path computation, enabling effective directory-based clustering for projects with absolute file paths. * HAL 9000 has contributed automated bug fixes, CLI output formatting improvements, and ongoing maintenance as part of the CleverAgents automation system. +* This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc. diff --git a/features/large_project_decomposition.feature b/features/large_project_decomposition.feature index 5f9e837e8..e006ce210 100644 --- a/features/large_project_decomposition.feature +++ b/features/large_project_decomposition.feature @@ -232,3 +232,19 @@ Feature: Large-project hierarchical decomposition Scenario: Directory key handles short paths When I compute directory key for a single-component path Then the directory key should be empty string + + # --- absolute path clustering ------------------------------------------ + + Scenario: Directory clustering works correctly with absolute file paths + Given a project with absolute paths in distinct subdirectories + When I decompose with directory clustering + Then at least two clusters should have different directory prefixes + + Scenario: _directory_key returns correct key for absolute path with root + When I compute directory key for an absolute path with a root + Then the directory key should reflect the relative path structure + + Scenario: Directory clustering does not collapse all absolute paths into one bucket + Given a project with absolute paths spanning multiple top-level directories + When I decompose with max_files_per_subplan 50 + Then the decomposition result should have max_depth_reached >= 1 diff --git a/features/steps/large_project_decomposition_coverage_steps.py b/features/steps/large_project_decomposition_coverage_steps.py index 744ca804f..cc589b8b9 100644 --- a/features/steps/large_project_decomposition_coverage_steps.py +++ b/features/steps/large_project_decomposition_coverage_steps.py @@ -240,3 +240,66 @@ def step_when_dir_key_short(context: Any) -> None: @then("the directory key should be empty string") def step_then_dir_key_empty(context: Any) -> None: assert context.dir_key == "" + + +# --------------------------------------------------------------------------- +# absolute path clustering scenarios +# --------------------------------------------------------------------------- + + +@given("a project with absolute paths in distinct subdirectories") +def step_given_abs_path_project(context): + import os + import tempfile + + tmpdir = tempfile.mkdtemp(prefix="decompose-abs-") + context.tmpdir = tmpdir + paths = [] + 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) + context.files = sorted(paths) + + +@given("a project with absolute paths spanning multiple top-level directories") +def step_given_abs_path_multi_top(context): + import os + import tempfile + + tmpdir = tempfile.mkdtemp(prefix="decompose-multi-") + context.tmpdir = tmpdir + paths = [] + for top in ("alpha", "beta", "gamma", "delta"): + for sub in ("core", "utils"): + dirpath = os.path.join(tmpdir, top, sub) + os.makedirs(dirpath, exist_ok=True) + for i in range(30): + fpath = os.path.join(dirpath, f"f_{i:03d}.py") + with open(fpath, "w") as fh: + fh.write("x" * 200) + paths.append(fpath) + context.files = sorted(paths) + + +@when("I compute directory key for an absolute path with a root") +def step_when_dir_key_abs_with_root(context): + from cleveragents.application.services.decomposition_clustering import ( + _directory_key, + ) + + root = "/home/user/project" + path = "/home/user/project/src/api/handler.py" + context.dir_key = _directory_key(path, depth=2, root=root) + context.expected_dir_key = "src/api" + + +@then("the directory key should reflect the relative path structure") +def step_then_dir_key_relative(context): + assert context.dir_key == context.expected_dir_key, ( + f"expected {context.expected_dir_key!r}, got {context.dir_key!r}" + ) diff --git a/test_reports/summary.txt b/test_reports/summary.txt deleted file mode 100644 index 70e00a651..000000000 --- a/test_reports/summary.txt +++ /dev/null @@ -1,27 +0,0 @@ -Test Framework: generic -Total Tests: 3 -Passed: 1 -Failed: 2 - ---- Test Results --- -✗ Output Block 1 -✓ nox > Running session typecheck -✗ Error Output - ---- Failed Tests --- -✗ Output Block 1 - /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py - /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import "langchain_groq" could not be resolved from source (reportMissingModuleSource) - /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import "langchain_together" could not be resolved from source (reportMissingModuleSource) - /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import "langchain_cohere" could not be resolved from source (reportMissingModuleSource) - 0 errors, 3 warnings, 0 informations - -✗ Error Output - nox > Running session typecheck - nox > Creating virtual environment (uv) using python3.13 in .nox/typecheck - nox > uv pip install pyright - nox > uv pip install -e . - nox > pyright - nox > Session typecheck was successful in 35 seconds. - - diff --git a/test_reports/test_results.json b/test_reports/test_results.json deleted file mode 100644 index d64e1e442..000000000 --- a/test_reports/test_results.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "framework": "generic", - "tests": [ - { - "name": "Output Block 1", - "passed": false, - "output": [ - "/tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py", - " /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import \"langchain_groq\" could not be resolved from source (reportMissingModuleSource)", - " /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import \"langchain_together\" could not be resolved from source (reportMissingModuleSource)", - " /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import \"langchain_cohere\" could not be resolved from source (reportMissingModuleSource)", - "0 errors, 3 warnings, 0 informations" - ], - "rawOutput": "/tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import \"langchain_groq\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import \"langchain_together\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import \"langchain_cohere\" could not be resolved from source (reportMissingModuleSource)\n0 errors, 3 warnings, 0 informations" - }, - { - "name": "nox > Running session typecheck", - "passed": true, - "output": [ - "nox > Running session typecheck", - "nox > Creating virtual environment (uv) using python3.13 in .nox/typecheck", - "nox > uv pip install pyright", - "nox > uv pip install -e .", - "nox > pyright ", - "nox > Session typecheck was successful in 35 seconds." - ], - "rawOutput": "nox > Running session typecheck\nnox > Creating virtual environment (uv) using python3.13 in .nox/typecheck\nnox > uv pip install pyright\nnox > uv pip install -e .\nnox > pyright \nnox > Session typecheck was successful in 35 seconds." - }, - { - "name": "Error Output", - "passed": false, - "output": [ - "nox > Running session typecheck", - "nox > Creating virtual environment (uv) using python3.13 in .nox/typecheck", - "nox > uv pip install pyright", - "nox > uv pip install -e .", - "nox > pyright ", - "nox > Session typecheck was successful in 35 seconds.", - "" - ], - "rawOutput": "nox > Running session typecheck\nnox > Creating virtual environment (uv) using python3.13 in .nox/typecheck\nnox > uv pip install pyright\nnox > uv pip install -e .\nnox > pyright \nnox > Session typecheck was successful in 35 seconds.\n" - } - ], - "summary": { - "total": 3, - "passed": 1, - "failed": 2 - }, - "rawOutput": "/tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:529:18 - warning: Import \"langchain_groq\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:537:18 - warning: Import \"langchain_together\" could not be resolved from source (reportMissingModuleSource)\n /tmp/gpt5codex-isolator-20260414-0023456789/repo/src/cleveragents/providers/registry.py:545:18 - warning: Import \"langchain_cohere\" could not be resolved from source (reportMissingModuleSource)\n0 errors, 3 warnings, 0 informations\n\nnox > Running session typecheck\nnox > Creating virtual environment (uv) using python3.13 in .nox/typecheck\nnox > uv pip install pyright\nnox > uv pip install -e .\nnox > pyright \nnox > Session typecheck was successful in 35 seconds." -} \ No newline at end of file -- 2.52.0