#!/bin/bash # install.sh — Link claude-meta config into ~/.claude/ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(dirname "$SCRIPT_DIR")" SOURCE_DIR="$REPO_ROOT/home-claude" CLAUDE_DIR="$HOME/.claude" # Verify source exists if [ ! -d "$SOURCE_DIR" ]; then echo "ERROR: home-claude/ not found at $SOURCE_DIR" exit 1 fi # Ensure ~/.claude/ is a real directory if [ -L "$CLAUDE_DIR" ]; then echo "ERROR: ~/.claude/ is a symlink. It must be a real directory." echo "Claude Code Bug #764: symlinked ~/.claude/ breaks file detection." exit 1 fi mkdir -p "$CLAUDE_DIR" echo "Installing claude-meta config..." echo " Source: $SOURCE_DIR" echo " Target: $CLAUDE_DIR" echo "" # File symlinks for file in CLAUDE.md settings.json; do src="$SOURCE_DIR/$file" dst="$CLAUDE_DIR/$file" [ ! -f "$src" ] && echo " SKIP: $file" && continue # Remove existing [ -e "$dst" ] || [ -L "$dst" ] && rm -f "$dst" ln -s "$src" "$dst" echo " SYMLINK: $file -> $src" done # Directory symlinks for dir in skills rules commands; do src="$SOURCE_DIR/$dir" dst="$CLAUDE_DIR/$dir" [ ! -d "$src" ] && echo " SKIP: $dir/" && continue # Remove existing if [ -L "$dst" ]; then rm "$dst" elif [ -d "$dst" ]; then rm -rf "$dst" fi ln -s "$src" "$dst" echo " SYMLINK: $dir/ -> $src" done # Validation echo "" echo "Validating..." for file in CLAUDE.md settings.json; do dst="$CLAUDE_DIR/$file" if [ -f "$dst" ] && [ -s "$dst" ]; then echo " OK: $file readable" else echo " FAIL: $file" fi done for dir in skills rules commands; do dst="$CLAUDE_DIR/$dir" if [ -d "$dst" ]; then count=$(find "$dst" -type f | wc -l) echo " OK: $dir/ ($count files)" else echo " FAIL: $dir/" fi done echo "" echo "Installing Gemini config..." GEMINI_SOURCE_DIR="$REPO_ROOT/home-gemini" GEMINI_DIR="$HOME/.gemini" CLAUDE_RULES_DIR="$SOURCE_DIR/rules" if [ ! -d "$GEMINI_SOURCE_DIR" ]; then echo " SKIP: home-gemini/ not found" else echo " Source: $GEMINI_SOURCE_DIR" echo " Target: $GEMINI_DIR" echo "" # Ensure ~/.gemini/ is a real directory if [ -L "$GEMINI_DIR" ]; then echo " ERROR: ~/.gemini/ is a symlink. Must be a real directory." exit 1 fi mkdir -p "$GEMINI_DIR" # GEMINI.md — always copy (no symlink, Gemini CLI issue #11547) src="$GEMINI_SOURCE_DIR/GEMINI.md" dst="$GEMINI_DIR/GEMINI.md" if [ -f "$src" ]; then cp -f "$src" "$dst" echo " COPY: GEMINI.md (symlinks not supported by Gemini CLI)" fi # settings.json — symlink src="$GEMINI_SOURCE_DIR/settings.json" dst="$GEMINI_DIR/settings.json" if [ -f "$src" ]; then [ -e "$dst" ] || [ -L "$dst" ] && rm -f "$dst" ln -s "$src" "$dst" echo " SYMLINK: settings.json -> $src" fi # rules/ — symlink to home-claude/rules/ (shared source of truth) dst="$GEMINI_DIR/rules" if [ -d "$CLAUDE_RULES_DIR" ]; then [ -L "$dst" ] && rm "$dst" || { [ -d "$dst" ] && rm -rf "$dst"; } ln -s "$CLAUDE_RULES_DIR" "$dst" echo " SYMLINK: rules/ -> $CLAUDE_RULES_DIR" fi # skills/ — selective symlinks for review/ and plan-project/ mkdir -p "$GEMINI_DIR/skills" for skill in review plan-project; do skill_src="$SOURCE_DIR/skills/$skill" skill_dst="$GEMINI_DIR/skills/$skill" if [ ! -d "$skill_src" ]; then echo " SKIP: skills/$skill/ (not in repo)" continue fi [ -L "$skill_dst" ] && rm "$skill_dst" || { [ -d "$skill_dst" ] && rm -rf "$skill_dst"; } ln -s "$skill_src" "$skill_dst" echo " SYMLINK: skills/$skill/ -> $skill_src" done echo "" echo "Validating Gemini..." dst="$GEMINI_DIR/GEMINI.md" if [ -f "$dst" ] && [ ! -L "$dst" ] && [ -s "$dst" ]; then echo " OK: ~/.gemini/GEMINI.md (real file)" elif [ -L "$dst" ]; then echo " FAIL: ~/.gemini/GEMINI.md is a symlink (Gemini CLI rejects this!)" else echo " FAIL: ~/.gemini/GEMINI.md" fi dst="$GEMINI_DIR/rules" if [ -d "$dst" ]; then count=$(find "$dst" -type f | wc -l) echo " OK: ~/.gemini/rules/ ($count files)" else echo " FAIL: ~/.gemini/rules/" fi fi # ============================================================ # PROJECT MEMORY SYMLINKS # ============================================================ PROJECT_MAP="$REPO_ROOT/memory/projects/project-map.json" PROJECTS_DIR="$CLAUDE_DIR/projects" echo "" echo "Installing project memory symlinks..." if [ -f "$PROJECT_MAP" ] && command -v python3 &>/dev/null; then python3 -c " import json, sys with open('$PROJECT_MAP') as f: data = json.load(f) for k, v in data.items(): print(f'{k} {v}') " | while read encoded friendly; do memory_src="$REPO_ROOT/memory/projects/$friendly" project_dir="$PROJECTS_DIR/$encoded" memory_dst="$project_dir/memory" [ ! -d "$project_dir" ] && echo " SKIP: $encoded (project dir not found)" && continue [ ! -d "$memory_src" ] && echo " SKIP: $friendly (repo dir not found)" && continue if [ -L "$memory_dst" ]; then echo " OK: $friendly (already linked)" continue fi # Move existing memory files to repo before replacing if [ -d "$memory_dst" ]; then for f in "$memory_dst"/*; do [ -f "$f" ] || continue base=$(basename "$f") [ ! -f "$memory_src/$base" ] && mv "$f" "$memory_src/" && echo " MOVED: $base -> $memory_src" done rm -rf "$memory_dst" fi ln -s "$memory_src" "$memory_dst" echo " SYMLINK: $friendly -> $memory_src" done elif [ -f "$PROJECT_MAP" ]; then echo " SKIP: python3 not found (needed to parse JSON)" fi # Project memory validation if [ -f "$PROJECT_MAP" ] && command -v python3 &>/dev/null; then echo "" echo "Validating project memory..." python3 -c " import json with open('$PROJECT_MAP') as f: data = json.load(f) for k, v in data.items(): print(f'{k} {v}') " | while read encoded friendly; do memory_dst="$PROJECTS_DIR/$encoded/memory" if [ -L "$memory_dst" ]; then echo " OK: $friendly memory/ (symlink)" elif [ -d "$memory_dst" ]; then echo " OK: $friendly memory/ (copy)" fi done fi echo "" echo "Installation complete!"