Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 450e118b1a |
@@ -47,16 +47,6 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
and milestone assignment. This eliminates systemic PR merge blockers caused by workers
|
||||
omitting required items.
|
||||
|
||||
- **ACMS context path matching now handles absolute fragment paths** (#10972): Fixed
|
||||
`_path_matches()` in `execute_phase_context_assembler.py` and `_matches_pattern()` in
|
||||
`context_phase_analysis.py` to correctly match absolute paths (e.g. `/app/.opencode/skills/SKILL.md`)
|
||||
against relative glob patterns (e.g. `.opencode/**`, `docs/*`). Previously
|
||||
`PurePath.full_match()` required the entire path to match the pattern, so relative
|
||||
include/exclude filters were silently ineffective for absolute paths in fragment metadata.
|
||||
Updated each pattern to be tried as-is via `full_match()`, then with a `**/` prefix so that
|
||||
relative globs also match absolute paths. Added BDD regression tests in
|
||||
`execute_phase_context_assembler_coverage.feature` and `project_context_phase_analysis.feature`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Restored `benchmark-regression` CI job to `master.yml` with `pull_request` trigger guard
|
||||
|
||||
@@ -31,4 +31,3 @@ Below are some of the specific details of various contributions.
|
||||
* HAL 9000 has contributed comprehensive milestone documentation for v3.6.0 (Advanced Concepts & Deferred Features) and v3.7.0 (TUI Implementation) (PR #9903): split into sub-documents covering context strategies, LLM backends, resource types, A2A rename, container tool execution, scope chain resolution, cost/safety budgets, E2E workflow tests, code review examples, plugin architecture, TUI layout, persona system, reference/command input, session management, configuration, and TuiMaterializer integration.
|
||||
* 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 ACMS context path matching fix (PR #10975 / issue #10972): corrects `_path_matches()` and `_matches_pattern()` to properly match absolute fragment paths against relative glob patterns by auto-prefixing with `**/` before calling `PurePath.full_match()`, preventing silent inefficacy of include/exclude filters for absolute paths in fragment metadata.
|
||||
|
||||
@@ -46,31 +46,6 @@ 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
|
||||
|
||||
@tdd_issue @tdd_issue_10972
|
||||
Scenario: epcov path matches absolute path against relative include glob
|
||||
When epcov I check path matching for "/app/.opencode/skills/SKILL.md" with include ".opencode/**"
|
||||
Then epcov the path should match
|
||||
|
||||
@tdd_issue @tdd_issue_10972
|
||||
Scenario: epcov path matches absolute path against relative exclude glob
|
||||
When epcov I check path matching for "/app/.opencode/skills/SKILL.md" with exclude ".opencode/**"
|
||||
Then epcov the path should not match
|
||||
|
||||
@tdd_issue @tdd_issue_10972
|
||||
Scenario: epcov path matches absolute path against relative include glob with wildcard
|
||||
When epcov I check path matching for "/app/docs/readme.md" with include "docs/*"
|
||||
Then epcov the path should match
|
||||
|
||||
@tdd_issue @tdd_issue_10972
|
||||
Scenario: epcov path matches absolute path not matching relative include glob
|
||||
When epcov I check path matching for "/app/src/main.py" with include "docs/*"
|
||||
Then epcov the path should not match
|
||||
|
||||
@tdd_issue @tdd_issue_10972
|
||||
Scenario: epcov relative path is excluded by trailing ** glob
|
||||
When epcov I check path matching for "build/debug/output.log" with exclude "build/**"
|
||||
Then epcov the path should not match
|
||||
|
||||
# ---- _resource_matches static method ----
|
||||
|
||||
Scenario: epcov resource matches with no rules passes all
|
||||
|
||||
@@ -29,10 +29,3 @@ Feature: Project context phase analysis summaries
|
||||
When I compute project context phase analysis with budget 1000
|
||||
Then execute phase should have fewer tokens than strategize phase
|
||||
And apply phase should have fewer or equal tokens than execute phase
|
||||
|
||||
@tdd_issue @tdd_issue_10972
|
||||
Scenario: Absolute path fragments are correctly excluded by relative exclude globs
|
||||
Given a phase analysis policy with opencode exclude paths
|
||||
And an absolute path fragment for phase analysis
|
||||
When I compute project context phase analysis with budget 2000
|
||||
Then strategize phase should exclude the absolute path fragment
|
||||
|
||||
@@ -16,18 +16,12 @@ Feature: LSP Resource Types
|
||||
|
||||
Examples:
|
||||
| type_name |
|
||||
| executable |
|
||||
| lsp-server |
|
||||
| lsp-workspace |
|
||||
| lsp-document |
|
||||
|
||||
# ── User-addable flags ────────────────────────────────────────
|
||||
|
||||
Scenario: executable is user-addable for lsp_rt
|
||||
Given the built-in executable YAML file for lsp_rt
|
||||
When I load the YAML via schema for lsp_rt
|
||||
Then the lsp_rt schema user_addable should be true
|
||||
|
||||
Scenario: lsp-server is user-addable for lsp_rt
|
||||
Given the built-in lsp-server YAML file for lsp_rt
|
||||
When I load the YAML via schema for lsp_rt
|
||||
@@ -51,12 +45,6 @@ Feature: LSP Resource Types
|
||||
Then the lsp_rt capability read should be true
|
||||
And the lsp_rt capability write should be true
|
||||
|
||||
Scenario: executable has read-only capabilities for lsp_rt
|
||||
Given the built-in executable YAML file for lsp_rt
|
||||
When I load the YAML via schema for lsp_rt
|
||||
Then the lsp_rt capability read should be true
|
||||
And the lsp_rt capability write should be false
|
||||
|
||||
# ── Parent/child hierarchy ────────────────────────────────────
|
||||
|
||||
Scenario: lsp-server has lsp-workspace as child type for lsp_rt
|
||||
@@ -81,12 +69,6 @@ Feature: LSP Resource Types
|
||||
|
||||
# ── Auto-discovery ────────────────────────────────────────────
|
||||
|
||||
Scenario: executable has auto-discovery from container-exec-env for lsp_rt
|
||||
Given the built-in executable YAML file for lsp_rt
|
||||
When I load the YAML via schema for lsp_rt
|
||||
Then the lsp_rt auto_discovery should be present
|
||||
And the lsp_rt auto_discovery trigger_types should contain "container-exec-env"
|
||||
|
||||
Scenario: lsp-workspace has auto-discovery from lsp-server for lsp_rt
|
||||
Given the built-in lsp-workspace YAML file for lsp_rt
|
||||
When I load the YAML via schema for lsp_rt
|
||||
@@ -97,8 +79,7 @@ Feature: LSP Resource Types
|
||||
|
||||
Scenario: BUILTIN_NAMES includes all LSP types for lsp_rt
|
||||
Given the ResourceTypeSpec BUILTIN_NAMES set for lsp_rt
|
||||
Then BUILTIN_NAMES should contain "executable" for lsp_rt
|
||||
And BUILTIN_NAMES should contain "lsp-server" for lsp_rt
|
||||
Then BUILTIN_NAMES should contain "lsp-server" for lsp_rt
|
||||
And BUILTIN_NAMES should contain "lsp-workspace" for lsp_rt
|
||||
And BUILTIN_NAMES should contain "lsp-document" for lsp_rt
|
||||
|
||||
@@ -106,9 +87,6 @@ Feature: LSP Resource Types
|
||||
|
||||
Scenario: LSP types survive DB roundtrip after bootstrap for lsp_rt
|
||||
Given a lsp_rt fresh in-memory resource registry with bootstrap
|
||||
When I query the lsp_rt registry for type "executable"
|
||||
Then the lsp_rt db type "executable" should exist
|
||||
And the lsp_rt db type "executable" should have kind "physical"
|
||||
When I query the lsp_rt registry for type "lsp-server"
|
||||
Then the lsp_rt db type "lsp-server" should exist
|
||||
When I query the lsp_rt registry for type "lsp-workspace"
|
||||
|
||||
@@ -183,54 +183,3 @@ def step_apply_less_or_equal(context: Any) -> None:
|
||||
exec_tokens = context.phase_result["phases"]["execute"]["total_tokens"]
|
||||
apply_tokens = context.phase_result["phases"]["apply"]["total_tokens"]
|
||||
assert apply_tokens <= exec_tokens
|
||||
|
||||
|
||||
@given("a phase analysis policy with opencode exclude paths")
|
||||
def step_policy_opencode_exclude(context: Any) -> None:
|
||||
"""Policy that excludes .opencode/** paths using relative globs."""
|
||||
context.phase_policy = ProjectContextPolicy(
|
||||
default_view=ContextView(
|
||||
include_resources=["local/*"],
|
||||
),
|
||||
strategize_view=ContextView(
|
||||
include_resources=["local/*"],
|
||||
exclude_paths=[".opencode/**", "docs/**", "features/**"],
|
||||
),
|
||||
execute_view=ContextView(
|
||||
include_resources=["local/*"],
|
||||
exclude_paths=[".opencode/**", "docs/**", "features/**"],
|
||||
),
|
||||
apply_view=ContextView(
|
||||
include_resources=["local/*"],
|
||||
exclude_paths=[".opencode/**", "docs/**", "features/**"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@given("an absolute path fragment for phase analysis")
|
||||
def step_absolute_path_fragment(context: Any) -> None:
|
||||
"""Fragment with an absolute path that should be excluded by relative globs."""
|
||||
context.phase_fragments = [
|
||||
TieredFragment(
|
||||
fragment_id="abs-skill",
|
||||
content="skill content",
|
||||
tier=ContextTier.HOT,
|
||||
resource_id="local/repo-a",
|
||||
project_name="local/ctx-app",
|
||||
token_count=50,
|
||||
metadata={
|
||||
"path": "/app/.opencode/skills/SKILL.md",
|
||||
"byte_size": 1000,
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@then("strategize phase should exclude the absolute path fragment")
|
||||
def step_strat_excludes_absolute(context: Any) -> None:
|
||||
"""Verify that the absolute path fragment is excluded by relative glob patterns."""
|
||||
strat = context.phase_result["phases"]["strategize"]
|
||||
assert strat["fragment_count"] == 0, (
|
||||
f"Expected 0 fragments (absolute path should be excluded by relative glob), "
|
||||
f"got {strat['fragment_count']}"
|
||||
)
|
||||
|
||||
@@ -15,26 +15,30 @@ def _import_lsp_types() -> None:
|
||||
LSP_RESOURCE_TYPES,
|
||||
)
|
||||
|
||||
assert len(LSP_RESOURCE_TYPES) == 4, f"Expected 4, got {len(LSP_RESOURCE_TYPES)}"
|
||||
assert len(LSP_RESOURCE_TYPES) == 3, f"Expected 3, got {len(LSP_RESOURCE_TYPES)}"
|
||||
names = [t["name"] for t in LSP_RESOURCE_TYPES]
|
||||
for expected in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
|
||||
for expected in ("lsp-server", "lsp-workspace", "lsp-document"):
|
||||
assert expected in names, f"{expected} not in LSP_RESOURCE_TYPES: {names}"
|
||||
|
||||
print("import-lsp-types-ok")
|
||||
|
||||
|
||||
def _check_builtin_names() -> None:
|
||||
"""Verify all 4 LSP types are in BUILTIN_NAMES."""
|
||||
"""Verify all 3 LSP types are in BUILTIN_NAMES."""
|
||||
from cleveragents.domain.models.core.resource_type import ResourceTypeSpec
|
||||
|
||||
for name in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
|
||||
for name in ("lsp-server", "lsp-workspace", "lsp-document"):
|
||||
assert name in ResourceTypeSpec.BUILTIN_NAMES, f"{name} not in BUILTIN_NAMES"
|
||||
|
||||
assert "executable" not in ResourceTypeSpec.BUILTIN_NAMES, (
|
||||
"executable should not be in BUILTIN_NAMES (not a spec-defined type)"
|
||||
)
|
||||
|
||||
print("check-builtin-names-ok")
|
||||
|
||||
|
||||
def _db_roundtrip() -> None:
|
||||
"""Bootstrap in-memory DB and verify all 4 LSP types are retrievable."""
|
||||
"""Bootstrap in-memory DB and verify all 3 LSP types are retrievable."""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
@@ -48,7 +52,7 @@ def _db_roundtrip() -> None:
|
||||
factory = sessionmaker(bind=engine, expire_on_commit=False)
|
||||
svc = ResourceRegistryService(session_factory=factory)
|
||||
|
||||
for name in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
|
||||
for name in ("lsp-server", "lsp-workspace", "lsp-document"):
|
||||
spec = svc.show_type(name)
|
||||
assert spec is not None, f"{name} not found after bootstrap"
|
||||
assert str(spec.resource_kind) == "physical", (
|
||||
@@ -85,32 +89,27 @@ def _check_hierarchy() -> None:
|
||||
assert "lsp-workspace" in types["lsp-document"]["parent_types"], (
|
||||
"lsp-document missing lsp-workspace parent"
|
||||
)
|
||||
# executable has no children
|
||||
assert types["executable"]["child_types"] == [], (
|
||||
"executable should have no children"
|
||||
)
|
||||
|
||||
print("check-hierarchy-ok")
|
||||
|
||||
|
||||
def _check_auto_discovery() -> None:
|
||||
"""Verify executable has auto_discovery with trigger_types."""
|
||||
"""Verify lsp-workspace has auto_discovery with trigger_types."""
|
||||
from cleveragents.application.services._resource_registry_lsp import (
|
||||
LSP_RESOURCE_TYPES,
|
||||
)
|
||||
|
||||
types = {t["name"]: t for t in LSP_RESOURCE_TYPES}
|
||||
types = {t["name"] for t in LSP_RESOURCE_TYPES}
|
||||
|
||||
# executable has auto_discovery
|
||||
ad = types["executable"].get("auto_discovery")
|
||||
assert ad is not None, "executable missing auto_discovery"
|
||||
assert "container-exec-env" in ad.get("trigger_types", []), (
|
||||
"executable missing container-exec-env trigger"
|
||||
# executable should not be present
|
||||
assert "executable" not in types, (
|
||||
"executable should not be in LSP_RESOURCE_TYPES (not a spec-defined type)"
|
||||
)
|
||||
assert ad.get("lazy") is True, "executable auto_discovery should be lazy"
|
||||
|
||||
lsp_types = {t["name"]: t for t in LSP_RESOURCE_TYPES}
|
||||
|
||||
# lsp-workspace has auto_discovery from lsp-server
|
||||
ad_ws = types["lsp-workspace"].get("auto_discovery")
|
||||
ad_ws = lsp_types["lsp-workspace"].get("auto_discovery")
|
||||
assert ad_ws is not None, "lsp-workspace missing auto_discovery"
|
||||
assert "lsp-server" in ad_ws.get("trigger_types", []), (
|
||||
"lsp-workspace missing lsp-server trigger"
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
"""LSP resource type definitions for the Resource Registry.
|
||||
|
||||
Contains the built-in LSP-related type entries (``executable``,
|
||||
``lsp-server``, ``lsp-workspace``, ``lsp-document``) that are appended
|
||||
to ``BUILTIN_TYPES`` in ``_resource_registry_data``.
|
||||
Contains the built-in LSP-related type entries (``lsp-server``,
|
||||
``lsp-workspace``, ``lsp-document``) that are appended to
|
||||
``BUILTIN_TYPES`` in ``_resource_registry_data``.
|
||||
|
||||
These types bridge the resource system with the LSP runtime, allowing
|
||||
actors to discover available language tooling and the LSP system to know
|
||||
which resources it operates on.
|
||||
|
||||
Spec reference: docs/adr/ADR-039-container-resource-types.md (executable),
|
||||
docs/adr/ADR-040-lsp-resource-types.md (lsp-server,
|
||||
Spec reference: docs/adr/ADR-040-lsp-resource-types.md (lsp-server,
|
||||
lsp-workspace, lsp-document)
|
||||
|
||||
.. note::
|
||||
@@ -28,52 +27,11 @@ __all__ = ["LSP_RESOURCE_TYPES"]
|
||||
# === LSP Resource Types (#832) ===
|
||||
#
|
||||
# These types model language tooling infrastructure in the resource DAG.
|
||||
# - executable: system binary / interpreter / LSP server binary
|
||||
# - lsp-server: a configured or running LSP server instance
|
||||
# - lsp-workspace: workspace root tracked by an LSP server
|
||||
# - lsp-document: text document tracked by an LSP server
|
||||
|
||||
LSP_RESOURCE_TYPES: list[dict[str, Any]] = [
|
||||
# ── executable ───────────────────────────────────────────────
|
||||
{
|
||||
"name": "executable",
|
||||
"description": (
|
||||
"System executable (language runtime, compiler, LSP server "
|
||||
"binary). Discovered from container or host PATH."
|
||||
),
|
||||
"resource_kind": "physical",
|
||||
"sandbox_strategy": "none",
|
||||
"user_addable": True,
|
||||
"built_in": True,
|
||||
"cli_args": [
|
||||
{
|
||||
"name": "path",
|
||||
"type": "string",
|
||||
"required": True,
|
||||
"description": "Absolute path to the executable",
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"type": "string",
|
||||
"required": False,
|
||||
"description": "Executable version if detectable",
|
||||
},
|
||||
],
|
||||
"parent_types": ["container-exec-env", "fs-directory"],
|
||||
"child_types": [],
|
||||
"handler": "cleveragents.resource.handlers.executable:ExecutableHandler",
|
||||
"auto_discovery": {
|
||||
"trigger_types": ["container-exec-env", "fs-directory"],
|
||||
"scan_paths": [],
|
||||
"lazy": True,
|
||||
},
|
||||
"capabilities": {
|
||||
"read": True,
|
||||
"write": False,
|
||||
"sandbox": False,
|
||||
"checkpoint": False,
|
||||
},
|
||||
},
|
||||
# ── lsp-server ───────────────────────────────────────────────
|
||||
{
|
||||
"name": "lsp-server",
|
||||
|
||||
@@ -31,20 +31,10 @@ def _path_matches(path: str, include_patterns: list[str]) -> bool:
|
||||
|
||||
|
||||
def _matches_pattern(path_obj: PurePosixPath, pattern: str) -> bool:
|
||||
"""Match a path against a glob pattern, handling absolute vs relative paths.
|
||||
|
||||
Tries ``full_match()`` with the pattern as-is, then with a ``**/``
|
||||
prefix so that relative globs (e.g. ``.opencode/*``) correctly match
|
||||
absolute paths (e.g. ``/app/.opencode/skills/SKILL.md``). Also
|
||||
handles the ``**/`` zero-depth compatibility shim.
|
||||
"""
|
||||
if path_obj.full_match(pattern):
|
||||
return True
|
||||
# Auto-prefix with **/ so relative patterns match absolute paths
|
||||
if not pattern.startswith("**/") and path_obj.full_match(f"**/{pattern}"):
|
||||
return True
|
||||
# Zero-depth shim: "**/" in pattern but full_match already tried above
|
||||
return bool("**/" in pattern and path_obj.full_match(pattern.replace("**/", "")))
|
||||
"""Match with a small compatibility shim for ``**/`` zero-depth cases."""
|
||||
return path_obj.match(pattern) or (
|
||||
"**/" in pattern and path_obj.match(pattern.replace("**/", ""))
|
||||
)
|
||||
|
||||
|
||||
def _extract_path(fragment: TieredFragment) -> str:
|
||||
|
||||
@@ -72,32 +72,13 @@ class ACMSExecutePhaseContextAssembler(ExecutePhaseContextAssembler):
|
||||
|
||||
@staticmethod
|
||||
def _path_matches(path: str, include: list[str], exclude: list[str]) -> bool:
|
||||
"""Return whether *path* passes include/exclude path globs.
|
||||
|
||||
Handles both absolute paths (e.g. ``/app/.opencode/skills/SKILL.md``)
|
||||
and relative paths (e.g. ``src/foo.py``) against relative glob
|
||||
patterns (e.g. ``.opencode/**``, ``src/**/*.py``).
|
||||
|
||||
Each pattern is tried with ``full_match()`` as-is (handles
|
||||
relative paths and ``**`` patterns). If the pattern is not
|
||||
already anchored with ``**/``, a second attempt prefixes
|
||||
``**/`` so that relative globs also match absolute paths.
|
||||
"""
|
||||
"""Return whether *path* passes include/exclude path globs."""
|
||||
pure_path = PurePath(path)
|
||||
|
||||
def _matches_any(patterns: list[str]) -> bool:
|
||||
for pattern in patterns:
|
||||
if pure_path.full_match(pattern):
|
||||
return True
|
||||
if not pattern.startswith("**/") and pure_path.full_match(
|
||||
f"**/{pattern}"
|
||||
):
|
||||
return True
|
||||
if include and not any(pure_path.full_match(pattern) for pattern in include):
|
||||
return False
|
||||
|
||||
if include and not _matches_any(include):
|
||||
return False
|
||||
return not (exclude and _matches_any(exclude))
|
||||
return not (
|
||||
exclude and any(pure_path.full_match(pattern) for pattern in exclude)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resource_matches(
|
||||
|
||||
@@ -125,7 +125,6 @@ BUILTIN_TYPE_NAMES: frozenset[str] = frozenset(
|
||||
"submodule",
|
||||
"symlink",
|
||||
# LSP resource types (#832)
|
||||
"executable",
|
||||
"lsp-server",
|
||||
"lsp-workspace",
|
||||
"lsp-document",
|
||||
|
||||
Reference in New Issue
Block a user