diff --git a/mempalace/hooks_cli.py b/mempalace/hooks_cli.py index 4672e85..01eca3f 100644 --- a/mempalace/hooks_cli.py +++ b/mempalace/hooks_cli.py @@ -490,14 +490,29 @@ def _parse_harness_input(data: dict, harness: str) -> dict: def _wing_from_transcript_path(transcript_path: str) -> str: """Derive a project wing name from a Claude Code transcript path. - Claude Code stores transcripts at: + Claude Code encodes the project's source directory by replacing path + separators with dashes, producing folders like: ~/.claude/projects/-home--Projects-/session.jsonl - We extract and return ``wing_`` to match the - AAAK_SPEC convention (``wing_user``, ``wing_agent``, ``wing_code``, - ``wing_``…). Falls back to ``wing_sessions``. + ~/.claude/projects/-home--dev--/session.jsonl + ~/.claude/projects/-Users---/session.jsonl + + The project directory name is the final dash-separated token of the + encoded folder. Returns ``wing_`` (lowercased, spaces → ``_``). + Falls back to ``wing_sessions`` if the path does not match a Claude Code + project-folder layout. """ # Normalize path separators for cross-platform (Windows backslashes) normalized = transcript_path.replace("\\", "/") + # Primary: pull the encoded project folder out of ``.claude/projects/`` + # and take its last dash-separated token. + match = re.search(r"/\.claude/projects/-([^/]+)", normalized) + if match: + encoded = match.group(1) + project = encoded.rsplit("-", 1)[-1] + if project: + return f"wing_{project.lower().replace(' ', '_')}" + # Legacy fallback: explicit ``-Projects-`` segment, useful for + # transcripts not under the standard Claude Code projects dir. match = re.search(r"-Projects-([^/]+?)(?:/|$)", normalized) if match: project = match.group(1).lower().replace(" ", "_") diff --git a/tests/test_hooks_cli.py b/tests/test_hooks_cli.py index a5af129..c9a0022 100644 --- a/tests/test_hooks_cli.py +++ b/tests/test_hooks_cli.py @@ -324,6 +324,24 @@ def test_wing_from_transcript_path_lowercases(): assert _wing_from_transcript_path(path) == "wing_myproject" +def test_wing_from_transcript_path_non_projects_layout(): + # Linux users with code under ~/dev/, ~/src/, ~/code/ — no -Projects- segment. + # Project name is the final dash-separated token of the encoded folder. + path = "/home/igor/.claude/projects/-home-igor-dev-MemPalace-mempalace/session.jsonl" + assert _wing_from_transcript_path(path) == "wing_mempalace" + + +def test_wing_from_transcript_path_macos_users_layout(): + # macOS ~/ layout without a Projects/ segment. + path = "/Users/alice/.claude/projects/-Users-alice-code-MyApp/session.jsonl" + assert _wing_from_transcript_path(path) == "wing_myapp" + + +def test_wing_from_transcript_path_nested_deep(): + path = "/home/bob/.claude/projects/-home-bob-work-clients-acme-frontend/session.jsonl" + assert _wing_from_transcript_path(path) == "wing_frontend" + + # --- _log ---