Fix claude-code-history-files-finder: preserve directory structure on recovery

Previously, recover_content.py saved all files flat in the output directory,
causing files with the same name (e.g., src/utils.py and tests/utils.py) to
overwrite each other.

Now the script preserves the original directory structure, creating subdirectories
as needed within the output directory.

- Bump version: 1.0.0 → 1.0.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
daymade
2026-04-05 13:32:54 +08:00
parent cfa702d9db
commit 22de8f043c
2 changed files with 31 additions and 6 deletions

View File

@@ -378,7 +378,7 @@
"description": "Find and recover content from Claude Code session history files. Use when searching for deleted files, tracking changes across sessions, analyzing conversation history, or recovering code/documents from previous Claude interactions. Triggers include mentions of session history, recover deleted, find in history, previous conversation, or .claude/projects", "description": "Find and recover content from Claude Code session history files. Use when searching for deleted files, tracking changes across sessions, analyzing conversation history, or recovering code/documents from previous Claude interactions. Triggers include mentions of session history, recover deleted, find in history, previous conversation, or .claude/projects",
"source": "./", "source": "./",
"strict": false, "strict": false,
"version": "1.0.0", "version": "1.0.1",
"category": "developer-tools", "category": "developer-tools",
"keywords": [ "keywords": [
"session-history", "session-history",

View File

@@ -120,7 +120,7 @@ class SessionContentRecovery:
self, write_calls: List[Dict[str, Any]], keywords: Optional[List[str]] = None self, write_calls: List[Dict[str, Any]], keywords: Optional[List[str]] = None
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
Save recovered files to disk. Save recovered files to disk, preserving original directory structure.
Args: Args:
write_calls: List of Write tool calls write_calls: List of Write tool calls
@@ -152,18 +152,43 @@ class SessionContentRecovery:
# Save files # Save files
for file_path, call in files_by_path.items(): for file_path, call in files_by_path.items():
try: try:
filename = Path(file_path).name if not file_path:
if not filename:
continue continue
output_file = self.output_dir / filename # Preserve original directory structure
# Convert absolute path to relative path within output directory
original_path = Path(file_path)
# Handle absolute paths: extract meaningful relative path
# e.g., /Users/username/project/src/file.py -> src/file.py
# e.g., /home/user/workspace/project/lib/module.py -> lib/module.py
path_parts = original_path.parts
if len(path_parts) > 1 and path_parts[0] == "/":
# For absolute paths, try to find a project-like directory
# Skip leading /, Users/username, home/username patterns
start_idx = 1 # Skip leading "/"
if len(path_parts) > 2 and path_parts[1].lower() in ("users", "home", "user"):
start_idx = 3 # Skip /Users/username or /home/user
relative_parts = path_parts[start_idx:]
else:
relative_parts = path_parts
# Construct output path preserving structure
if relative_parts:
output_file = self.output_dir.joinpath(*relative_parts)
else:
# Fallback to filename only if path is too shallow
output_file = self.output_dir / original_path.name
# Create parent directories
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, "w") as f: with open(output_file, "w") as f:
f.write(call["content"]) f.write(call["content"])
saved.append( saved.append(
{ {
"file": filename, "file": output_file.name,
"original_path": file_path, "original_path": file_path,
"size": len(call["content"]), "size": len(call["content"]),
"lines": call["content"].count("\n") + 1, "lines": call["content"].count("\n") + 1,