Skip to content

fix: handle dots and multi-hyphen dirnames in _decode_project_path#49

Open
withsivram wants to merge 2 commits intochopratejas:mainfrom
withsivram:fix/dots-in-path-issue-47
Open

fix: handle dots and multi-hyphen dirnames in _decode_project_path#49
withsivram wants to merge 2 commits intochopratejas:mainfrom
withsivram:fix/dots-in-path-issue-47

Conversation

@withsivram
Copy link
Copy Markdown

Summary

  • _greedy_path_decode previously only tried joining two consecutive dash-split tokens, making it impossible to reconstruct directory names with 3+ hyphens (e.g. my-cool-project) or dots with hyphens (e.g. GitHub.nosync, my-project.nosync)
  • Fix: replace the pair-only join with a loop that tries joining 1, 2, 3… consecutive tokens, so all possible hyphen groupings are explored
  • Algorithm remains the same depth-first greedy search validated by filesystem existence checks
  • Adds tests/test_learn/test_scanner.py with 16 test cases covering plain dirs, single-hyphen, multi-hyphen, dot-only, and dot+hyphen combinations

Test plan

  • pytest tests/test_learn/test_scanner.py — all 16 tests pass
  • headroom learn works correctly for projects in directories with dots/hyphens in their names

Fixes #47

🤖 Generated with Claude Code

…ssue chopratejas#47)

_greedy_path_decode previously only tried joining two consecutive
dash-split tokens as a single hyphenated directory component.  This made
it impossible to reconstruct directory names that contained three or more
hyphens (e.g. my-cool-project) or that combined dots with hyphens
(e.g. GitHub.nosync, my-project.nosync).  As a result headroom learn
silently skipped any project whose path passed through such a directory.

Fix: replace the pair-only join with a loop that tries joining 1, 2, 3 …
consecutive tokens as a single path component, so all possible hyphen
groupings are explored.  The algorithm remains the same depth-first
greedy search validated by filesystem existence checks.

Adds tests/test_learn/test_scanner.py with 16 test cases covering plain
dirs, single-hyphen names, multi-hyphen names, dot-only names, and every
combination of dots + hyphens across parent and child components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 20, 2026 16:33
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Claude Code project path decoding so projects aren’t skipped when directory names contain multiple literal hyphens (including cases where a dotted suffix like .nosync is present alongside hyphens), and adds targeted regression tests for the decoder logic (issue #47).

Changes:

  • Update _greedy_path_decode to try merging 1..N consecutive dash-split tokens into a single path component.
  • Expand _decode_project_path docstring to clarify support for dotted and multi-hyphen directory names.
  • Add a new test module with cases covering plain, single-hyphen, multi-hyphen, dot-only, and dot+hyphen combinations.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
headroom/learn/scanner.py Expands greedy decoding to consider all token-merge lengths when reconstructing real filesystem paths.
tests/test_learn/test_scanner.py Adds regression/unit tests for _greedy_path_decode and integration-style tests for _decode_project_path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread headroom/learn/scanner.py
Comment on lines +440 to +447
# Try using 1, 2, 3, … consecutive tokens as a single directory component
# (tokens joined with literal hyphens). The single-token case handles the
# common path-separator interpretation; multi-token cases handle directory
# names that themselves contain hyphens.
for n_tokens in range(1, len(parts) + 1):
component = "-".join(parts[:n_tokens])
candidate = base / component
result = _greedy_path_decode(candidate, parts[n_tokens:])
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_greedy_path_decode explores all token groupings but currently recurses into candidate paths even when the candidate prefix directory doesn't exist. For longer escaped names this can blow up exponentially (and do lots of unnecessary recursion) when the final path doesn't exist. Add a fast pruning check (e.g., skip recursion unless candidate.exists()/candidate.is_dir()), and consider early-returning None when base itself doesn't exist.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_learn/test_scanner.py Outdated

from __future__ import annotations

import tempfile
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempfile is imported but never used in this test module. Please remove the unused import to avoid lint failures and keep the test file minimal.

Suggested change
import tempfile

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +171
import os

home = Path.home()
if str(home).startswith("/Users/"):
base = home / ".pytest_headroom_tmp"
base.mkdir(exist_ok=True)
# Use a sub-directory unique to this test invocation
import uuid

unique = base / uuid.uuid4().hex
unique.mkdir()
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The users_tmp fixture writes into the real home directory and the comment says it will "skip rather than fail" if not writable, but there’s no error handling—mkdir() can raise (e.g., in sandboxed CI or read-only HOME) and fail the suite. Consider wrapping the home-directory creation in try/except OSError and either pytest.skip(...) or fall back to tmp_path; also remove the unused os import in this fixture.

Suggested change
import os
home = Path.home()
if str(home).startswith("/Users/"):
base = home / ".pytest_headroom_tmp"
base.mkdir(exist_ok=True)
# Use a sub-directory unique to this test invocation
import uuid
unique = base / uuid.uuid4().hex
unique.mkdir()
home = Path.home()
if str(home).startswith("/Users/"):
base = home / ".pytest_headroom_tmp"
try:
base.mkdir(exist_ok=True)
except OSError:
pytest.skip("Home directory not writable for users_tmp fixture")
# Use a sub-directory unique to this test invocation
import uuid
unique = base / uuid.uuid4().hex
try:
unique.mkdir()
except OSError:
pytest.skip("Home directory not writable for users_tmp fixture")

Copilot uses AI. Check for mistakes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gglucass
Copy link
Copy Markdown
Contributor

Thanks @withsivram but this didn't work for me. I created my own PR #52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] headroom learn silently skips projects in paths containing dots (e.g. GitHub.nosync)

3 participants