Skip to content

Instantly share code, notes, and snippets.

@eevmanu
Created August 4, 2026 21:25
Show Gist options
  • Select an option

  • Save eevmanu/5c847203a283a1843af90185c3b4cec2 to your computer and use it in GitHub Desktop.

Select an option

Save eevmanu/5c847203a283a1843af90185c3b4cec2 to your computer and use it in GitHub Desktop.
Handoff & Technical Design Specification: Claude Memory Search Script
#!/usr/bin/env bash
# ==============================================================================
# claude_mem_search.sh
# ==============================================================================
# Dynamic interactive search across Claude memory markdown files using fzf + ripgrep.
#
# Features:
# 1. Automatic discovery of memory directories (~/.claude/**/memory/).
# 2. Dynamic ripgrep search triggered only when query length >= 3 characters.
# 3. Instant clean screen when query length < 3 characters.
# 4. Rich preview pane with syntax highlighting (`bat` with line highlighting).
# 5. Extensible output / selection hook (defaults to printing to stdout).
#
# Usage:
# ./claude_mem_search.sh # Launch interactive fzf search
# ./claude_mem_search.sh --help # Show usage & configuration
# ==============================================================================
# Customize minimum query length to 2 characters
# ./claude_mem_search.sh --min-len 2
#
# Enable regex search mode instead of literal fixed-strings
# ./claude_mem_search.sh --mode regex
#
# See usage options
# ./claude_mem_search.sh --help
# ==============================================================================
set -euo pipefail
# ------------------------------------------------------------------------------
# Configuration & Defaults
# ------------------------------------------------------------------------------
SCRIPT_PATH="$(readlink -f "$0")"
SEARCH_MIN_LEN="${SEARCH_MIN_LEN:-3}" # Minimum query length to start search
CLAUDE_DIR="${CLAUDE_DIR:-"$HOME/.claude"}" # Target base directory
SEARCH_MODE="${SEARCH_MODE:-fixed}" # 'fixed' (literal) or 'regex'
CASE_MODE="${CASE_MODE:-smart}" # 'smart', 'ignore', or 'sensitive'
# ------------------------------------------------------------------------------
# Helper Functions
# ------------------------------------------------------------------------------
# Display usage help
show_help() {
cat << EOF
Usage: $(basename "$0") [OPTIONS]
Interactive search across Claude memory markdown files (~/.claude/**/memory/*.md).
Options:
-m, --min-len NUM Minimum characters required to trigger search (default: $SEARCH_MIN_LEN)
--mode MODE Search mode: 'fixed' (literal text, default) or 'regex'
--case CASE Case sensitivity: 'smart' (default), 'ignore', or 'sensitive'
-h, --help Display this help message and exit
Environment Variables:
SEARCH_MIN_LEN Default minimum query length ($SEARCH_MIN_LEN)
CLAUDE_DIR Target Claude directory ($CLAUDE_DIR)
EDITOR / VISUAL Default editor if extending selection action
EOF
}
# Discover memory directories under ~/.claude
discover_memory_dirs() {
if [[ ! -d "$CLAUDE_DIR" ]]; then
echo "Error: Directory '$CLAUDE_DIR' does not exist." >&2
return 1
fi
mapfile -t MEM_DIRS < <(fd --hidden --no-ignore --ignore-case --type=directory memory "$CLAUDE_DIR" 2>/dev/null || true)
if [[ ${#MEM_DIRS[@]} -eq 0 ]]; then
echo "Error: No 'memory' directories found under '$CLAUDE_DIR'." >&2
return 1
fi
}
# Perform ripgrep search across discovered memory directories
search_memories() {
local query="$1"
# Stop search and clear list if query length is less than SEARCH_MIN_LEN
if [[ ${#query} -lt "$SEARCH_MIN_LEN" ]]; then
return 0
fi
# Build ripgrep options
local rg_opts=(
-g '*.md'
--line-number
--column
--max-columns 150
--max-columns-preview
--color=always
)
# Case sensitivity flags
case "$CASE_MODE" in
ignore) rg_opts+=(--ignore-case) ;;
sensitive) rg_opts+=(--case-sensitive) ;;
smart|*) rg_opts+=(--smart-case) ;;
esac
# Search mode flags
if [[ "$SEARCH_MODE" == "fixed" ]]; then
rg_opts+=(--fixed-strings)
fi
# Execute ripgrep across discovered memory directories
rg "${rg_opts[@]}" -- "$query" "${MEM_DIRS[@]}" 2>/dev/null || true
}
# Render file preview in fzf
preview_match() {
local file_path raw_line
# Strip ANSI color codes if present
file_path=$(echo "${1:-}" | sed -r 's/\x1b\[[0-9;]*[a-zA-Z]//g')
raw_line=$(echo "${2:-1}" | sed -r 's/\x1b\[[0-9;]*[a-zA-Z]//g')
local line_number="${raw_line:-1}"
if [[ -z "$file_path" || ! -f "$file_path" ]]; then
echo "No file selected."
return 0
fi
# Use `bat` for syntax-highlighted preview if available
if command -v bat &>/dev/null; then
local start_line=$(( line_number > 10 ? line_number - 10 : 1 ))
local end_line=$(( line_number + 20 ))
bat --style=full \
--color=always \
--highlight-line "$line_number" \
--line-range "${start_line}:${end_line}" \
"$file_path"
else
# Fallback to cat with line context
echo "=== $file_path (Line $line_number) ==="
sed -n "$(( line_number > 10 ? line_number - 10 : 1 )),$(( line_number + 20 ))p" "$file_path"
fi
}
# Selection action hook: executed when user selects a match in fzf and presses Enter
on_select_match() {
local selection="${1:-}"
if [[ -z "$selection" ]]; then
return 0
fi
# DEFAULT ACTION: Print selected line to STDOUT
echo "$selection"
# --------------------------------------------------------------------------
# CUSTOMIZATION HOOK:
# To open the selected match directly in an editor, uncomment one of below:
# --------------------------------------------------------------------------
# IFS=':' read -r file line col _ <<< "$selection"
# ${EDITOR:-nvim} "+${line}" "$file"
# code -g "${file}:${line}:${col}"
}
# Main interactive fzf loop
run_interactive_fzf() {
discover_memory_dirs
# Export functions and variables for fzf callbacks
export SCRIPT_PATH CLAUDE_DIR SEARCH_MIN_LEN SEARCH_MODE CASE_MODE
export -f discover_memory_dirs search_memories preview_match
export MEM_DIRS_STR="${MEM_DIRS[*]}"
# Launch fzf in dynamic mode
local selected
selected=$(fzf \
--disabled \
--ansi \
--delimiter : \
--header "🔍 Minimum $SEARCH_MIN_LEN characters required to search. [Esc: Exit | Enter: Select]" \
--prompt "Claude Memory Search > " \
--pointer "▶ " \
--bind "start:reload:$SCRIPT_PATH --internal-search {q}" \
--bind "change:reload:$SCRIPT_PATH --internal-search {q}" \
--preview "$SCRIPT_PATH --internal-preview {1} {2}" \
--preview-window "right:55%:wrap" \
--color "hl:green,hl+:green:bold,fg+:white:bold,bg+:black" \
--layout=reverse \
|| true)
if [[ -n "$selected" ]]; then
on_select_match "$selected"
fi
}
# ------------------------------------------------------------------------------
# Internal Callbacks & Subcommand Dispatcher
# ------------------------------------------------------------------------------
# Callback for fzf --bind change:reload
if [[ "${1:-}" == "--internal-search" ]]; then
shift
discover_memory_dirs
search_memories "${1:-}"
exit 0
fi
# Callback for fzf --preview
if [[ "${1:-}" == "--internal-preview" ]]; then
shift
preview_match "${1:-}" "${2:-1}"
exit 0
fi
# ------------------------------------------------------------------------------
# CLI Argument Parsing
# ------------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--min-len)
SEARCH_MIN_LEN="$2"
shift 2
;;
--mode)
SEARCH_MODE="$2"
shift 2
;;
--case)
CASE_MODE="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "Unknown option: $1" >&2
show_help
exit 1
;;
esac
done
# ------------------------------------------------------------------------------
# Entry Point
# ------------------------------------------------------------------------------
run_interactive_fzf

Handoff & Technical Design Specification: Claude Memory Search Script

Overview & Goal

This document provides the complete technical design specification to rebuild claude_mem_search.sh from scratch. The script is an interactive command-line utility that allows searching across Claude memory markdown files (~/.claude/**/memory/*.md) using fzf, ripgrep (rg), and fd, with rich preview provided by bat.


Workspace & Artifact References

  • Script Location: claude_mem_search.sh
  • Dependencies Installed:
    • fzf: 0.71.0
    • ripgrep (rg): 15.2.0
    • fd: available
    • bat: 0.25.0 (/home/user/bin/bat)

Architectural & Behavioral Specifications

1. Memory Directory Discovery

  • Logic: Find all directories named memory under ~/.claude/ (or $CLAUDE_DIR).
  • Command:
    mapfile -t MEM_DIRS < <(fd --hidden --no-ignore --ignore-case --type=directory memory "$CLAUDE_DIR" 2>/dev/null || true)
  • Error Handling: If no memory directories exist or $CLAUDE_DIR is missing, exit gracefully with a warning to stderr.

2. Query Threshold & Search Logic (search_memories)

  • Query Length Rule:
    • length({q}) < 3: Return code 0 immediately without invoking rg. Outputs nothing, producing an empty list in fzf.
    • length({q}) >= 3: Execute rg across all discovered MEM_DIRS.
  • Ripgrep Flags:
    • -g '*.md': Scope search exclusively to markdown files.
    • --line-number --column: Include line and column numbers in output (file:line:col:content).
    • --max-columns 150 --max-columns-preview: Truncate exceptionally long markdown lines while providing a preview count.
    • --color=always: Retain ANSI color highlighting for match matches.
    • --fixed-strings (Default): Literal text matching (unless regex mode is explicitly enabled).
    • --smart-case (Default): Smart case sensitivity matching.

3. fzf Interactive Engine & Subcommand Dispatcher

  • Architecture: Self-dispatching script ($0). The main process launches fzf, which invokes $0 --internal-search {q} on search queries and $0 --internal-preview {1} {2} for preview pane rendering.
  • fzf Parameters:
    fzf \
      --disabled \
      --ansi \
      --delimiter : \
      --header "🔍 Minimum $SEARCH_MIN_LEN characters required to search. [Esc: Exit | Enter: Select]" \
      --prompt "Claude Memory Search > " \
      --pointer "" \
      --bind "start:reload:$SCRIPT_PATH --internal-search {q}" \
      --bind "change:reload:$SCRIPT_PATH --internal-search {q}" \
      --preview "$SCRIPT_PATH --internal-preview {1} {2}" \
      --preview-window "right:55%:wrap" \
      --color "hl:green,hl+:green:bold,fg+:white:bold,bg+:black" \
      --layout=reverse

4. Preview Pane Logic (preview_match)

  • Input: Takes $1 (filepath) and $2 (line number).
  • ANSI Sanitization: Strips ANSI escape sequences from $1 and $2 using sed -r 's/\x1b\[[0-9;]*[a-zA-Z]//g' before passing to bat or file existence checks.
  • Renderer:
    • If bat is available: bat --style=full --color=always --highlight-line "$line" --line-range "$start:$end" "$file"
    • Fallback: sed -n "$start,$end p" "$file".

5. Output & Selection Hook (on_select_match)

  • Default Action: Prints selected line (file:line:col:content) to stdout.
  • Extension Hooks: Documented inline comments showing how to parse selection with IFS=':' read -r file line col _ and open in $EDITOR / code.

Suggested Skills for Fresh Agent

  • antigravity-guide: Reference for Antigravity tools and CLI setup.

Rebuilding Checklist

  1. Ensure fzf, rg, fd, and bat are installed.
  2. Implement self-dispatching script structure (--internal-search, --internal-preview).
  3. Enforce 3-character threshold in search_memories.
  4. Validate ANSI stripping in preview handlers.
  5. Set chmod +x on the generated file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment