diff --git a/plugins/README.md b/plugins/README.md index cf4a21ecc5..dab6b1734b 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -25,6 +25,7 @@ Learn more in the [official plugins documentation](https://docs.claude.com/en/do | [pr-review-toolkit](./pr-review-toolkit/) | Comprehensive PR review agents specializing in comments, tests, error handling, type design, code quality, and code simplification | **Command:** `/pr-review-toolkit:review-pr` - Run with optional review aspects (comments, tests, errors, types, code, simplify, all)
**Agents:** `comment-analyzer`, `pr-test-analyzer`, `silent-failure-hunter`, `type-design-analyzer`, `code-reviewer`, `code-simplifier` | | [ralph-wiggum](./ralph-wiggum/) | Interactive self-referential AI loops for iterative development. Claude works on the same task repeatedly until completion | **Commands:** `/ralph-loop`, `/cancel-ralph` - Start/stop autonomous iteration loops
**Hook:** Stop - Intercepts exit attempts to continue iteration | | [security-guidance](./security-guidance/) | Security reminder hook that warns about potential security issues when editing files | **Hook:** PreToolUse - Monitors 9 security patterns including command injection, XSS, eval usage, dangerous HTML, pickle deserialization, and os.system calls | +| [session-management](./session-management/) | Fork or move conversations to a different working directory, preserving full history | **Commands:** `/fork`, `/move`, `/sessions` - Fork, move, or list conversation sessions across directories | ## Installation diff --git a/plugins/session-management/.claude-plugin/plugin.json b/plugins/session-management/.claude-plugin/plugin.json new file mode 100644 index 0000000000..10f95a8bbd --- /dev/null +++ b/plugins/session-management/.claude-plugin/plugin.json @@ -0,0 +1,9 @@ +{ + "name": "session-management", + "description": "Fork or move Claude Code conversations to a different working directory, preserving full conversation history", + "version": "1.0.0", + "author": { + "name": "Mostagir Bhuiyan", + "email": "mmostagirbhuiyan@users.noreply.github.com" + } +} diff --git a/plugins/session-management/README.md b/plugins/session-management/README.md new file mode 100644 index 0000000000..67561da99e --- /dev/null +++ b/plugins/session-management/README.md @@ -0,0 +1,99 @@ +# Session Management Plugin + +Fork or move Claude Code conversations to a different working directory, preserving full conversation history and context. + +## Overview + +When you reorganize your project files — moving a folder from one location to another — your Claude Code conversation history stays tied to the original directory. This plugin lets you fork or move conversations so you don't lose valuable context like design decisions, debugging history, and iterative refinements. + +## Commands + +### `/fork` — Fork a conversation to a new directory + +Creates a copy of the current conversation in a different working directory. The original stays intact, and you get a full copy with all history preserved. + +``` +/fork ~/Documents/new-project-location +``` + +**What it does:** +1. Finds the current session's conversation file +2. Copies it (and any companion data) to the target directory's storage +3. Updates internal directory references so Claude associates it with the new location +4. Both original and fork can continue independently + +### `/move` — Move a conversation to a new directory + +Moves the current conversation to a different working directory, removing it from the original location. + +``` +/move ~/Documents/new-project-location +``` + +**What it does:** +1. Finds the current session's conversation file +2. Moves it (and any companion data) to the target directory's storage +3. Updates internal directory references +4. The original is removed — only the moved copy remains + +### `/sessions` — List conversation sessions + +Shows all stored sessions for the current (or specified) directory with timestamps, sizes, and message previews. + +``` +/sessions +/sessions ~/Documents/other-project +``` + +## Usage Examples + +### Project reorganization + +You've been working in `~/Desktop/my-app/` and want to move it to `~/Projects/my-app/`: + +```bash +# After moving your project files +mv ~/Desktop/my-app ~/Projects/my-app + +# Fork the conversation to the new location +/fork ~/Projects/my-app + +# End this session, then: +cd ~/Projects/my-app +claude --resume +``` + +### Working on the same project from multiple locations + +``` +/fork ~/Projects/my-app-v2 +``` + +Now both directories have the full conversation history, and you can continue independently in each. + +### Checking available sessions + +``` +/sessions +``` + +## How It Works + +Claude Code stores conversations as JSONL files in `~/.claude/projects/`, organized by the working directory path (with `/` replaced by `-`). This plugin: + +1. **Locates** the conversation file for the current session +2. **Copies/moves** it to the target directory's project storage +3. **Updates** the `cwd` references inside the conversation so Claude correctly associates it with the new directory +4. **Preserves** companion data (subagent logs, cached tool results) + +## Requirements + +- Claude Code installed +- `sed`, `cp`, `mv` (standard Unix tools) +- Python 3 (for session preview in `/sessions`) + +## Limitations + +- The current running session remains bound to its original directory — after forking/moving, you need to start a new session from the target directory using `claude --resume` +- File history backups (`~/.claude/file-history/`) are not moved (they reference session UUIDs, not directories) +- Memory files (auto-memory in `~/.claude/projects/`) are directory-specific and not copied — consider manually copying relevant memory if needed diff --git a/plugins/session-management/commands/fork.md b/plugins/session-management/commands/fork.md new file mode 100644 index 0000000000..1119d42c9b --- /dev/null +++ b/plugins/session-management/commands/fork.md @@ -0,0 +1,159 @@ +--- +description: Fork the current conversation to a different working directory +argument-hint: /path/to/new/directory +allowed-tools: ["Bash(*)", "Read", "AskUserQuestion"] +--- + +# Fork Conversation to New Directory + +Fork (copy) the current conversation to a different working directory so you can continue it there with full history preserved. + +## Your Task + +### Step 1: Validate Target Directory + +The user wants to fork this conversation to: `$ARGUMENTS` + +**If $ARGUMENTS is empty:** +Use AskUserQuestion to ask: +- question: "Where would you like to fork this conversation to?" +- description: "Enter the absolute path to the target directory (e.g., ~/Documents/my-project)" + +Once you have the target path: + +1. Expand the path safely (resolve `~` without eval) and validate it is absolute: + ```bash + target_dir="$ARGUMENTS" + # Safe tilde expansion without eval + case "$target_dir" in + ~/*) target_dir="$HOME/${target_dir#\~/}" ;; + ~) target_dir="$HOME" ;; + esac + # Resolve to canonical absolute path + target_dir="$(cd "$target_dir" 2>/dev/null && pwd -P)" || { echo "ERROR: Cannot resolve path: $ARGUMENTS"; exit 1; } + # Verify it is absolute + case "$target_dir" in + /*) echo "Target directory: $target_dir" ;; + *) echo "ERROR: Target must be an absolute path. Got: $target_dir"; exit 1 ;; + esac + ``` +2. Verify the target directory exists: + ```bash + test -d "$target_dir" && echo "Directory exists" || echo "Directory does not exist" + ``` +3. If it does not exist, ask the user if they want to create it. If yes, create it with `mkdir -p`. + +### Step 2: Identify Current Session + +Get the current session ID and source project directory: + +```bash +# Get the current working directory encoded as Claude stores it +cwd="$(pwd -P)" +encoded_cwd=$(printf '%s' "$cwd" | sed 's|/|-|g') +project_dir="$HOME/.claude/projects/$encoded_cwd" +echo "Source project dir: $project_dir" +ls -lt "$project_dir"/*.jsonl 2>/dev/null | head -5 +``` + +The most recently modified `.jsonl` file is the current (or most recent) conversation session. + +### Step 3: Identify Target Project Directory + +```bash +# Encode the target directory path the same way Claude does +encoded_target=$(printf '%s' "$target_dir" | sed 's|/|-|g') +target_project_dir="$HOME/.claude/projects/$encoded_target" +echo "Target project dir: $target_project_dir" +``` + +### Step 4: Show Summary and Confirm + +Before copying, show the user a summary: + +- Source directory: `{cwd}` +- Target directory: `{target_dir}` +- Session file to copy: `{session_file}` +- File size: `{size}` + +Check if the file is large and warn if needed: +```bash +size_bytes=$(stat -f "%z" "$project_dir/$session_file" 2>/dev/null || stat -c "%s" "$project_dir/$session_file" 2>/dev/null) +if [ "$size_bytes" -gt 10485760 ]; then + echo "Note: This conversation file is large ($(du -h "$project_dir/$session_file" | cut -f1)). The copy may take a moment." +fi +``` + +Use AskUserQuestion to confirm: +- question: "Ready to fork this conversation?" +- description: "This will copy the conversation to the target directory. The original stays intact. You can resume it by running `claude --resume` from the new directory." + +### Step 5: Fork the Conversation + +Execute the fork with error handling: + +```bash +# Create the target project directory if it doesn't exist +mkdir -p "$target_project_dir" || { echo "ERROR: Failed to create target directory"; exit 1; } + +# Copy the session JSONL file +cp "$project_dir/$session_file" "$target_project_dir/$session_file" || { echo "ERROR: Failed to copy session file"; exit 1; } + +# Copy the session companion directory if it exists (contains subagent data, tool results) +if [ -d "$project_dir/${session_file%.jsonl}" ]; then + cp -r "$project_dir/${session_file%.jsonl}" "$target_project_dir/${session_file%.jsonl}" || { echo "ERROR: Failed to copy companion directory"; exit 1; } +fi + +echo "Fork complete!" +``` + +### Step 6: Update CWD References in Forked Conversation + +After copying, update the `cwd` fields in the forked conversation so Claude associates it with the new directory. Use Python for safe string replacement (avoids sed injection with special characters in paths): + +```bash +python3 -c " +import sys +filepath, old_cwd, new_cwd = sys.argv[1], sys.argv[2], sys.argv[3] +with open(filepath, 'r') as f: + data = f.read() +data = data.replace('\"cwd\":\"' + old_cwd + '\"', '\"cwd\":\"' + new_cwd + '\"') +with open(filepath, 'w') as f: + f.write(data) +print('Updated cwd references in forked conversation') +" "$target_project_dir/$session_file" "$cwd" "$target_dir" || { echo "ERROR: Failed to update cwd references"; exit 1; } +``` + +### Step 7: Check for Memory Files + +```bash +if [ -f "$project_dir/CLAUDE.md" ]; then + echo "" + echo "Note: The source directory has a CLAUDE.md memory file." + echo "This was NOT copied. To copy it manually:" + echo " cp \"$project_dir/CLAUDE.md\" \"$target_project_dir/CLAUDE.md\"" +fi +if [ -d "$project_dir/memory" ]; then + echo "" + echo "Note: The source directory has a memory/ folder." + echo "This was NOT copied. To copy it manually:" + echo " cp -r \"$project_dir/memory\" \"$target_project_dir/memory\"" +fi +``` + +### Step 8: Confirm Success + +Tell the user: + +**Conversation forked successfully!** + +- Original: `{cwd}` (unchanged) +- Fork: `{target_dir}` + +To resume the forked conversation: +``` +cd {target_dir} +claude --resume +``` + +The forked conversation has full history preserved. Both the original and the fork can continue independently. diff --git a/plugins/session-management/commands/move.md b/plugins/session-management/commands/move.md new file mode 100644 index 0000000000..26127e4222 --- /dev/null +++ b/plugins/session-management/commands/move.md @@ -0,0 +1,174 @@ +--- +description: Move the current conversation to a different working directory +argument-hint: /path/to/new/directory +allowed-tools: ["Bash(*)", "Read", "AskUserQuestion"] +--- + +# Move Conversation to New Directory + +Move the current conversation to a different working directory. This is useful when you've reorganized your project files and want to continue the conversation from the new location. + +## Your Task + +### Step 1: Validate Target Directory + +The user wants to move this conversation to: `$ARGUMENTS` + +**If $ARGUMENTS is empty:** +Use AskUserQuestion to ask: +- question: "Where would you like to move this conversation to?" +- description: "Enter the absolute path to the target directory (e.g., ~/Documents/my-project)" + +Once you have the target path: + +1. Expand the path safely (resolve `~` without eval) and validate it is absolute: + ```bash + target_dir="$ARGUMENTS" + # Safe tilde expansion without eval + case "$target_dir" in + ~/*) target_dir="$HOME/${target_dir#\~/}" ;; + ~) target_dir="$HOME" ;; + esac + # Resolve to canonical absolute path + target_dir="$(cd "$target_dir" 2>/dev/null && pwd -P)" || { echo "ERROR: Cannot resolve path: $ARGUMENTS"; exit 1; } + # Verify it is absolute + case "$target_dir" in + /*) echo "Target directory: $target_dir" ;; + *) echo "ERROR: Target must be an absolute path. Got: $target_dir"; exit 1 ;; + esac + ``` +2. Verify the target directory exists: + ```bash + test -d "$target_dir" && echo "Directory exists" || echo "Directory does not exist" + ``` +3. If it does not exist, ask the user if they want to create it. If yes, create it with `mkdir -p`. + +### Step 2: Identify Current Session + +Get the current session ID and source project directory: + +```bash +# Get the current working directory encoded as Claude stores it +cwd="$(pwd -P)" +encoded_cwd=$(printf '%s' "$cwd" | sed 's|/|-|g') +project_dir="$HOME/.claude/projects/$encoded_cwd" +echo "Source project dir: $project_dir" +ls -lt "$project_dir"/*.jsonl 2>/dev/null | head -5 +``` + +The most recently modified `.jsonl` file is the current (or most recent) conversation session. + +### Step 3: Identify Target Project Directory + +```bash +# Encode the target directory path the same way Claude does +encoded_target=$(printf '%s' "$target_dir" | sed 's|/|-|g') +target_project_dir="$HOME/.claude/projects/$encoded_target" +echo "Target project dir: $target_project_dir" +``` + +### Step 4: Check for Conflicts + +```bash +# Check if target already has conversations +if ls "$target_project_dir"/*.jsonl 2>/dev/null | head -1 > /dev/null 2>&1; then + echo "WARNING: Target directory already has existing conversations" + ls -lt "$target_project_dir"/*.jsonl | head -5 +else + echo "No existing conversations in target directory" +fi +``` + +### Step 5: Show Summary and Confirm + +Before moving, show the user a summary: + +- Source directory: `{cwd}` +- Target directory: `{target_dir}` +- Session file to move: `{session_file}` +- File size: `{size}` +- **Note**: The original conversation will be removed from the source directory + +Check if the file is large and warn if needed: +```bash +size_bytes=$(stat -f "%z" "$project_dir/$session_file" 2>/dev/null || stat -c "%s" "$project_dir/$session_file" 2>/dev/null) +if [ "$size_bytes" -gt 10485760 ]; then + echo "Note: This conversation file is large ($(du -h "$project_dir/$session_file" | cut -f1)). The move may take a moment." +fi +``` + +Use AskUserQuestion to confirm: +- question: "Ready to move this conversation? The original will be removed." +- description: "This moves the conversation to the target directory. You can resume it by running `claude --resume` from the new directory." + +### Step 6: Move the Conversation + +Execute the move with error handling: + +```bash +# Create the target project directory if it doesn't exist +mkdir -p "$target_project_dir" || { echo "ERROR: Failed to create target directory"; exit 1; } + +# Move the session JSONL file +mv "$project_dir/$session_file" "$target_project_dir/$session_file" || { echo "ERROR: Failed to move session file"; exit 1; } + +# Move the session companion directory if it exists (contains subagent data, tool results) +if [ -d "$project_dir/${session_file%.jsonl}" ]; then + mv "$project_dir/${session_file%.jsonl}" "$target_project_dir/${session_file%.jsonl}" || { echo "ERROR: Failed to move companion directory"; exit 1; } +fi + +echo "Move complete!" +``` + +### Step 7: Update CWD References in Moved Conversation + +After moving, update the `cwd` fields so Claude associates it with the new directory. Use Python for safe string replacement (avoids sed injection with special characters in paths): + +```bash +python3 -c " +import sys +filepath, old_cwd, new_cwd = sys.argv[1], sys.argv[2], sys.argv[3] +with open(filepath, 'r') as f: + data = f.read() +data = data.replace('\"cwd\":\"' + old_cwd + '\"', '\"cwd\":\"' + new_cwd + '\"') +with open(filepath, 'w') as f: + f.write(data) +print('Updated cwd references in moved conversation') +" "$target_project_dir/$session_file" "$cwd" "$target_dir" || { echo "ERROR: Failed to update cwd references"; exit 1; } +``` + +### Step 8: Check for Memory Files + +```bash +if [ -f "$project_dir/CLAUDE.md" ]; then + echo "" + echo "Note: The source directory has a CLAUDE.md memory file." + echo "This was NOT moved. To move it manually:" + echo " mv \"$project_dir/CLAUDE.md\" \"$target_project_dir/CLAUDE.md\"" +fi +if [ -d "$project_dir/memory" ]; then + echo "" + echo "Note: The source directory has a memory/ folder." + echo "This was NOT moved. To move it manually:" + echo " mv \"$project_dir/memory\" \"$target_project_dir/memory\"" +fi +``` + +### Step 9: Confirm Success + +Tell the user: + +**Conversation moved successfully!** + +- From: `{cwd}` +- To: `{target_dir}` + +To resume the moved conversation: +``` +cd {target_dir} +claude --resume +``` + +The conversation retains full history. The original has been removed from the source directory. + +**Important**: This current session is still running from the old directory. After this session ends, use the command above to continue from the new location. diff --git a/plugins/session-management/commands/sessions.md b/plugins/session-management/commands/sessions.md new file mode 100644 index 0000000000..f7d0cc79a7 --- /dev/null +++ b/plugins/session-management/commands/sessions.md @@ -0,0 +1,75 @@ +--- +description: List all conversation sessions for the current or specified directory +argument-hint: Optional /path/to/directory +allowed-tools: ["Bash(ls:*)", "Bash(du:*)", "Bash(stat:*)", "Bash(basename:*)", "Bash(grep:*)", "Bash(python3:*)", "Bash(test:*)", "Bash(wc:*)"] +--- + +# List Conversation Sessions + +Show all conversation sessions stored for a working directory. + +## Your Task + +### Step 1: Determine Target Directory + +```bash +if [ -n "$ARGUMENTS" ]; then + target_dir="$ARGUMENTS" + # Safe tilde expansion without eval + case "$target_dir" in + ~/*) target_dir="$HOME/${target_dir#\~/}" ;; + ~) target_dir="$HOME" ;; + esac + # Resolve to canonical absolute path + target_dir="$(cd "$target_dir" 2>/dev/null && pwd -P)" || { echo "ERROR: Cannot resolve path: $ARGUMENTS"; exit 1; } +else + target_dir="$(pwd -P)" +fi +encoded_dir=$(printf '%s' "$target_dir" | sed 's|/|-|g') +project_dir="$HOME/.claude/projects/$encoded_dir" +echo "Looking for sessions in: $target_dir" +echo "Storage path: $project_dir" +``` + +### Step 2: List Sessions + +```bash +if [ -d "$project_dir" ]; then + echo "" + echo "Sessions (most recent first):" + echo "==============================" + ls -t "$project_dir"/*.jsonl 2>/dev/null | while IFS= read -r f; do + [ -z "$f" ] && continue + session_id=$(basename "$f" .jsonl) + size=$(du -h "$f" | cut -f1) + modified=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$f" 2>/dev/null || (stat -c "%y" "$f" 2>/dev/null | cut -d. -f1)) + # Get the first user message as a preview + preview=$(grep -m1 '"type":"user"' "$f" 2>/dev/null | python3 -c " +import sys, json +try: + d = json.loads(sys.stdin.readline()) + c = d.get('message', {}).get('content', '') + print((c[:80] if isinstance(c, str) else str(c)[:80])) +except Exception: + print('(no preview)') +" 2>/dev/null || echo "(no preview)") + echo "" + echo " ID: $session_id" + echo " Modified: $modified" + echo " Size: $size" + echo " Preview: $preview" + done +else + echo "No sessions found for this directory." + echo "" + echo "Available project directories:" + ls -dt "$HOME/.claude/projects"/*/ 2>/dev/null | head -10 | while IFS= read -r dir; do + dirname=$(basename "$dir") + decoded=$(printf '%s' "$dirname" | sed 's|-|/|g') + count=$(ls "$dir"/*.jsonl 2>/dev/null | wc -l | tr -d ' ') + echo " $decoded ($count sessions)" + done +fi +``` + +Present the results in a clean, readable format. If no sessions are found, suggest the user check the directory path.