mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
Fix headroom learn failing on project paths with underscores (#159)
_component_tokenizations only split on `-` and `.`, so directory names like `my_project` could never be reconstructed from the dash-encoded slug. Add `_` as a separator so the greedy decoder matches snake_case directory names correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a7b8b608f
commit
7c91fe1e4b
2 changed files with 68 additions and 4 deletions
|
|
@ -375,9 +375,9 @@ def _component_tokenizations(component: str) -> list[list[str]]:
|
|||
|
||||
add([component])
|
||||
|
||||
for separator in ("-", ".", None):
|
||||
for separator in ("-", ".", "_", None):
|
||||
if separator is None:
|
||||
tokens = [token for token in re.split(r"[-.]", component) if token]
|
||||
tokens = [token for token in re.split(r"[-._]", component) if token]
|
||||
else:
|
||||
tokens = [token for token in component.split(separator) if token]
|
||||
add(tokens)
|
||||
|
|
@ -385,9 +385,9 @@ def _component_tokenizations(component: str) -> list[list[str]]:
|
|||
if component.startswith(".") and len(component) > 1:
|
||||
hidden_component = component[1:]
|
||||
add(["", hidden_component])
|
||||
for separator in ("-", ".", None):
|
||||
for separator in ("-", ".", "_", None):
|
||||
if separator is None:
|
||||
tokens = [token for token in re.split(r"[-.]", hidden_component) if token]
|
||||
tokens = [token for token in re.split(r"[-._]", hidden_component) if token]
|
||||
else:
|
||||
tokens = [token for token in hidden_component.split(separator) if token]
|
||||
add(["", *tokens])
|
||||
|
|
|
|||
|
|
@ -103,6 +103,38 @@ class TestGreedyPathDecode:
|
|||
result = _greedy_path_decode(tmp_path, ["my", "cool", "project", "nosync", "headroom"])
|
||||
assert result == tmp_path / "my-cool-project.nosync" / "headroom"
|
||||
|
||||
# ---- Underscore tests (issue #159) ----
|
||||
|
||||
def test_single_underscore_in_dirname(self, tmp_path: Path) -> None:
|
||||
"""Directory name contains one literal underscore (e.g. my_project)."""
|
||||
_make_dirs(tmp_path, "my_project")
|
||||
result = _greedy_path_decode(tmp_path, ["my", "project"])
|
||||
assert result == tmp_path / "my_project"
|
||||
|
||||
def test_multiple_underscores_in_dirname(self, tmp_path: Path) -> None:
|
||||
"""Directory name contains multiple underscores (e.g. my_cool_project)."""
|
||||
_make_dirs(tmp_path, "my_cool_project")
|
||||
result = _greedy_path_decode(tmp_path, ["my", "cool", "project"])
|
||||
assert result == tmp_path / "my_cool_project"
|
||||
|
||||
def test_underscore_nested_path(self, tmp_path: Path) -> None:
|
||||
"""Nested path like org/my_project should decode correctly."""
|
||||
_make_dirs(tmp_path, "org/my_project")
|
||||
result = _greedy_path_decode(tmp_path, ["org", "my", "project"])
|
||||
assert result == tmp_path / "org" / "my_project"
|
||||
|
||||
def test_mixed_underscore_and_hyphen_in_dirname(self, tmp_path: Path) -> None:
|
||||
"""Directory with both hyphens and underscores (e.g. my-cool_project)."""
|
||||
_make_dirs(tmp_path, "my-cool_project")
|
||||
result = _greedy_path_decode(tmp_path, ["my", "cool", "project"])
|
||||
assert result == tmp_path / "my-cool_project"
|
||||
|
||||
def test_underscore_dir_containing_hyphen_subdir(self, tmp_path: Path) -> None:
|
||||
"""Path like my_app/sub-module — underscore parent + hyphen child."""
|
||||
_make_dirs(tmp_path, "my_app/sub-module")
|
||||
result = _greedy_path_decode(tmp_path, ["my", "app", "sub", "module"])
|
||||
assert result == tmp_path / "my_app" / "sub-module"
|
||||
|
||||
def test_nonexistent_path_returns_none(self, tmp_path: Path) -> None:
|
||||
result = _greedy_path_decode(tmp_path, ["does", "not", "exist"])
|
||||
assert result is None
|
||||
|
|
@ -236,6 +268,38 @@ class TestDecodeProjectPath:
|
|||
else:
|
||||
assert result is None or result == project
|
||||
|
||||
def test_underscore_dirname_via_greedy(self, users_tmp: Path) -> None:
|
||||
"""my_project — underscore in directory name (issue #159).
|
||||
|
||||
Claude Code encodes /Users/foo/org/my_project as
|
||||
-Users-foo-org-my-project. Simple replace gives
|
||||
…/org/my/project which does not exist, so the greedy decoder
|
||||
must reconstruct my_project from tokens ['my', 'project'].
|
||||
"""
|
||||
project = users_tmp / "org" / "my_project"
|
||||
project.mkdir(parents=True)
|
||||
|
||||
encoded = "-" + str(project)[1:].replace("/", "-")
|
||||
result = _decode_project_path(encoded)
|
||||
|
||||
if str(users_tmp).startswith("/Users/"):
|
||||
assert result == project
|
||||
else:
|
||||
assert result is None or result == project
|
||||
|
||||
def test_multi_underscore_dirname_via_greedy(self, users_tmp: Path) -> None:
|
||||
"""my_cool_project — multiple underscores (issue #159)."""
|
||||
project = users_tmp / "my_cool_project"
|
||||
project.mkdir(parents=True)
|
||||
|
||||
encoded = "-" + str(project)[1:].replace("/", "-")
|
||||
result = _decode_project_path(encoded)
|
||||
|
||||
if str(users_tmp).startswith("/Users/"):
|
||||
assert result == project
|
||||
else:
|
||||
assert result is None or result == project
|
||||
|
||||
def test_windows_drive_letter_pattern(self) -> None:
|
||||
"""Encoded name -C-MQ2-macros should detect Windows drive letter."""
|
||||
import sys
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue