Agent Memory
Hook
Hooks
What shodh-memory runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
$ npx -y skills add varun29ankuS/shodh-memory --agent claude-codeShips with shodh-memory. Installing the plugin gets these hooks.
Where it lives
- hooks/claude-code-ingest.shGitHub
Read the script
#!/bin/bash # Shodh-Memory Auto-Ingest Hook for Claude Code # # This hook runs after each Claude Code response to automatically # store conversations in shodh-memory for persistent learning. # # Installation: # 1. Copy this file to ~/.claude/hooks/ # 2. Add to ~/.claude/settings.json: # { # "hooks": { # "Stop": [{ # "matcher": "", # "hooks": [{ # "type": "command", # "command": "bash ~/.claude/hooks/claude-code-ingest.sh" # }] # }] # } # } # 3. Ensure shodh-memory-server is running on localhost:3030 # # Environment Variables: # SHODH_API_URL - API endpoint (default: http://127.0.0.1:3030) # SHODH_API_KEY - API key (default: dev key) # SHODH_USER_ID - User ID for memory isolation (default: claude-code) set -e API_URL="${SHODH_API_URL:-http://127.0.0.1:3030}" USER_ID="${SHODH_USER_ID:-claude-code}" # API Key - env, else the shared key file persisted by the MCP server # (<data-root>/.api-key, see mcp-server/api-key-store.ts). No hardcoded fallback. if [ -z "$SHODH_API_KEY" ]; then for KEY_FILE in \ ${SHODH_MEMORY_PATH:+"$SHODH_MEMORY_PATH/.api-key"} \ "${XDG_DATA_HOME:-$HOME/.local/share}/shodh-memory/.api-key" \ "$HOME/Library/Application Support/shodh-memory/.api-key" \ ${APPDATA:+"$APPDATA/shodh-memory/.api-key"}; do if [ -f "$KEY_FILE" ]; then SHODH_API_KEY=$(tr -d '[:space:]' < "$KEY_FILE") if [ -n "$SHODH_API_KEY" ]; then break; fi fi done fi if [ -z "$SHODH_API_KEY" ]; then echo "ERROR: SHODH_API_KEY not set and no shared key file found (connect the shodh-memory MCP server once to create it)" >&2 exit 1 fi API_KEY="$SHODH_API_KEY" # Read hook input from stdin INPUT=$(cat) # Extract transcript path from hook input TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty') if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then exit 0 fi # Extract the last exchange (user message + assistant response) LAST_MESSAGES=$(jq -c '.[-2:]' "$TRANSCRIPT_PATH" 2>/dev/null || echo "[]") if [ "$LAST_MESSAGES" = "[]" ] || [ "$LAST_MESSAGES" = "null" ]; then exit 0 fi # Format conversation content - extract text from both user and assistant messages CONTENT=$(echo "$LAST_MESSAGES" | jq -r ' map( if .role == "user" then "User: " + (.content | if type == "array" then map(select(.type == "text") | .text) | join("\n") else tostring end) elif .role == "assistant" then "Assistant: " + (.content | if type == "array" then map(select(.type == "text") | .text) | join("\n") else tostring end) else empty end ) | join("\n\n") ' 2>/dev/null || echo "") # Skip if content is empty or too short (< 50 chars = likely noise) if [ -z "$CONTENT" ] || [ ${#CONTENT} -lt 50 ]; then exit 0 fi # Truncate if too long (max 4000 chars for a single memory) if [ ${#CONTENT} -gt 4000 ]; then CONTENT="${CONTENT:0:4000}..." fi # Escape content for JSON CONTENT_ESCAPED=$(echo "$CONTENT" | jq -Rs '.') # Extract project context from working directory CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"') PROJECT=$(basename "$CWD") # Send to shodh-memory API (fire and forget, don't block Claude) curl -s -X POST "$API_URL/api/record" \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ --connect-timeout 2 \ --max-time 5 \ -d "{ \"user_id\": \"$USER_ID\", \"experience\": { \"content\": $CONTENT_ESCAPED, \"experience_type\": \"Conversation\", \"tags\": [\"claude-code\", \"auto-ingest\", \"$PROJECT\"] } }" > /dev/null 2>&1 || true exit 0 - hooks/memory-hook.test.tsGitHub
Read the script
import { describe, expect, it } from "bun:test"; import { buildPreToolContext, formatRelativeTime, formatMemoriesForContext, isErrorOutput, describeKeyOrigin, reportAuthFailure, } from "./memory-hook"; describe("formatRelativeTime", () => { it("returns today for current date", () => { const nowIso = new Date().toISOString(); expect(formatRelativeTime(nowIso)).toBe("today"); }); it("returns yesterday for one day old date", () => { const d = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); expect(formatRelativeTime(d)).toBe("yesterday"); }); it("returns Xd ago for dates under a week", () => { const d = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(); expect(formatRelativeTime(d)).toBe("3d ago"); }); it("returns calendar date for older memories", () => { const d = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(); const value = formatRelativeTime(d); expect(value).not.toContain("ago"); expect(value).not.toBe("today"); expect(value).not.toBe("yesterday"); }); }); describe("formatMemoriesForContext", () => { const memory = (over: Partial<Record<string, unknown>> = {}) => ({ id: "m1", content: "Remember to review deployment checklist before release", memory_type: "Task", score: 0.83, importance: 0.7, created_at: new Date().toISOString(), tags: ["deploy"], relevance_reason: "matches query", matched_entities: ["release"], ...over, }); // Returns SurfaceResult | null, not a string: callers need `meta` for the // surfacing decision, and null is how "nothing worth surfacing" is expressed. it("returns null for empty input", () => { expect(formatMemoriesForContext([])).toBeNull(); }); it("returns null when the best score is under the noise floor", () => { expect(formatMemoriesForContext([memory({ score: 0.01 })])).toBeNull(); }); // Displayed percentages are normalised across the set, so a lone memory is // always 100% — its raw score survives on meta.bestScore. it("normalises the displayed score and keeps the raw one in meta", () => { const out = formatMemoriesForContext([memory()]); expect(out).not.toBeNull(); expect(out!.text).toContain("100% match"); expect(out!.text).toContain("today"); expect(out!.text).toContain("Remember to review deployment checklist"); expect(out!.meta.bestScore).toBe(83); expect(out!.meta.count).toBe(1); }); it("spreads the set across the normalised range", () => { const now = new Date().toISOString(); const out = formatMemoriesForContext([ memory({ id: "m1", content: "A", score: 0.2, created_at: now }), memory({ id: "m2", content: "B", score: 0.3, created_at: now }), ]); expect(out).not.toBeNull(); expect((out!.text.match(/\u2022/g) || []).length).toBe(2); expect(out!.text).toContain("0% match"); expect(out!.text).toContain("100% match"); }); it("truncates long content at 120 chars with an ellipsis", () => { const out = formatMemoriesForContext([memory({ content: "x".repeat(130) })]); expect(out).not.toBeNull(); expect(out!.text).toContain("..."); expect(out!.text).toContain("x".repeat(120)); expect(out!.text).not.toContain("x".repeat(121)); }); }); describe("buildPreToolContext", () => { it("builds edit context", () => { expect(buildPreToolContext("Edit", { file_path: "src/main.ts" })).toBe("Editing file: src/main.ts"); }); it("builds write context from the same branch", () => { expect(buildPreToolContext("Write", { file_path: "src/new.ts" })).toBe("Editing file: src/new.ts"); }); // Bash deliberately has no branch of its own. handlePreToolUse returns before // calling this for anything but Edit/Write (see the guard in that handler), // and routing a raw shell command through here would send it to the memory // server — commands carry credentials often enough that the narrow surface // is the point, not an oversight. it("falls through to the generic form for tools it no longer specialises", () => { expect(buildPreToolContext("Bash", { command: "curl -H 'X-API-Key: sk-live-abc' https://api" })) .toBe("About to use Bash"); expect(buildPreToolContext("Read", {})).toBe("About to use Read"); }); it("falls back when the expected input field is absent", () => { expect(buildPreToolContext("Edit", {})).toBe("About to use Edit"); }); }); describe("isErrorOutput", () => { it("detects the error shapes it targets", () => { expect(isErrorOutput("error[E0308]: mismatched types")).toBe(true); expect(isErrorOutput("Operation FAILED quickly")).toBe(true); expect(isErrorOutput("thread 'main' panicked at src/lib.rs:4")).toBe(true); expect(isErrorOutput("fatal: not a git repository")).toBe(true); expect(isErrorOutput("process exited with exit code 1")).toBe(true); expect(isErrorOutput("bash: foo: command not found")).toBe(true); }); // Cargo, npm and tsc all prefix diagnostics with a lowercase "error:", so a // case-sensitive match missed the most common failure line in this repo. // Only this pattern is case-insensitive — see the trade pinned below. it("detects lowercase error: diagnostics", () => { expect(isErrorOutput("error: could not compile `shodh-memory`")).toBe(true); expect(isErrorOutput("Error: connect ECONNREFUSED")).toBe(true); }); it("returns false on clean output", () => { expect(isErrorOutput("Command completed successfully")).toBe(false); }); // The matching is case-sensitive on FAILED by design. Relaxing it would make // "0 tests failed" — an ordinary green-build line — read as a failure, and a // false positive here mislabels a successful run in memory, which is worse // than missing one. These cases pin that trade so it is not relaxed by accident. it("does not fire on clean lines that merely contain the words", () => { expect(isErrorOutput("0 tests failed")).toBe(false - hooks/memory-hook.tsGitHub
Read the script
#!/usr/bin/env bun /** * Shodh Memory Hook — Native Claude Code Integration * * Aggressive proactive context surfacing at every opportunity. * Memory is woven into every interaction — the AI thinks with memory. * * Events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, SubagentStop, Stop * * Architecture: Each hook event spawns a new Bun process. All state is persisted * to a temp file keyed by session_id, loaded at process start, saved on exit. */ // --------------------------------------------------------------------------- // 1. Config Constants // --------------------------------------------------------------------------- const SHODH_API_URL = process.env.SHODH_API_URL || "http://127.0.0.1:3030"; /** * API key resolution — MUST match the MCP shim's persistence scheme * (mcp-server/api-key-store.ts; this file ships standalone and cannot import * it). The auto-spawned server only accepts the key the shim generated, which * the shim persists to `<data-root>/.api-key`. Resolution order: * 1. SHODH_API_KEY env (explicit) * 2. `<data-root>/.api-key` — the shared key persisted by the MCP shim * (data root = SHODH_MEMORY_PATH, else the platform data dir, mirroring * the Rust server's default_storage_path in src/config.rs) * 3. Legacy dev key — only correct when the user started the server * manually with that key; SessionStart verifies auth and fails LOUDLY * instead of letting capture die silently on a 401. */ interface ResolvedApiKey { key: string; source: "env" | "shared-key-file" | "legacy-dev-fallback"; file?: string; } /** * Where the key came from, for the auth-failure report. * * Derived from the discriminant alone. The resolved path lives on * RESOLVED_API_KEY.file and is deliberately not read here: printing it puts the * operator's home directory into stderr and into the user-visible * systemMessage, and the fix lines below name the remedy without it. */ export function describeKeyOrigin(source: ResolvedApiKey["source"]): string { switch (source) { case "env": return "the SHODH_API_KEY environment variable"; case "shared-key-file": return "the shared key file in the shodh-memory data directory"; case "legacy-dev-fallback": return "the legacy dev key (no SHODH_API_KEY set and no shared key file found)"; default: { // Unreachable for the declared union — adding a variant without a case // above fails this assignment at compile time. The return keeps the // message sane if a value ever arrives from outside the type system. const unhandled: never = source; void unhandled; return "an unrecognised key source"; } } } function resolveApiKey(): ResolvedApiKey { if (process.env.SHODH_API_KEY) { return { key: process.env.SHODH_API_KEY, source: "env" }; } const fs = require("fs"); const path = require("path"); const os = require("os"); const roots: string[] = []; if (process.env.SHODH_MEMORY_PATH) { roots.push(process.env.SHODH_MEMORY_PATH); } const home = os.homedir(); if (process.platform === "win32") { roots.push(path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "shodh-memory")); } else if (process.platform === "darwin") { roots.push(path.join(home, "Library", "Application Support", "shodh-memory")); } else { roots.push(path.join(process.env.XDG_DATA_HOME || path.join(home, ".local", "share"), "shodh-memory")); } for (const root of roots) { const file = path.join(root, ".api-key"); try { const raw = fs.readFileSync(file, "utf-8").trim(); if (raw) return { key: raw, source: "shared-key-file", file }; } catch { // File absent or unreadable — try the next candidate. } } return { key: "sk-shodh-dev-local-testing-key", source: "legacy-dev-fallback" }; } const RESOLVED_API_KEY = resolveApiKey(); const SHODH_API_KEY = RESOLVED_API_KEY.key; const SHODH_USER_ID = process.env.SHODH_USER_ID || "claude-code"; const HOOK_TIMEOUT_MS = 5000; const CIRCUIT_BREAKER_THRESHOLD = 3; // --------------------------------------------------------------------------- // 2. Type Interfaces // --------------------------------------------------------------------------- interface HookInput { hook_event_name: string; session_id?: string; transcript_path?: string; cwd?: string; // UserPromptSubmit prompt?: string; // PreToolUse / PostToolUse tool_name?: string; tool_input?: Record<string, unknown>; tool_output?: string; tool_response?: unknown; // Stop stop_reason?: string; // SubagentStop agent_id?: string; agent_type?: string; agent_transcript_path?: string; // Legacy field names (backward compat) subagent_type?: string; subagent_result?: string; } interface SurfacedMemory { id: string; content: string; memory_type: string; score: number; importance: number; created_at: string; tags: string[]; relevance_reason: string; matched_entities: string[]; } interface ProactiveContextResponse { memories: SurfacedMemory[]; due_reminders: unknown[]; context_reminders: unknown[]; memory_count: number; reminder_count: number; ingested_memory_id: string | null; feedback_processed: { memories_evaluated: number; reinforced: string[]; weakened: string[] } | null; relevant_todos: { id: string; short_id: string; content: string; status: string; priority: string; project: string | null; due_date: string | null; relevance_reason: string }[]; todo_count: number; relevant_facts: { id: string; fact: string; confidence: number; support_count: number; related_entities: string[] }[]; latency_ms: number; detected_entities: { name: string; entity_type: string }[]; } interface ToolAction { tool_name: string; inputs: Record<string, string>; success: boolean; output_snippet?: string; } interface RememberResponse { id?: string; memory_id?: string; } interface SurfaceMetadata { count: number; - hooks/session-start.shGitHub
Read the script
#!/bin/bash # Shodh Memory - Session Start Hook # Loads proactive context at session start SHODH_API_URL="${SHODH_API_URL:-http://127.0.0.1:3030}" SHODH_USER_ID="${SHODH_USER_ID:-claude-code}" # Resolve API key: env > shared key file persisted by the MCP server > legacy dev key. # The shared file lives at <data-root>/.api-key (see mcp-server/api-key-store.ts); # the data root mirrors the Rust server's default_storage_path (src/config.rs). if [ -z "$SHODH_API_KEY" ]; then for KEY_FILE in \ ${SHODH_MEMORY_PATH:+"$SHODH_MEMORY_PATH/.api-key"} \ "${XDG_DATA_HOME:-$HOME/.local/share}/shodh-memory/.api-key" \ "$HOME/Library/Application Support/shodh-memory/.api-key" \ ${APPDATA:+"$APPDATA/shodh-memory/.api-key"}; do if [ -f "$KEY_FILE" ]; then SHODH_API_KEY=$(tr -d '[:space:]' < "$KEY_FILE") if [ -n "$SHODH_API_KEY" ]; then break; fi fi done fi # Last resort: legacy dev key (only valid when the server was started with it). # A wrong key is reported loudly below instead of failing silently. SHODH_API_KEY="${SHODH_API_KEY:-sk-shodh-dev-local-testing-key}" # Get project directory for context PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}" PROJECT_NAME=$(basename "$PROJECT_DIR") # Build context from recent git activity and current directory CONTEXT="Working in: $PROJECT_NAME" if [ -d "$PROJECT_DIR/.git" ]; then RECENT_FILES=$(cd "$PROJECT_DIR" && git diff --name-only HEAD~5 2>/dev/null | head -10 | tr '\n' ', ') if [ -n "$RECENT_FILES" ]; then CONTEXT="$CONTEXT. Recently modified: $RECENT_FILES" fi fi # Query proactive context from brain, capturing the HTTP status so an auth # failure is reported LOUDLY instead of memory dying silently for the session. RESPONSE_FILE=$(mktemp) trap 'rm -f "$RESPONSE_FILE"' EXIT HTTP_CODE=$(curl -s -o "$RESPONSE_FILE" -w "%{http_code}" -X POST "$SHODH_API_URL/api/proactive_context" \ -H "Content-Type: application/json" \ -H "X-API-Key: $SHODH_API_KEY" \ -d "{ \"user_id\": \"$SHODH_USER_ID\", \"context\": \"$CONTEXT\", \"max_results\": 5, \"auto_ingest\": false }" 2>/dev/null) if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then echo "[shodh] ============================================================" >&2 echo "[shodh] MEMORY CAPTURE DISABLED - server rejected API key ($HTTP_CODE)" >&2 echo "[shodh] Set SHODH_API_KEY to the key the server was started with, or" >&2 echo "[shodh] connect the shodh-memory MCP server once so it persists the" >&2 echo "[shodh] shared key file (<data-dir>/.api-key) that hooks read." >&2 echo "[shodh] ============================================================" >&2 echo "{\"systemMessage\": \"shodh-memory: capture DISABLED - the memory server rejected the hook's API key ($HTTP_CODE). Set SHODH_API_KEY or connect the MCP server once to create the shared key file.\"}" exit 0 fi RESPONSE=$(cat "$RESPONSE_FILE") # Extract memories if response is valid MEMORIES=$(echo "$RESPONSE" | jq -r '.memories[]? | "- [\(.memory_type)] \(.content | .[0:200])"' 2>/dev/null) if [ -n "$MEMORIES" ] && [ "$MEMORIES" != "null" ]; then # Write to CLAUDE.local.md for automatic injection cat > "$PROJECT_DIR/.claude/memory-context.md" << EOF # Proactive Memory Context The following memories from past sessions may be relevant: $MEMORIES Use these to maintain continuity. If they conflict with current instructions, prioritize current. EOF echo "Loaded $(echo "$MEMORIES" | wc -l) memories from brain" fi - hooks/stop.shGitHub
Read the script
#!/bin/bash # Shodh Memory - Stop Hook # Stores the interaction when Claude finishes responding SHODH_API_URL="${SHODH_API_URL:-http://127.0.0.1:3030}" SHODH_USER_ID="${SHODH_USER_ID:-claude-code}" # Resolve API key: env > shared key file persisted by the MCP server > legacy dev key. # (Shared file: <data-root>/.api-key, see mcp-server/api-key-store.ts.) if [ -z "$SHODH_API_KEY" ]; then for KEY_FILE in \ ${SHODH_MEMORY_PATH:+"$SHODH_MEMORY_PATH/.api-key"} \ "${XDG_DATA_HOME:-$HOME/.local/share}/shodh-memory/.api-key" \ "$HOME/Library/Application Support/shodh-memory/.api-key" \ ${APPDATA:+"$APPDATA/shodh-memory/.api-key"}; do if [ -f "$KEY_FILE" ]; then SHODH_API_KEY=$(tr -d '[:space:]' < "$KEY_FILE") if [ -n "$SHODH_API_KEY" ]; then break; fi fi done fi SHODH_API_KEY="${SHODH_API_KEY:-sk-shodh-dev-local-testing-key}" # Read hook input from stdin (JSON with stop_hook_active, etc.) INPUT=$(cat) # Extract relevant fields STOP_REASON=$(echo "$INPUT" | jq -r '.stop_reason // "end_turn"') # Get the transcript if available (from environment or temp file) TRANSCRIPT_FILE="${CLAUDE_TRANSCRIPT_FILE:-}" if [ -n "$TRANSCRIPT_FILE" ] && [ -f "$TRANSCRIPT_FILE" ]; then # Parse last exchange from transcript LAST_USER=$(tail -100 "$TRANSCRIPT_FILE" | grep -A 50 '"role": "user"' | head -50) LAST_ASSISTANT=$(tail -100 "$TRANSCRIPT_FILE" | grep -A 50 '"role": "assistant"' | head -50) if [ -n "$LAST_USER" ] && [ -n "$LAST_ASSISTANT" ]; then # Store to brain CONTENT="User: $(echo "$LAST_USER" | jq -r '.content' 2>/dev/null | head -c 500) Assistant: $(echo "$LAST_ASSISTANT" | jq -r '.content' 2>/dev/null | head -c 1000)" curl -s -X POST "$SHODH_API_URL/api/remember" \ -H "Content-Type: application/json" \ -H "X-API-Key: $SHODH_API_KEY" \ -d "{ \"user_id\": \"$SHODH_USER_ID\", \"content\": $(echo "$CONTENT" | jq -Rs .), \"memory_type\": \"Conversation\", \"tags\": [\"source:hook\", \"stop:$STOP_REASON\"] }" > /dev/null 2>&1 fi fi # Always exit successfully - don't block Claude exit 0 - hooks/user-prompt.shGitHub
Read the script
#!/bin/bash # Shodh Memory - User Prompt Submit Hook # Enriches context based on what user is asking SHODH_API_URL="${SHODH_API_URL:-http://127.0.0.1:3030}" SHODH_USER_ID="${SHODH_USER_ID:-claude-code}" # Resolve API key: env > shared key file persisted by the MCP server > legacy dev key. # (Shared file: <data-root>/.api-key, see mcp-server/api-key-store.ts.) if [ -z "$SHODH_API_KEY" ]; then for KEY_FILE in \ ${SHODH_MEMORY_PATH:+"$SHODH_MEMORY_PATH/.api-key"} \ "${XDG_DATA_HOME:-$HOME/.local/share}/shodh-memory/.api-key" \ "$HOME/Library/Application Support/shodh-memory/.api-key" \ ${APPDATA:+"$APPDATA/shodh-memory/.api-key"}; do if [ -f "$KEY_FILE" ]; then SHODH_API_KEY=$(tr -d '[:space:]' < "$KEY_FILE") if [ -n "$SHODH_API_KEY" ]; then break; fi fi done fi SHODH_API_KEY="${SHODH_API_KEY:-sk-shodh-dev-local-testing-key}" # Read hook input from stdin INPUT=$(cat) # Extract the prompt PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""') # Skip if empty or very short if [ ${#PROMPT} -lt 10 ]; then exit 0 fi # Query brain for relevant memories based on this specific prompt RESPONSE=$(curl -s -X POST "$SHODH_API_URL/api/recall" \ -H "Content-Type: application/json" \ -H "X-API-Key: $SHODH_API_KEY" \ -d "{ \"user_id\": \"$SHODH_USER_ID\", \"query\": $(echo "$PROMPT" | head -c 500 | jq -Rs .), \"limit\": 3 }" 2>/dev/null) # Extract memories MEMORIES=$(echo "$RESPONSE" | jq -r '.results[]? | "[\(.memory_type)] \(.content | .[0:150])..."' 2>/dev/null | head -3) if [ -n "$MEMORIES" ] && [ "$MEMORIES" != "null" ]; then # Output additional context (Claude Code will inject this) echo "{\"additionalContext\": \"Relevant memories: $MEMORIES\"}" fi exit 0
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withshodh-memory
Local, LLM-free memory for AI agents. A single offline Rust binary — deterministic and auditable — that learns from use, forgets the irrelevant, and strengthens what matters. No cloud, no API keys.
Get the whole plugin
Stats
273
Stars
37
Forks
Active
Maintenance
Rust
Language
Apache-2.0
License
54m ago
Last commit
8mo ago
Created
Repo: varun29ankuS/shodh-memory
In this plugin
See everything inside 
