diff --git a/.forgejo/workflows/master.yml b/.forgejo/workflows/master.yml index 7c959ba40..ccdede22d 100644 --- a/.forgejo/workflows/master.yml +++ b/.forgejo/workflows/master.yml @@ -3,8 +3,6 @@ name: CI on: push: branches: [master, develop] - pull_request: - branches: [master, develop] vars: docker_prefix: "http://harbor.cleverthis.com/docker/" diff --git a/CHANGELOG.md b/CHANGELOG.md index fba107b5f..1cb058bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed + +- **fix(acms): normalize context path matching for absolute paths in `_path_matches`** (#10975): + Added leading-slash stripping and sub-suffix enumeration inside + ``ACMSExecutePhaseContextAssembler._path_matches()`` so that relative include/exclude + globs (e.g. ``src/**/*.py``) still match files whose metadata carries full POSIX paths + (e.g. ``/home/user/src/main.py``) emitted by the UKO indexer / file walker. Without this + fix, absolute paths were silently excluded by every relative pattern, causing the execute-phase + context pipeline to discard a large fraction of valid source files — especially in sandbox or + Docker-container workflows where paths are stored as canonical POSIX strings. Also added + three new Behave scenarios covering absolute-path include matches, includes that still reject + correctly after normalisation, and absolute-path exclude matching post-normalisation. + - Fixed `ReactiveEventBus.emit()` exception handler to log the full exception message (`str(exc)`) and enable traceback forwarding (`exc_info=True`). Previously the handler logged only the exception type name (e.g. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 382d4fe35..54e73c2ec 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -33,4 +33,5 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the LLMTraceRepository data-integrity fix (PR #8185 / issue #7505): replaced the unconditional `session.commit()` in `LLMTraceRepository.save()` with a dual-path implementation that respects the UnitOfWork pattern — flushing only when an external session is provided, and flushing + committing + closing when operating standalone. This eliminates premature transaction commits, loss of rollback capability, and a docstring/implementation mismatch. * HAL 9000 has contributed the ACMS Index Data Model and File Traversal Engine (PR #9664 / issue #9579): foundational data structures for indexed context entries with hot/warm/cold/archive storage tier classification, tag system, and a timeout-safe chunked file traversal engine for large projects with 10,000+ files. * HAL 9000 has contributed the error-suppression removal fix (PR #9247 / issue #9060): removed both `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors from `actor_registry.list_actors()` and the route bridge refresh, enabling exceptions to propagate per CONTRIBUTING.md fail-fast policy. Added three Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation. + * HAL 9000 has contributed the ACMS path-matching normalisation fix (PR #10975 / issue #10975): added sub-suffix enumeration in `ACMSExecutePhaseContextAssembler._path_matches()` via leading-slash stripping so relative include/exclude globs match absolute POSIX paths from the indexer. Includes three new Behave scenarios for absolute-path matching edge cases. * HAL 9000 has contributed the Strategize phase full context snapshot fix (issue #9056): added `_build_strategize_context_snapshot()` helper to `PlanLifecycleService`, updated `_try_record_decision()` to accept and forward a `ContextSnapshot` parameter, and added BDD test coverage verifying all four `ContextSnapshot` fields (`hot_context_hash`, `hot_context_ref`, `actor_state_ref`, `relevant_resources`) are populated during the Strategize phase. diff --git a/features/execute_phase_context_assembler_coverage.feature b/features/execute_phase_context_assembler_coverage.feature index 58e24a72d..d050c2bfa 100644 --- a/features/execute_phase_context_assembler_coverage.feature +++ b/features/execute_phase_context_assembler_coverage.feature @@ -46,6 +46,18 @@ Feature: Execute-phase context assembler coverage When epcov I check path matching for "src/foo.py" with exclude "src/secret*" Then epcov the path should match + Scenario: epcov absolute path matches relative include pattern after normalisation + When epcov I check path matching for "/home/user/src/main.py" with include "src/**/*.py" + Then epcov the path should match + + Scenario: epcov absolute path does not match unrelated relative include pattern + When epcov I check path matching for "/home/user/docs/readme.md" with include "src/**/*.py" + Then epcov the path should not match + + Scenario: epcov absolute path excluded by relative exclude pattern after normalisation + When epcov I check path matching for "/var/lib/src/secret.py" with exclude "src/secret*" + Then epcov the path should not match + # ---- _resource_matches static method ---- Scenario: epcov resource matches with no rules passes all diff --git a/src/cleveragents/application/services/execute_phase_context_assembler.py b/src/cleveragents/application/services/execute_phase_context_assembler.py index f3810ab3e..a99d95275 100644 --- a/src/cleveragents/application/services/execute_phase_context_assembler.py +++ b/src/cleveragents/application/services/execute_phase_context_assembler.py @@ -3,7 +3,7 @@ from __future__ import annotations import fnmatch -from pathlib import PurePath +from pathlib import PurePath, PurePosixPath from typing import Any, Protocol import structlog @@ -72,13 +72,53 @@ class ACMSExecutePhaseContextAssembler(ExecutePhaseContextAssembler): @staticmethod def _path_matches(path: str, include: list[str], exclude: list[str]) -> bool: - """Return whether *path* passes include/exclude path globs.""" - pure_path = PurePath(path) - if include and not any(pure_path.full_match(pattern) for pattern in include): - return False - return not ( - exclude and any(pure_path.full_match(pattern) for pattern in exclude) - ) + """Return whether *path* passes include/exclude path globs. + + Normalises absolute paths by stripping a leading ``/`` and then tries each + include/exclude glob against every possible sub-suffix of the normalised path. + This lets relative patterns such as ``src/**/*.py`` match files whose metadata + carries full POSIX paths (e.g. ``/home/user/src/main.py``) emitted by the UKO + indexer / file walker. Paths with a single component are matched directly via + ``full_match()`` to avoid unnecessary suffix enumeration. + """ + # Strip a leading slash so relative globs can match against absolute paths. + normalised = path.removeprefix("/") + + if len(PurePosixPath(normalised).parts) <= 1: + # Single-component path -- match directly via ``full_match()``. + pure_path = PurePath(normalised) + if include and not any( + pure_path.full_match(p) for p in include + ): + return False + return not (exclude and any( + pure_path.full_match(p) for p in exclude + )) + + # Multi-component path: enumerate every sub-suffix by dropping leading + # components one at a time. For ``/home/user/src/main.py`` the suffixes + # are ``src/main.py`` and ``main.py``, so a relative glob like + # ``src/**/*.py`` will find a match on the ``src/main.py`` suffix. + parts = list(PurePosixPath(normalised).parts) + suffixes = [PurePath("/".join(parts[i:])) for i in range(len(parts))] + + if include: + if not any( + sub.full_match(pattern) + for pattern in include + for sub in suffixes + ): + return False + + if exclude: + if any( + sub.full_match(pattern) + for pattern in exclude + for sub in suffixes + ): + return False + + return True @staticmethod def _resource_matches(