diff --git a/plugins/persistent-identity/.claude-plugin/plugin.json b/plugins/persistent-identity/.claude-plugin/plugin.json new file mode 100644 index 0000000000..8ed6064ae6 --- /dev/null +++ b/plugins/persistent-identity/.claude-plugin/plugin.json @@ -0,0 +1,9 @@ +{ + "name": "persistent-identity", + "version": "1.0.0", + "description": "Gives Claude Code a persistent name and per-project memory that carries across sessions", + "author": { + "name": "Hrishikesh S", + "email": "hrish2006@gmail.com" + } +} diff --git a/plugins/persistent-identity/README.md b/plugins/persistent-identity/README.md new file mode 100644 index 0000000000..93653d504c --- /dev/null +++ b/plugins/persistent-identity/README.md @@ -0,0 +1,54 @@ +# Persistent Identity Plugin + +Gives your Claude Code instance a persistent name and per-project memory that carries across sessions. + +## Features + +- **Persistent Name**: Auto-generated on first run (e.g., "swift-fox", "keen-owl"), consistent across all projects +- **Per-Project Memory**: Claude remembers important context, decisions, and preferences for each project between sessions +- **Natural Identity**: Claude uses its name naturally in conversation without being repetitive + +## How It Works + +On every session start, the plugin: +1. Reads (or creates) your instance's identity from `~/.claude/persistent-identity/identity.md` +2. Loads per-project memory from `~/.claude/persistent-identity/projects//memory.md` +3. Injects identity and memory into the session context + +Claude will naturally maintain its memory file by writing important observations as it works with you. + +## Commands + +### `/name [new-name]` + +View or change your Claude instance's name. + +- `/name` -- show current name +- `/name marvin` -- rename to "marvin" + +Name requirements: 2-30 characters, letters/numbers/hyphens, must start with a letter. + +## Data Storage + +All data lives in `~/.claude/persistent-identity/`: + +``` +~/.claude/persistent-identity/ +├── identity.md # Your instance's name +└── projects/ + └── / + ├── project-info.txt # Maps hash to project path + └── memory.md # Per-project memory +``` + +## Uninstallation + +1. Remove the plugin from your Claude Code configuration +2. Optionally delete `~/.claude/persistent-identity/` to remove all identity and memory data + +## Notes + +- Memory files are markdown and can be manually edited +- The plugin adds ~400-600 tokens of context per session (plus the size of your memory file) +- Names are auto-generated from 1,600 adjective-noun combinations +- The identity is global (same name across all projects); memory is per-project diff --git a/plugins/persistent-identity/commands/name.md b/plugins/persistent-identity/commands/name.md new file mode 100644 index 0000000000..f5a7b5ddb7 --- /dev/null +++ b/plugins/persistent-identity/commands/name.md @@ -0,0 +1,16 @@ +--- +description: View or change your Claude Code instance's name +argument-hint: [new-name] +allowed-tools: + - Bash +--- + +Run the rename script with the provided arguments: + +```bash +bash "${CLAUDE_PLUGIN_ROOT}/scripts/rename-identity.sh" $ARGUMENTS +``` + +After running: +- If the user provided a name, confirm the rename and note it takes effect next session +- If no name was provided, show them their current identity diff --git a/plugins/persistent-identity/data/wordlists.sh b/plugins/persistent-identity/data/wordlists.sh new file mode 100644 index 0000000000..95d2e47019 --- /dev/null +++ b/plugins/persistent-identity/data/wordlists.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Word lists for identity name generation +# Names are generated as adjective-noun pairs (e.g., "swift-fox", "keen-owl") + +ADJECTIVES=( + swift keen bold calm bright + quick sharp clear steady warm + agile lucid vivid prime sage + nimble astute deft adept brisk + noble gentle fierce ardent grand + witty clever subtle poised stoic + serene fluid lithe vibrant crisp + staunch placid earnest candid apt +) + +NOUNS=( + fox owl hawk bear wolf + lynx heron crane raven falcon + otter cedar birch willow oak + aspen maple spruce cypress elm + lark finch wren robin dove + pike flint ridge brook vale + summit harbor reef crest glade + beacon forge nexus pulse spark +) diff --git a/plugins/persistent-identity/hooks-handlers/session-start.sh b/plugins/persistent-identity/hooks-handlers/session-start.sh new file mode 100755 index 0000000000..79f62397c6 --- /dev/null +++ b/plugins/persistent-identity/hooks-handlers/session-start.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Configuration --- +IDENTITY_DIR="$HOME/.claude/persistent-identity" +IDENTITY_FILE="$IDENTITY_DIR/identity.md" +PROJECTS_DIR="$IDENTITY_DIR/projects" + +# --- Source word lists --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")" +source "$PLUGIN_ROOT/data/wordlists.sh" + +# --- Ensure directory structure exists --- +mkdir -p "$IDENTITY_DIR" + +# --- Read or create identity --- +if [[ -f "$IDENTITY_FILE" ]]; then + IDENTITY_NAME=$(head -1 "$IDENTITY_FILE" | sed 's/^# //') +else + # Generate random adjective-noun name + ADJ_INDEX=$((RANDOM % ${#ADJECTIVES[@]})) + NOUN_INDEX=$((RANDOM % ${#NOUNS[@]})) + IDENTITY_NAME="${ADJECTIVES[$ADJ_INDEX]}-${NOUNS[$NOUN_INDEX]}" + + cat > "$IDENTITY_FILE" << EOF +# ${IDENTITY_NAME} + +Created: $(date -u +%Y-%m-%dT%H:%M:%SZ) + +This is the persistent identity for this Claude Code installation. +Use /name to change your name. +EOF +fi + +# --- Determine project and load memory --- +PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}" + +if command -v shasum &>/dev/null; then + PROJECT_HASH=$(echo -n "$PROJECT_DIR" | shasum -a 256 | cut -c1-16) +else + PROJECT_HASH=$(echo -n "$PROJECT_DIR" | sha256sum | cut -c1-16) +fi + +PROJECT_MEMORY_DIR="$PROJECTS_DIR/$PROJECT_HASH" +MEMORY_FILE="$PROJECT_MEMORY_DIR/memory.md" + +mkdir -p "$PROJECT_MEMORY_DIR" + +# Write project info for reverse lookup +echo "$PROJECT_DIR" > "$PROJECT_MEMORY_DIR/project-info.txt" + +# --- Read memory if it exists --- +MEMORY_CONTENT="" +if [[ -f "$MEMORY_FILE" ]] && [[ -s "$MEMORY_FILE" ]]; then + MEMORY_CONTENT=$(cat "$MEMORY_FILE") +fi + +# --- Build additionalContext --- +if [[ -n "$MEMORY_CONTENT" ]]; then + MEMORY_SECTION="## Your Memory for This Project + +The following are your notes from previous sessions in this project. Use them to maintain continuity. + +${MEMORY_CONTENT} + +---" +else + MEMORY_SECTION="## Your Memory for This Project + +You have no saved memories for this project yet. As you work, save important observations, decisions, and context to your memory file." +fi + +CONTEXT="## Your Identity + +You are **${IDENTITY_NAME}**. This is your persistent name across all sessions and projects. When greeting the user or referring to yourself, use this name naturally -- for example in your initial greeting or when asked who you are. Do not announce your name on every message. + +${MEMORY_SECTION} + +## Memory Management + +You have a persistent memory file for this project at: +\`${MEMORY_FILE}\` + +Write to this file when you learn something important that should persist across sessions: +- Key architectural decisions and their rationale +- User preferences for this project (coding style, conventions, preferred tools) +- Important context about the codebase that took effort to discover +- Recurring issues or patterns +- Your relationship with the user -- things they've shared, how they like to work + +Keep your memory file organized with markdown headers. Update it incrementally -- append new sections or update existing ones. Keep entries concise and actionable. + +Only write to your memory file when you have genuinely useful information to persist. Quality over quantity." + +# --- JSON-escape and output --- +if command -v jq &>/dev/null; then + ESCAPED_CONTEXT=$(echo "$CONTEXT" | jq -Rs .) +else + ESCAPED_CONTEXT=$(echo "$CONTEXT" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))') +fi + +cat << EOF +{ + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": ${ESCAPED_CONTEXT} + } +} +EOF + +exit 0 diff --git a/plugins/persistent-identity/hooks/hooks.json b/plugins/persistent-identity/hooks/hooks.json new file mode 100644 index 0000000000..a13ad66a9b --- /dev/null +++ b/plugins/persistent-identity/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "description": "Persistent identity and memory injection at session start", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks-handlers/session-start.sh", + "timeout": 10 + } + ] + } + ] + } +} diff --git a/plugins/persistent-identity/scripts/rename-identity.sh b/plugins/persistent-identity/scripts/rename-identity.sh new file mode 100755 index 0000000000..20d4192c39 --- /dev/null +++ b/plugins/persistent-identity/scripts/rename-identity.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +IDENTITY_DIR="$HOME/.claude/persistent-identity" +IDENTITY_FILE="$IDENTITY_DIR/identity.md" + +# --- No argument: show current name --- +if [[ $# -eq 0 ]] || [[ -z "${1:-}" ]]; then + if [[ -f "$IDENTITY_FILE" ]]; then + CURRENT_NAME=$(head -1 "$IDENTITY_FILE" | sed 's/^# //') + echo "Current identity: ${CURRENT_NAME}" + echo "" + echo "To rename, use: /name " + echo "Example: /name swift-fox" + else + echo "No identity set yet. Start a new session to auto-generate one," + echo "or use: /name " + fi + exit 0 +fi + +NEW_NAME="$1" + +# --- Validate name format --- +if [[ ! "$NEW_NAME" =~ ^[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]$ ]] || [[ ${#NEW_NAME} -lt 2 ]] || [[ ${#NEW_NAME} -gt 30 ]]; then + echo "Error: Invalid name '${NEW_NAME}'" >&2 + echo "" >&2 + echo "Name requirements:" >&2 + echo " - 2-30 characters" >&2 + echo " - Letters, numbers, and hyphens only" >&2 + echo " - Must start with a letter" >&2 + echo " - Must not end with a hyphen" >&2 + exit 1 +fi + +# --- Read old name --- +OLD_NAME="" +if [[ -f "$IDENTITY_FILE" ]]; then + OLD_NAME=$(head -1 "$IDENTITY_FILE" | sed 's/^# //') +fi + +# --- Update identity file --- +mkdir -p "$IDENTITY_DIR" + +cat > "$IDENTITY_FILE" << EOF +# ${NEW_NAME} + +Created: $(date -u +%Y-%m-%dT%H:%M:%SZ) +Renamed from: ${OLD_NAME:-"(first name)"} + +This is the persistent identity for this Claude Code installation. +Use /name to change your name. +EOF + +# --- Report --- +if [[ -n "$OLD_NAME" ]]; then + echo "Renamed from '${OLD_NAME}' to '${NEW_NAME}'" +else + echo "Identity set to '${NEW_NAME}'" +fi +echo "" +echo "The new name will appear in your greeting on the next session."