From 3d00a936552af50515cd1f7d14bcbbbf7ff4e640 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 14:55:46 +0300 Subject: [PATCH 01/24] feat: add MemPalace Claude Code plugin with hooks and instructions - Introduced README.md for plugin overview and installation instructions. - Added hooks configuration in hooks.json for auto-save and pre-compact functionality. - Implemented stop and pre-compact hooks in bash scripts for memory management. - Created marketplace.json and plugin.json for plugin metadata and versioning. - Developed skills and instructions for help, init, mine, search, and status functionalities. - Added CLI commands for executing hooks and displaying skill instructions. - Implemented hooks_cli.py for handling hook logic and JSON input/output. - Enhanced instruction files for user guidance on setup and usage. - Updated .gitignore to exclude additional files. - Created GitHub Actions workflow for syncing plugin version on push. --- .claude-plugin/README.md | 60 +++++ .claude-plugin/hooks/hooks.json | 25 +++ .../hooks/mempal-precompact-hook.sh | 5 + .claude-plugin/hooks/mempal-stop-hook.sh | 5 + .claude-plugin/marketplace.json | 18 ++ .claude-plugin/plugin.json | 33 +++ .claude-plugin/skills/help/SKILL.md | 12 + .claude-plugin/skills/init/SKILL.md | 18 ++ .claude-plugin/skills/mine/SKILL.md | 12 + .claude-plugin/skills/search/SKILL.md | 12 + .claude-plugin/skills/status/SKILL.md | 12 + .github/workflows/bump-plugin-version.yml | 34 +++ .gitignore | 1 + mempalace/cli.py | 60 +++++ mempalace/hooks_cli.py | 208 ++++++++++++++++++ mempalace/instructions/help.md | 105 +++++++++ mempalace/instructions/init.md | 69 ++++++ mempalace/instructions/mine.md | 64 ++++++ mempalace/instructions/search.md | 57 +++++ mempalace/instructions/status.md | 49 +++++ mempalace/instructions_cli.py | 28 +++ 21 files changed, 887 insertions(+) create mode 100644 .claude-plugin/README.md create mode 100644 .claude-plugin/hooks/hooks.json create mode 100644 .claude-plugin/hooks/mempal-precompact-hook.sh create mode 100644 .claude-plugin/hooks/mempal-stop-hook.sh create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 .claude-plugin/skills/help/SKILL.md create mode 100644 .claude-plugin/skills/init/SKILL.md create mode 100644 .claude-plugin/skills/mine/SKILL.md create mode 100644 .claude-plugin/skills/search/SKILL.md create mode 100644 .claude-plugin/skills/status/SKILL.md create mode 100644 .github/workflows/bump-plugin-version.yml create mode 100644 mempalace/hooks_cli.py create mode 100644 mempalace/instructions/help.md create mode 100644 mempalace/instructions/init.md create mode 100644 mempalace/instructions/mine.md create mode 100644 mempalace/instructions/search.md create mode 100644 mempalace/instructions/status.md create mode 100644 mempalace/instructions_cli.py diff --git a/.claude-plugin/README.md b/.claude-plugin/README.md new file mode 100644 index 0000000..6754626 --- /dev/null +++ b/.claude-plugin/README.md @@ -0,0 +1,60 @@ +# MemPalace Claude Code Plugin + +A Claude Code plugin that gives your AI a persistent memory system. Mine projects and conversations into a searchable palace backed by ChromaDB, with 19 MCP tools, auto-save hooks, and 5 guided skills. + +## Prerequisites + +- Python 3.9+ + +## Installation + +### Claude Code Marketplace + +```bash +claude plugin add mempalace +``` + +### Git + +```bash +claude plugin add --git https://github.com/milla-jovovich/mempalace +``` + +### Local Clone + +```bash +claude plugin add /path/to/mempalace +``` + +## Post-Install Setup + +After installing the plugin, run the init command to complete setup (pip install, MCP configuration, etc.): + +``` +/mempalace:init +``` + +## Available Slash Commands + +| Command | Description | +|---------|-------------| +| `/mempalace:help` | Show available tools, skills, and architecture | +| `/mempalace:init` | Set up MemPalace -- install, configure MCP, onboard | +| `/mempalace:search` | Search your memories across the palace | +| `/mempalace:mine` | Mine projects and conversations into the palace | +| `/mempalace:status` | Show palace overview -- wings, rooms, drawer counts | + +## Hooks + +MemPalace registers two hooks that run automatically: + +- **Stop** -- Saves conversation context when a session ends. +- **PreCompact** -- Preserves important memories before context compaction. + +## MCP Server + +The plugin automatically configures a local MCP server with 19 tools for storing, searching, and managing memories. No manual MCP setup is required -- `/mempalace:init` handles everything. + +## Full Documentation + +See the main [README](../README.md) for complete documentation, architecture details, and advanced usage. diff --git a/.claude-plugin/hooks/hooks.json b/.claude-plugin/hooks/hooks.json new file mode 100644 index 0000000..f1f0a90 --- /dev/null +++ b/.claude-plugin/hooks/hooks.json @@ -0,0 +1,25 @@ +{ + "description": "MemPalace auto-save and pre-compact hooks", + "hooks": { + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/mempal-stop-hook.sh" + } + ] + } + ], + "PreCompact": [ + { + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/mempal-precompact-hook.sh" + } + ] + } + ] + } +} diff --git a/.claude-plugin/hooks/mempal-precompact-hook.sh b/.claude-plugin/hooks/mempal-precompact-hook.sh new file mode 100644 index 0000000..0ac46dd --- /dev/null +++ b/.claude-plugin/hooks/mempal-precompact-hook.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# MemPalace PreCompact Hook — thin wrapper calling Python CLI +# All logic lives in mempalace.hooks_cli for cross-harness extensibility +INPUT=$(cat) +echo "$INPUT" | python3 -m mempalace hook run --hook precompact --harness claude-code diff --git a/.claude-plugin/hooks/mempal-stop-hook.sh b/.claude-plugin/hooks/mempal-stop-hook.sh new file mode 100644 index 0000000..cba3284 --- /dev/null +++ b/.claude-plugin/hooks/mempal-stop-hook.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# MemPalace Stop Hook — thin wrapper calling Python CLI +# All logic lives in mempalace.hooks_cli for cross-harness extensibility +INPUT=$(cat) +echo "$INPUT" | python3 -m mempalace hook run --hook stop --harness claude-code diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..0bf890c --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "mempalace", + "owner": { + "name": "milla-jovovich", + "url": "https://github.com/milla-jovovich" + }, + "plugins": [ + { + "name": "mempalace", + "source": ".", + "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", + "version": "3.0.0", + "author": { + "name": "milla-jovovich" + } + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..df7f544 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,33 @@ +{ + "name": "mempalace", + "version": "3.0.0", + "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", + "author": { "name": "milla-jovovich" }, + "license": "MIT", + "hooks": { + "Stop": "hooks/mempal-stop-hook.sh", + "PreCompact": "hooks/mempal-precompact-hook.sh" + }, + "skills": [ + { "name": "init", "file": "skills/init/SKILL.md" }, + { "name": "search", "file": "skills/search/SKILL.md" }, + { "name": "mine", "file": "skills/mine/SKILL.md" }, + { "name": "help", "file": "skills/help/SKILL.md" }, + { "name": "status", "file": "skills/status/SKILL.md" } + ], + "commands": [ + { "name": "mempalace:help", "description": "Show MemPalace help — available tools, skills, architecture", "skill": "help" }, + { "name": "mempalace:init", "description": "Set up MemPalace — install, configure MCP, onboard", "skill": "init" }, + { "name": "mempalace:search", "description": "Search your memories across the palace", "skill": "search" }, + { "name": "mempalace:mine", "description": "Mine projects and conversations into the palace", "skill": "mine" }, + { "name": "mempalace:status", "description": "Show palace overview — wings, rooms, drawer counts", "skill": "status" } + ], + "mcp": { + "mempalace": { + "command": "python3", + "args": ["-m", "mempalace.mcp_server"] + } + }, + "keywords": ["memory", "ai", "rag", "mcp", "chromadb", "palace", "search"], + "repository": { "type": "git", "url": "https://github.com/milla-jovovich/mempalace" } +} diff --git a/.claude-plugin/skills/help/SKILL.md b/.claude-plugin/skills/help/SKILL.md new file mode 100644 index 0000000..d389a1b --- /dev/null +++ b/.claude-plugin/skills/help/SKILL.md @@ -0,0 +1,12 @@ +--- +name: help +description: Show comprehensive MemPalace help — available skills, MCP tools, CLI commands, hooks, and architecture. +--- + +Run the following command and display its output to the user: + +```bash +mempalace instructions help +``` + +Display the output as-is — it's pre-formatted markdown. diff --git a/.claude-plugin/skills/init/SKILL.md b/.claude-plugin/skills/init/SKILL.md new file mode 100644 index 0000000..a255711 --- /dev/null +++ b/.claude-plugin/skills/init/SKILL.md @@ -0,0 +1,18 @@ +--- +name: init +description: Set up MemPalace — install the package, initialize a palace, configure MCP server, and verify everything works. +--- + +Run the following command to get setup instructions, then follow them step by step: + +```bash +mempalace instructions init +``` + +If the command fails (mempalace not installed yet), first install it: + +```bash +pip install mempalace +``` + +Then run the instructions command again and follow the output. diff --git a/.claude-plugin/skills/mine/SKILL.md b/.claude-plugin/skills/mine/SKILL.md new file mode 100644 index 0000000..8896ed7 --- /dev/null +++ b/.claude-plugin/skills/mine/SKILL.md @@ -0,0 +1,12 @@ +--- +name: mine +description: Mine projects and conversations into the MemPalace. Supports project files, conversation exports, and auto-classification. +--- + +Run the following command to get mining instructions, then follow them: + +```bash +mempalace instructions mine +``` + +Follow the returned instructions to mine the user's data. diff --git a/.claude-plugin/skills/search/SKILL.md b/.claude-plugin/skills/search/SKILL.md new file mode 100644 index 0000000..6ad2d24 --- /dev/null +++ b/.claude-plugin/skills/search/SKILL.md @@ -0,0 +1,12 @@ +--- +name: search +description: Search your memories across the MemPalace using semantic search with wing/room filtering. +--- + +Run the following command to get search instructions, then follow them: + +```bash +mempalace instructions search +``` + +Follow the returned instructions to execute the user's search query. diff --git a/.claude-plugin/skills/status/SKILL.md b/.claude-plugin/skills/status/SKILL.md new file mode 100644 index 0000000..dca3646 --- /dev/null +++ b/.claude-plugin/skills/status/SKILL.md @@ -0,0 +1,12 @@ +--- +name: status +description: Show the current state of your memory palace — wings, rooms, drawer counts, and suggestions. +--- + +Run the following command to get status instructions, then follow them: + +```bash +mempalace instructions status +``` + +Follow the returned instructions to display the palace status. diff --git a/.github/workflows/bump-plugin-version.yml b/.github/workflows/bump-plugin-version.yml new file mode 100644 index 0000000..8735f51 --- /dev/null +++ b/.github/workflows/bump-plugin-version.yml @@ -0,0 +1,34 @@ +name: Sync Plugin Version + +on: + push: + branches: [main] + +jobs: + sync-version: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + + - name: Extract version from version.py + id: version + run: | + VERSION=$(python3 -c "exec(open('mempalace/version.py').read()); print(__version__)") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Update plugin.json + run: | + jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' .claude-plugin/plugin.json > tmp.json && mv tmp.json .claude-plugin/plugin.json + + - name: Update marketplace.json + run: | + jq --arg v "${{ steps.version.outputs.version }}" '.plugins[0].version = $v' .claude-plugin/marketplace.json > tmp.json && mv tmp.json .claude-plugin/marketplace.json + + - name: Commit if changed + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add .claude-plugin/plugin.json .claude-plugin/marketplace.json + git diff --staged --quiet || git commit -m "chore: sync plugin version to ${{ steps.version.outputs.version }}" && git push diff --git a/.gitignore b/.gitignore index 54c2d1b..c8b10cc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__/ *.pyc .pytest_cache/ mempal.yaml +.a5c/ diff --git a/mempalace/cli.py b/mempalace/cli.py index 1599b08..9d2465b 100644 --- a/mempalace/cli.py +++ b/mempalace/cli.py @@ -226,6 +226,20 @@ def cmd_repair(args): print(f"\n{'=' * 55}\n") +def cmd_hook(args): + """Run hook logic: reads JSON from stdin, outputs JSON to stdout.""" + from .hooks_cli import run_hook + + run_hook(hook_name=args.hook, harness=args.harness) + + +def cmd_instructions(args): + """Output skill instructions to stdout.""" + from .instructions_cli import run_instructions + + run_instructions(name=args.name) + + def cmd_compress(args): """Compress drawers in a wing using AAAK Dialect.""" import chromadb @@ -451,6 +465,35 @@ def main(): help="Only split files containing at least N sessions (default: 2)", ) + # hook + p_hook = sub.add_parser( + "hook", + help="Run hook logic (reads JSON from stdin, outputs JSON to stdout)", + ) + hook_sub = p_hook.add_subparsers(dest="hook_action") + p_hook_run = hook_sub.add_parser("run", help="Execute a hook") + p_hook_run.add_argument( + "--hook", + required=True, + choices=["stop", "precompact"], + help="Hook name to run", + ) + p_hook_run.add_argument( + "--harness", + required=True, + choices=["claude-code"], + help="Harness type (determines stdin JSON format)", + ) + + # instructions + p_instructions = sub.add_parser( + "instructions", + help="Output skill instructions to stdout", + ) + instructions_sub = p_instructions.add_subparsers(dest="instructions_name") + for instr_name in ["init", "search", "mine", "help", "status"]: + instructions_sub.add_parser(instr_name, help=f"Output {instr_name} instructions") + # repair sub.add_parser( "repair", @@ -466,6 +509,23 @@ def main(): parser.print_help() return + # Handle two-level subcommands + if args.command == "hook": + if not getattr(args, "hook_action", None): + p_hook.print_help() + return + cmd_hook(args) + return + + if args.command == "instructions": + name = getattr(args, "instructions_name", None) + if not name: + p_instructions.print_help() + return + args.name = name + cmd_instructions(args) + return + dispatch = { "init": cmd_init, "mine": cmd_mine, diff --git a/mempalace/hooks_cli.py b/mempalace/hooks_cli.py new file mode 100644 index 0000000..6e0ec0a --- /dev/null +++ b/mempalace/hooks_cli.py @@ -0,0 +1,208 @@ +""" +Hook logic for MemPalace — Python implementation of stop and precompact hooks. + +Reads JSON from stdin, outputs JSON to stdout. +Supported hooks: stop, precompact +Supported harnesses: claude-code (extensible to cursor, gemini, etc.) +""" + +import json +import os +import re +import subprocess +import sys +from datetime import datetime +from pathlib import Path + +SAVE_INTERVAL = 15 +STATE_DIR = Path.home() / ".mempalace" / "hook_state" + +STOP_BLOCK_REASON = ( + "AUTO-SAVE checkpoint. Save key topics, decisions, quotes, and code " + "from this session to your memory system. Organize into appropriate " + "categories. Use verbatim quotes where possible. Continue conversation " + "after saving." +) + +PRECOMPACT_BLOCK_REASON = ( + "COMPACTION IMMINENT. Save ALL topics, decisions, quotes, code, and " + "important context from this session to your memory system. Be thorough " + "\u2014 after compaction, detailed context will be lost. Organize into " + "appropriate categories. Use verbatim quotes where possible. Save " + "everything, then allow compaction to proceed." +) + + +def _sanitize_session_id(session_id: str) -> str: + """Only allow alnum, dash, underscore to prevent path traversal.""" + sanitized = re.sub(r"[^a-zA-Z0-9_-]", "", session_id) + return sanitized or "unknown" + + +def _count_human_messages(transcript_path: str) -> int: + """Count human messages in a JSONL transcript, skipping command-messages.""" + path = Path(transcript_path).expanduser() + if not path.is_file(): + return 0 + count = 0 + try: + with open(path) as f: + for line in f: + try: + entry = json.loads(line) + msg = entry.get("message", {}) + if isinstance(msg, dict) and msg.get("role") == "user": + content = msg.get("content", "") + if isinstance(content, str) and "" in content: + continue + count += 1 + except (json.JSONDecodeError, AttributeError): + pass + except OSError: + return 0 + return count + + +def _log(message: str): + """Append to hook state log file.""" + try: + STATE_DIR.mkdir(parents=True, exist_ok=True) + log_path = STATE_DIR / "hook.log" + timestamp = datetime.now().strftime("%H:%M:%S") + with open(log_path, "a") as f: + f.write(f"[{timestamp}] {message}\n") + except OSError: + pass + + +def _output(data: dict): + """Print JSON to stdout with consistent formatting (pretty-printed).""" + print(json.dumps(data, indent=2, ensure_ascii=False)) + + +def _maybe_auto_ingest(): + """If MEMPAL_DIR is set and exists, run mempalace mine in background.""" + mempal_dir = os.environ.get("MEMPAL_DIR", "") + if mempal_dir and os.path.isdir(mempal_dir): + try: + log_path = STATE_DIR / "hook.log" + with open(log_path, "a") as log_f: + subprocess.Popen( + [sys.executable, "-m", "mempalace", "mine", mempal_dir], + stdout=log_f, + stderr=log_f, + ) + except OSError: + pass + + +def _parse_claude_code_input(data: dict) -> dict: + """Parse stdin JSON for the claude-code harness.""" + return { + "session_id": _sanitize_session_id(str(data.get("session_id", "unknown"))), + "stop_hook_active": data.get("stop_hook_active", False), + "transcript_path": str(data.get("transcript_path", "")), + } + + +def _parse_harness_input(data: dict, harness: str) -> dict: + """Parse stdin JSON according to the harness type.""" + parsers = { + "claude-code": _parse_claude_code_input, + } + parser = parsers.get(harness) + if parser is None: + print(f"Unknown harness: {harness}", file=sys.stderr) + sys.exit(1) + return parser(data) + + +def hook_stop(data: dict, harness: str): + """Stop hook: block every N messages for auto-save.""" + parsed = _parse_harness_input(data, harness) + session_id = parsed["session_id"] + stop_hook_active = parsed["stop_hook_active"] + transcript_path = parsed["transcript_path"] + + # If already in a save cycle, let through (infinite-loop prevention) + if stop_hook_active in (True, "True", "true"): + _output({}) + return + + # Count human messages + exchange_count = _count_human_messages(transcript_path) + + # Track last save point + STATE_DIR.mkdir(parents=True, exist_ok=True) + last_save_file = STATE_DIR / f"{session_id}_last_save" + last_save = 0 + if last_save_file.is_file(): + try: + last_save = int(last_save_file.read_text().strip()) + except (ValueError, OSError): + last_save = 0 + + since_last = exchange_count - last_save + + _log(f"Session {session_id}: {exchange_count} exchanges, {since_last} since last save") + + if since_last >= SAVE_INTERVAL and exchange_count > 0: + # Update last save point + try: + last_save_file.write_text(str(exchange_count)) + except OSError: + pass + + _log(f"TRIGGERING SAVE at exchange {exchange_count}") + + # Optional: auto-ingest if MEMPAL_DIR is set + _maybe_auto_ingest() + + _output({"decision": "block", "reason": STOP_BLOCK_REASON}) + else: + _output({}) + + +def hook_precompact(data: dict, harness: str): + """Precompact hook: always block with comprehensive save instruction.""" + parsed = _parse_harness_input(data, harness) + session_id = parsed["session_id"] + + _log(f"PRE-COMPACT triggered for session {session_id}") + + # Optional: auto-ingest synchronously before compaction (so memories land first) + mempal_dir = os.environ.get("MEMPAL_DIR", "") + if mempal_dir and os.path.isdir(mempal_dir): + try: + log_path = STATE_DIR / "hook.log" + with open(log_path, "a") as log_f: + subprocess.run( + [sys.executable, "-m", "mempalace", "mine", mempal_dir], + stdout=log_f, + stderr=log_f, + ) + except OSError: + pass + + # Always block -- compaction = save everything + _output({"decision": "block", "reason": PRECOMPACT_BLOCK_REASON}) + + +def run_hook(hook_name: str, harness: str): + """Main entry point: read stdin JSON, dispatch to hook handler.""" + try: + data = json.load(sys.stdin) + except (json.JSONDecodeError, EOFError): + data = {} + + hooks = { + "stop": hook_stop, + "precompact": hook_precompact, + } + + handler = hooks.get(hook_name) + if handler is None: + print(f"Unknown hook: {hook_name}", file=sys.stderr) + sys.exit(1) + + handler(data, harness) diff --git a/mempalace/instructions/help.md b/mempalace/instructions/help.md new file mode 100644 index 0000000..f18c1de --- /dev/null +++ b/mempalace/instructions/help.md @@ -0,0 +1,105 @@ +# MemPalace + +AI memory system. Store everything, find anything. Local, free, no API key. + +--- + +## Slash Commands + +| Command | Description | +|----------------------|--------------------------------| +| /mempalace:init | Install and set up MemPalace | +| /mempalace:search | Search your memories | +| /mempalace:mine | Mine projects and conversations| +| /mempalace:status | Palace overview and stats | +| /mempalace:help | This help message | + +--- + +## MCP Tools (19) + +### Palace (read) +- mempalace_status -- Palace status and stats +- mempalace_list_wings -- List all wings +- mempalace_list_rooms -- List rooms in a wing +- mempalace_get_taxonomy -- Get the full taxonomy tree +- mempalace_search -- Search memories by query +- mempalace_check_duplicate -- Check if a memory already exists +- mempalace_get_aaak_spec -- Get the AAAK specification + +### Palace (write) +- mempalace_add_drawer -- Add a new memory (drawer) +- mempalace_delete_drawer -- Delete a memory (drawer) + +### Knowledge Graph +- mempalace_kg_query -- Query the knowledge graph +- mempalace_kg_add -- Add a knowledge graph entry +- mempalace_kg_invalidate -- Invalidate a knowledge graph entry +- mempalace_kg_timeline -- View knowledge graph timeline +- mempalace_kg_stats -- Knowledge graph statistics + +### Navigation +- mempalace_traverse -- Traverse the palace structure +- mempalace_find_tunnels -- Find cross-wing connections +- mempalace_graph_stats -- Graph connectivity statistics + +### Agent Diary +- mempalace_diary_write -- Write a diary entry +- mempalace_diary_read -- Read diary entries + +--- + +## CLI Commands + + mempalace init Initialize a new palace + mempalace mine Mine a project (default mode) + mempalace mine --mode convos Mine conversation exports + mempalace search "query" Search your memories + mempalace split Split large transcript files + mempalace wake-up Load palace into context + mempalace compress Compress palace storage + mempalace status Show palace status + mempalace repair Rebuild vector index + mempalace hook run Run hook logic (for harness integration) + mempalace instructions Output skill instructions + +--- + +## Auto-Save Hooks + +- Stop hook -- Automatically saves memories every 15 messages. Counts human + messages in the session transcript (skipping command-messages). When the + threshold is reached, blocks the AI with a save instruction. Uses + ~/.mempalace/hook_state/ to track save points per session. If + stop_hook_active is true, passes through to prevent infinite loops. + +- PreCompact hook -- Emergency save before context compaction. Always blocks + with a comprehensive save instruction because compaction means the AI is + about to lose detailed context. + +Hooks read JSON from stdin and output JSON to stdout. They can be invoked via: + + echo '{"session_id":"abc","stop_hook_active":false,"transcript_path":"..."}' | mempalace hook run --hook stop --harness claude-code + +--- + +## Architecture + + Wings (projects/people) + +-- Rooms (topics) + +-- Closets (summaries) + +-- Drawers (verbatim memories) + + Halls connect rooms within a wing. + Tunnels connect rooms across wings. + +The palace is stored locally using ChromaDB for vector search and SQLite for +metadata. No cloud services or API keys required. + +--- + +## Getting Started + +1. /mempalace:init -- Set up your palace +2. /mempalace:mine -- Mine a project or conversation +3. /mempalace:search -- Find what you stored diff --git a/mempalace/instructions/init.md b/mempalace/instructions/init.md new file mode 100644 index 0000000..40fe8fc --- /dev/null +++ b/mempalace/instructions/init.md @@ -0,0 +1,69 @@ +# MemPalace Init + +Guide the user through a complete MemPalace setup. Follow each step in order, +stopping to report errors and attempt remediation before proceeding. + +## Step 1: Check Python version + +Run `python3 --version` (or `python --version` on Windows) and confirm the +version is 3.9 or higher. If Python is not found or the version is too old, +tell the user they need Python 3.9+ installed and stop. + +## Step 2: Check if mempalace is already installed + +Run `pip show mempalace` to see if the package is already present. If it is, +report the installed version and skip to Step 4. + +## Step 3: Install mempalace + +Run `pip install mempalace`. + +### Error handling -- pip failures + +If `pip install mempalace` fails, try these fallbacks in order: + +1. Try `pip3 install mempalace` +2. Try `python -m pip install mempalace` (or `python3 -m pip install mempalace`) +3. If the error mentions missing build tools or compilation failures (commonly + from chromadb or its native dependencies): + - On Linux/macOS: suggest `sudo apt-get install build-essential python3-dev` + (Debian/Ubuntu) or `xcode-select --install` (macOS) + - On Windows: suggest installing Microsoft C++ Build Tools from + https://visualstudio.microsoft.com/visual-cpp-build-tools/ + - Then retry the install command +4. If all attempts fail, report the error clearly and stop. + +## Step 4: Ask for project directory + +Ask the user which project directory they want to initialize with MemPalace. +Offer the current working directory as the default. Wait for their response +before continuing. + +## Step 5: Initialize the palace + +Run `mempalace init ` where `` is the directory from Step 4. + +If this fails, report the error and stop. + +## Step 6: Configure MCP server + +Run the following command to register the MemPalace MCP server with Claude: + + claude mcp add mempalace -- python -m mempalace.mcp_server + +If this fails, report the error but continue to the next step (MCP +configuration can be done manually later). + +## Step 7: Verify installation + +Run `mempalace status` and confirm the output shows a healthy palace. + +If the command fails or reports errors, walk the user through troubleshooting +based on the output. + +## Step 8: Show next steps + +Tell the user setup is complete and suggest these next actions: + +- Use /mempalace:mine to start adding data to their palace +- Use /mempalace:search to query their palace and retrieve stored knowledge diff --git a/mempalace/instructions/mine.md b/mempalace/instructions/mine.md new file mode 100644 index 0000000..ec8c250 --- /dev/null +++ b/mempalace/instructions/mine.md @@ -0,0 +1,64 @@ +# MemPalace Mine + +When the user invokes this skill, follow these steps: + +## 1. Ask what to mine + +Ask the user what they want to mine and where the source data is located. +Clarify: +- Is it a project directory (code, docs, notes)? +- Is it conversation exports (Claude, ChatGPT, Slack)? +- Do they want auto-classification (decisions, milestones, problems)? + +## 2. Choose the mining mode + +There are three mining modes: + +### Project mining + + mempalace mine + +Mines code files, documentation, and notes from a project directory. + +### Conversation mining + + mempalace mine --mode convos + +Mines conversation exports from Claude, ChatGPT, or Slack into the palace. + +### General extraction (auto-classify) + + mempalace mine --mode convos --extract general + +Auto-classifies mined content into decisions, milestones, and problems. + +## 3. Optionally split mega-files first + +If the source directory contains very large files, suggest splitting them +before mining: + + mempalace split [--dry-run] + +Use --dry-run first to preview what will be split without making changes. + +## 4. Optionally tag with a wing + +If the user wants to organize mined content under a specific wing, add the +--wing flag: + + mempalace mine --wing + +## 5. Show progress and results + +Run the selected mining command and display progress as it executes. After +completion, summarize the results including: +- Number of items mined +- Categories or classifications applied +- Any warnings or skipped files + +## 6. Suggest next steps + +After mining completes, suggest the user try: +- /mempalace:search -- search the newly mined content +- /mempalace:status -- check the current state of their palace +- Mine more data from additional sources diff --git a/mempalace/instructions/search.md b/mempalace/instructions/search.md new file mode 100644 index 0000000..0b6a813 --- /dev/null +++ b/mempalace/instructions/search.md @@ -0,0 +1,57 @@ +# MemPalace Search + +When the user wants to search their MemPalace memories, follow these steps: + +## 1. Parse the Search Query + +Extract the core search intent from the user's message. Identify any explicit +or implicit filters: +- Wing -- a top-level category (e.g., "work", "personal", "research") +- Room -- a sub-category within a wing +- Keywords / semantic query -- the actual search terms + +## 2. Determine Wing/Room Filters + +If the user mentions a specific domain, topic area, or context, map it to the +appropriate wing and/or room. If unsure, omit filters to search globally. You +can discover the taxonomy first if needed. + +## 3. Use MCP Tools (Preferred) + +If MCP tools are available, use them in this priority order: + +- mempalace_search(query, wing, room) -- Primary search tool. Pass the semantic + query and any wing/room filters. +- mempalace_list_wings -- Discover all available wings. Use when the user asks + what categories exist or you need to resolve a wing name. +- mempalace_list_rooms(wing) -- List rooms within a specific wing. Use to help + the user navigate or to resolve a room name. +- mempalace_get_taxonomy -- Retrieve the full wing/room/drawer tree. Use when + the user wants an overview of their entire memory structure. +- mempalace_traverse(room) -- Walk the knowledge graph starting from a room. + Use when the user wants to explore connections and related memories. +- mempalace_find_tunnels(wing1, wing2) -- Find cross-wing connections (tunnels) + between two wings. Use when the user asks about relationships between + different knowledge domains. + +## 4. CLI Fallback + +If MCP tools are not available, fall back to the CLI: + + mempalace search "query" [--wing X] [--room Y] + +## 5. Present Results + +When presenting search results: +- Always include source attribution: wing, room, and drawer for each result +- Show relevance or similarity scores if available +- Group results by wing/room when returning multiple hits +- Quote or summarize the memory content clearly + +## 6. Offer Next Steps + +After presenting results, offer the user options to go deeper: +- Drill deeper -- search within a specific room or narrow the query +- Traverse -- explore the knowledge graph from a related room +- Check tunnels -- look for cross-wing connections if the topic spans domains +- Browse taxonomy -- show the full structure for manual exploration diff --git a/mempalace/instructions/status.md b/mempalace/instructions/status.md new file mode 100644 index 0000000..ceb902b --- /dev/null +++ b/mempalace/instructions/status.md @@ -0,0 +1,49 @@ +# MemPalace Status + +Display the current state of the user's memory palace. + +## Step 1: Gather Palace Status + +Check if MCP tools are available (look for mempalace_status in available tools). + +- If MCP is available: Call the mempalace_status tool to retrieve palace state. +- If MCP is not available: Run the CLI command: mempalace status + +## Step 2: Display Wing/Room/Drawer Counts + +Present the palace structure counts clearly: +- Number of wings +- Number of rooms +- Number of drawers +- Total memories stored + +Keep the output concise -- use a brief summary format, not verbose tables. + +## Step 3: Knowledge Graph Stats (MCP only) + +If MCP tools are available, also call: +- mempalace_kg_stats -- for a knowledge graph overview (triple count, entity + count, relationship types) +- mempalace_graph_stats -- for connectivity information (connected components, + average connections per entity) + +Present these alongside the palace counts in a unified summary. + +## Step 4: Suggest Next Actions + +Based on the current state, suggest one relevant action: + +- Empty palace (zero memories): Suggest "Try /mempalace:mine to add data from + files, URLs, or text." +- Has data but no knowledge graph (memories exist but KG stats show zero + triples): Suggest "Consider adding knowledge graph triples for richer + queries." +- Healthy palace (has memories and KG data): Suggest "Use /mempalace:search to + query your memories." + +## Output Style + +- Be concise and informative -- aim for a quick glance, not a report. +- Use short labels and numbers, not prose paragraphs. +- If any step fails or a tool is unavailable, note it briefly and continue + with what is available. diff --git a/mempalace/instructions_cli.py b/mempalace/instructions_cli.py new file mode 100644 index 0000000..239d721 --- /dev/null +++ b/mempalace/instructions_cli.py @@ -0,0 +1,28 @@ +""" +Instruction text output for MemPalace CLI commands. + +Each instruction lives as a .md file in the instructions/ directory +inside the package. The CLI reads and prints the file content. +""" + +import sys +from pathlib import Path + +INSTRUCTIONS_DIR = Path(__file__).parent / "instructions" + +AVAILABLE = ["init", "search", "mine", "help", "status"] + + +def run_instructions(name: str): + """Read and print the instruction .md file for the given name.""" + if name not in AVAILABLE: + print(f"Unknown instructions: {name}", file=sys.stderr) + print(f"Available: {', '.join(sorted(AVAILABLE))}", file=sys.stderr) + sys.exit(1) + + md_path = INSTRUCTIONS_DIR / f"{name}.md" + if not md_path.is_file(): + print(f"Instructions file not found: {md_path}", file=sys.stderr) + sys.exit(1) + + print(md_path.read_text()) From 6b5e869dd517272a177d84dee1984de39d0222d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 11:56:13 +0000 Subject: [PATCH 02/24] chore: sync plugin version to 3.0.0 --- .claude-plugin/plugin.json | 79 +++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index df7f544..f438433 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,32 +2,83 @@ "name": "mempalace", "version": "3.0.0", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", - "author": { "name": "milla-jovovich" }, + "author": { + "name": "milla-jovovich" + }, "license": "MIT", "hooks": { "Stop": "hooks/mempal-stop-hook.sh", "PreCompact": "hooks/mempal-precompact-hook.sh" }, "skills": [ - { "name": "init", "file": "skills/init/SKILL.md" }, - { "name": "search", "file": "skills/search/SKILL.md" }, - { "name": "mine", "file": "skills/mine/SKILL.md" }, - { "name": "help", "file": "skills/help/SKILL.md" }, - { "name": "status", "file": "skills/status/SKILL.md" } + { + "name": "init", + "file": "skills/init/SKILL.md" + }, + { + "name": "search", + "file": "skills/search/SKILL.md" + }, + { + "name": "mine", + "file": "skills/mine/SKILL.md" + }, + { + "name": "help", + "file": "skills/help/SKILL.md" + }, + { + "name": "status", + "file": "skills/status/SKILL.md" + } ], "commands": [ - { "name": "mempalace:help", "description": "Show MemPalace help — available tools, skills, architecture", "skill": "help" }, - { "name": "mempalace:init", "description": "Set up MemPalace — install, configure MCP, onboard", "skill": "init" }, - { "name": "mempalace:search", "description": "Search your memories across the palace", "skill": "search" }, - { "name": "mempalace:mine", "description": "Mine projects and conversations into the palace", "skill": "mine" }, - { "name": "mempalace:status", "description": "Show palace overview — wings, rooms, drawer counts", "skill": "status" } + { + "name": "mempalace:help", + "description": "Show MemPalace help — available tools, skills, architecture", + "skill": "help" + }, + { + "name": "mempalace:init", + "description": "Set up MemPalace — install, configure MCP, onboard", + "skill": "init" + }, + { + "name": "mempalace:search", + "description": "Search your memories across the palace", + "skill": "search" + }, + { + "name": "mempalace:mine", + "description": "Mine projects and conversations into the palace", + "skill": "mine" + }, + { + "name": "mempalace:status", + "description": "Show palace overview — wings, rooms, drawer counts", + "skill": "status" + } ], "mcp": { "mempalace": { "command": "python3", - "args": ["-m", "mempalace.mcp_server"] + "args": [ + "-m", + "mempalace.mcp_server" + ] } }, - "keywords": ["memory", "ai", "rag", "mcp", "chromadb", "palace", "search"], - "repository": { "type": "git", "url": "https://github.com/milla-jovovich/mempalace" } + "keywords": [ + "memory", + "ai", + "rag", + "mcp", + "chromadb", + "palace", + "search" + ], + "repository": { + "type": "git", + "url": "https://github.com/milla-jovovich/mempalace" + } } From 94a41913a3facd973631027e9c2ea90aaf5ca0a5 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 15:37:57 +0300 Subject: [PATCH 03/24] fix: update plugin source path in marketplace.json --- .claude-plugin/marketplace.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0bf890c..c7cd6df 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,7 +7,7 @@ "plugins": [ { "name": "mempalace", - "source": ".", + "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", "version": "3.0.0", "author": { From 2f83518415eda1591ba6e7930fb17efe34dead6f Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 18:37:46 +0300 Subject: [PATCH 04/24] refactor: simplify plugin.json by removing unused hooks and commands --- .claude-plugin/plugin.json | 59 ++------------------------------------ 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index f438433..6918b43 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -6,59 +6,7 @@ "name": "milla-jovovich" }, "license": "MIT", - "hooks": { - "Stop": "hooks/mempal-stop-hook.sh", - "PreCompact": "hooks/mempal-precompact-hook.sh" - }, - "skills": [ - { - "name": "init", - "file": "skills/init/SKILL.md" - }, - { - "name": "search", - "file": "skills/search/SKILL.md" - }, - { - "name": "mine", - "file": "skills/mine/SKILL.md" - }, - { - "name": "help", - "file": "skills/help/SKILL.md" - }, - { - "name": "status", - "file": "skills/status/SKILL.md" - } - ], - "commands": [ - { - "name": "mempalace:help", - "description": "Show MemPalace help — available tools, skills, architecture", - "skill": "help" - }, - { - "name": "mempalace:init", - "description": "Set up MemPalace — install, configure MCP, onboard", - "skill": "init" - }, - { - "name": "mempalace:search", - "description": "Search your memories across the palace", - "skill": "search" - }, - { - "name": "mempalace:mine", - "description": "Mine projects and conversations into the palace", - "skill": "mine" - }, - { - "name": "mempalace:status", - "description": "Show palace overview — wings, rooms, drawer counts", - "skill": "status" - } - ], + "skills": "./skills/", "mcp": { "mempalace": { "command": "python3", @@ -77,8 +25,5 @@ "palace", "search" ], - "repository": { - "type": "git", - "url": "https://github.com/milla-jovovich/mempalace" - } + "repository": "https://github.com/milla-jovovich/mempalace" } From c9d395585906058fb27f8bc70bdda9421456e305 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 18:41:22 +0300 Subject: [PATCH 05/24] refactor: update skill names to include 'mempalace:' prefix for consistency --- .claude-plugin/skills/help/SKILL.md | 2 +- .claude-plugin/skills/init/SKILL.md | 2 +- .claude-plugin/skills/mine/SKILL.md | 2 +- .claude-plugin/skills/search/SKILL.md | 2 +- .claude-plugin/skills/status/SKILL.md | 2 +- .github/workflows/bump-plugin-version.yml | 5 ++++- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/skills/help/SKILL.md b/.claude-plugin/skills/help/SKILL.md index d389a1b..53c0acc 100644 --- a/.claude-plugin/skills/help/SKILL.md +++ b/.claude-plugin/skills/help/SKILL.md @@ -1,5 +1,5 @@ --- -name: help +name: mempalace:help description: Show comprehensive MemPalace help — available skills, MCP tools, CLI commands, hooks, and architecture. --- diff --git a/.claude-plugin/skills/init/SKILL.md b/.claude-plugin/skills/init/SKILL.md index a255711..b6a1c99 100644 --- a/.claude-plugin/skills/init/SKILL.md +++ b/.claude-plugin/skills/init/SKILL.md @@ -1,5 +1,5 @@ --- -name: init +name: mempalace:init description: Set up MemPalace — install the package, initialize a palace, configure MCP server, and verify everything works. --- diff --git a/.claude-plugin/skills/mine/SKILL.md b/.claude-plugin/skills/mine/SKILL.md index 8896ed7..333084a 100644 --- a/.claude-plugin/skills/mine/SKILL.md +++ b/.claude-plugin/skills/mine/SKILL.md @@ -1,5 +1,5 @@ --- -name: mine +name: mempalace:mine description: Mine projects and conversations into the MemPalace. Supports project files, conversation exports, and auto-classification. --- diff --git a/.claude-plugin/skills/search/SKILL.md b/.claude-plugin/skills/search/SKILL.md index 6ad2d24..5884e98 100644 --- a/.claude-plugin/skills/search/SKILL.md +++ b/.claude-plugin/skills/search/SKILL.md @@ -1,5 +1,5 @@ --- -name: search +name: mempalace:search description: Search your memories across the MemPalace using semantic search with wing/room filtering. --- diff --git a/.claude-plugin/skills/status/SKILL.md b/.claude-plugin/skills/status/SKILL.md index dca3646..a244753 100644 --- a/.claude-plugin/skills/status/SKILL.md +++ b/.claude-plugin/skills/status/SKILL.md @@ -1,5 +1,5 @@ --- -name: status +name: mempalace:status description: Show the current state of your memory palace — wings, rooms, drawer counts, and suggestions. --- diff --git a/.github/workflows/bump-plugin-version.yml b/.github/workflows/bump-plugin-version.yml index 8735f51..fc78d76 100644 --- a/.github/workflows/bump-plugin-version.yml +++ b/.github/workflows/bump-plugin-version.yml @@ -31,4 +31,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add .claude-plugin/plugin.json .claude-plugin/marketplace.json - git diff --staged --quiet || git commit -m "chore: sync plugin version to ${{ steps.version.outputs.version }}" && git push + if ! git diff --staged --quiet; then + git commit -m "chore: sync plugin version to ${{ steps.version.outputs.version }}" + git push + fi From 94b39cbfe9dd6174ab5cfcd310d1d8d6293ffc9e Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 18:43:53 +0300 Subject: [PATCH 06/24] refactor: rename workflow and improve version bumping process --- .github/workflows/bump-plugin-version.yml | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/bump-plugin-version.yml b/.github/workflows/bump-plugin-version.yml index fc78d76..d91c026 100644 --- a/.github/workflows/bump-plugin-version.yml +++ b/.github/workflows/bump-plugin-version.yml @@ -1,37 +1,43 @@ -name: Sync Plugin Version +name: Bump Version on: push: branches: [main] jobs: - sync-version: + bump-version: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v6 - - name: Extract version from version.py - id: version + - name: Bump patch version run: | - VERSION=$(python3 -c "exec(open('mempalace/version.py').read()); print(__version__)") - echo "version=$VERSION" >> "$GITHUB_OUTPUT" + CURRENT=$(python3 -c "exec(open('mempalace/version.py').read()); print(__version__)") + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" + PATCH=$((PATCH + 1)) + NEW="${MAJOR}.${MINOR}.${PATCH}" + echo "__version__ = \"${NEW}\"" > mempalace/version.py + # Prepend docstring + sed -i '1i"""Single source of truth for the MemPalace package version."""\n' mempalace/version.py + echo "version=$NEW" >> "$GITHUB_OUTPUT" + id: version - - name: Update plugin.json + - name: Sync plugin.json run: | jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' .claude-plugin/plugin.json > tmp.json && mv tmp.json .claude-plugin/plugin.json - - name: Update marketplace.json + - name: Sync marketplace.json run: | jq --arg v "${{ steps.version.outputs.version }}" '.plugins[0].version = $v' .claude-plugin/marketplace.json > tmp.json && mv tmp.json .claude-plugin/marketplace.json - - name: Commit if changed + - name: Commit and push run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add .claude-plugin/plugin.json .claude-plugin/marketplace.json + git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json if ! git diff --staged --quiet; then - git commit -m "chore: sync plugin version to ${{ steps.version.outputs.version }}" + git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" git push fi From a788af8919a555efd0dcbb24b4b64b5a08f474e7 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 18:49:45 +0300 Subject: [PATCH 07/24] feat: add command documentation for help, init, mine, search, and status --- .claude-plugin/commands/help.md | 6 ++++ .claude-plugin/commands/init.md | 6 ++++ .claude-plugin/commands/mine.md | 7 +++++ .claude-plugin/commands/search.md | 7 +++++ .claude-plugin/commands/status.md | 6 ++++ .claude-plugin/plugin.json | 8 +++++- .claude-plugin/skills/help/SKILL.md | 12 -------- .claude-plugin/skills/init/SKILL.md | 18 ------------ .claude-plugin/skills/mempalace/SKILL.md | 35 ++++++++++++++++++++++++ .claude-plugin/skills/mine/SKILL.md | 12 -------- .claude-plugin/skills/search/SKILL.md | 12 -------- .claude-plugin/skills/status/SKILL.md | 12 -------- 12 files changed, 74 insertions(+), 67 deletions(-) create mode 100644 .claude-plugin/commands/help.md create mode 100644 .claude-plugin/commands/init.md create mode 100644 .claude-plugin/commands/mine.md create mode 100644 .claude-plugin/commands/search.md create mode 100644 .claude-plugin/commands/status.md delete mode 100644 .claude-plugin/skills/help/SKILL.md delete mode 100644 .claude-plugin/skills/init/SKILL.md create mode 100644 .claude-plugin/skills/mempalace/SKILL.md delete mode 100644 .claude-plugin/skills/mine/SKILL.md delete mode 100644 .claude-plugin/skills/search/SKILL.md delete mode 100644 .claude-plugin/skills/status/SKILL.md diff --git a/.claude-plugin/commands/help.md b/.claude-plugin/commands/help.md new file mode 100644 index 0000000..d1c1418 --- /dev/null +++ b/.claude-plugin/commands/help.md @@ -0,0 +1,6 @@ +--- +description: Show comprehensive MemPalace help — available skills, MCP tools, CLI commands, hooks, and architecture. +allowed-tools: Bash, Read +--- + +Invoke the mempalace skill (using the Skill tool) with the `help` command, then follow its instructions. diff --git a/.claude-plugin/commands/init.md b/.claude-plugin/commands/init.md new file mode 100644 index 0000000..a96a64a --- /dev/null +++ b/.claude-plugin/commands/init.md @@ -0,0 +1,6 @@ +--- +description: Set up MemPalace — install the package, initialize a palace, configure MCP server, and verify everything works. +allowed-tools: Bash, Read, Write, Edit, Glob, Grep +--- + +Invoke the mempalace skill (using the Skill tool) with the `init` command, then follow its instructions. diff --git a/.claude-plugin/commands/mine.md b/.claude-plugin/commands/mine.md new file mode 100644 index 0000000..6fa554b --- /dev/null +++ b/.claude-plugin/commands/mine.md @@ -0,0 +1,7 @@ +--- +description: Mine projects and conversations into the MemPalace. Supports project files, conversation exports, and auto-classification. +argument-hint: Path to project or conversation export to mine. +allowed-tools: Bash, Read, Write, Edit, Glob, Grep +--- + +Invoke the mempalace skill (using the Skill tool) with the `mine` command, then follow its instructions. diff --git a/.claude-plugin/commands/search.md b/.claude-plugin/commands/search.md new file mode 100644 index 0000000..092d8bb --- /dev/null +++ b/.claude-plugin/commands/search.md @@ -0,0 +1,7 @@ +--- +description: Search your memories across the MemPalace using semantic search with wing/room filtering. +argument-hint: Search query, optionally with wing/room filters. +allowed-tools: Bash, Read +--- + +Invoke the mempalace skill (using the Skill tool) with the `search` command, then follow its instructions. diff --git a/.claude-plugin/commands/status.md b/.claude-plugin/commands/status.md new file mode 100644 index 0000000..66732e0 --- /dev/null +++ b/.claude-plugin/commands/status.md @@ -0,0 +1,6 @@ +--- +description: Show the current state of your memory palace — wings, rooms, drawer counts, and suggestions. +allowed-tools: Bash, Read +--- + +Invoke the mempalace skill (using the Skill tool) with the `status` command, then follow its instructions. diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 6918b43..974e9bf 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -6,7 +6,13 @@ "name": "milla-jovovich" }, "license": "MIT", - "skills": "./skills/", + "commands": [], + "skills": [ + { + "name": "mempalace", + "file": "skills/mempalace/SKILL.md" + } + ], "mcp": { "mempalace": { "command": "python3", diff --git a/.claude-plugin/skills/help/SKILL.md b/.claude-plugin/skills/help/SKILL.md deleted file mode 100644 index 53c0acc..0000000 --- a/.claude-plugin/skills/help/SKILL.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: mempalace:help -description: Show comprehensive MemPalace help — available skills, MCP tools, CLI commands, hooks, and architecture. ---- - -Run the following command and display its output to the user: - -```bash -mempalace instructions help -``` - -Display the output as-is — it's pre-formatted markdown. diff --git a/.claude-plugin/skills/init/SKILL.md b/.claude-plugin/skills/init/SKILL.md deleted file mode 100644 index b6a1c99..0000000 --- a/.claude-plugin/skills/init/SKILL.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: mempalace:init -description: Set up MemPalace — install the package, initialize a palace, configure MCP server, and verify everything works. ---- - -Run the following command to get setup instructions, then follow them step by step: - -```bash -mempalace instructions init -``` - -If the command fails (mempalace not installed yet), first install it: - -```bash -pip install mempalace -``` - -Then run the instructions command again and follow the output. diff --git a/.claude-plugin/skills/mempalace/SKILL.md b/.claude-plugin/skills/mempalace/SKILL.md new file mode 100644 index 0000000..ae60fca --- /dev/null +++ b/.claude-plugin/skills/mempalace/SKILL.md @@ -0,0 +1,35 @@ +--- +name: mempalace +description: MemPalace — mine projects and conversations into a searchable memory palace. Use when asked about mempalace, memory palace, mining memories, searching memories, or palace setup. +allowed-tools: Bash, Read, Write, Edit, Glob, Grep +--- + +# MemPalace + +A searchable memory palace for AI — mine projects and conversations, then search them semantically. + +## Prerequisites + +Ensure `mempalace` is installed: + +```bash +mempalace --version +``` + +If not installed: + +```bash +pip install mempalace +``` + +## Usage + +MemPalace provides dynamic instructions via the CLI. To get instructions for any operation: + +```bash +mempalace instructions +``` + +Where `` is one of: `help`, `init`, `mine`, `search`, `status`. + +Run the appropriate instructions command, then follow the returned instructions step by step. diff --git a/.claude-plugin/skills/mine/SKILL.md b/.claude-plugin/skills/mine/SKILL.md deleted file mode 100644 index 333084a..0000000 --- a/.claude-plugin/skills/mine/SKILL.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: mempalace:mine -description: Mine projects and conversations into the MemPalace. Supports project files, conversation exports, and auto-classification. ---- - -Run the following command to get mining instructions, then follow them: - -```bash -mempalace instructions mine -``` - -Follow the returned instructions to mine the user's data. diff --git a/.claude-plugin/skills/search/SKILL.md b/.claude-plugin/skills/search/SKILL.md deleted file mode 100644 index 5884e98..0000000 --- a/.claude-plugin/skills/search/SKILL.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: mempalace:search -description: Search your memories across the MemPalace using semantic search with wing/room filtering. ---- - -Run the following command to get search instructions, then follow them: - -```bash -mempalace instructions search -``` - -Follow the returned instructions to execute the user's search query. diff --git a/.claude-plugin/skills/status/SKILL.md b/.claude-plugin/skills/status/SKILL.md deleted file mode 100644 index a244753..0000000 --- a/.claude-plugin/skills/status/SKILL.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: mempalace:status -description: Show the current state of your memory palace — wings, rooms, drawer counts, and suggestions. ---- - -Run the following command to get status instructions, then follow them: - -```bash -mempalace instructions status -``` - -Follow the returned instructions to display the palace status. From d4d328a545beb45ccba7f18d25b5ff51f2dd39a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 15:50:39 +0000 Subject: [PATCH 08/24] chore: bump version to 3.0.1 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c7cd6df..7dfe174 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.0", + "version": "3.0.1", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 974e9bf..482d97f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.0", + "version": "3.0.1", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index 08a910f..e5602d1 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.0" +__version__ = "3.0.1" From aaa6f5944670b92e4db2a611666c5b5610703cb4 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 18:56:00 +0300 Subject: [PATCH 09/24] refactor: remove unused skills section from plugin.json --- .claude-plugin/plugin.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 482d97f..c2e28fc 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -7,12 +7,6 @@ }, "license": "MIT", "commands": [], - "skills": [ - { - "name": "mempalace", - "file": "skills/mempalace/SKILL.md" - } - ], "mcp": { "mempalace": { "command": "python3", From 61924ea018bd9e0973585deae28f19351e8ce9ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 15:56:25 +0000 Subject: [PATCH 10/24] chore: bump version to 3.0.2 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 7dfe174..4ee6b5f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.1", + "version": "3.0.2", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index c2e28fc..482e77a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.1", + "version": "3.0.2", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index e5602d1..dba0e3f 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.1" +__version__ = "3.0.2" From 50c3db383a3fb8827e63f1477b78b922febf1832 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 19:10:44 +0300 Subject: [PATCH 11/24] feat: add Codex plugin support with hooks, commands, and documentation --- .agents/plugins/marketplace.json | 20 +++++ .claude-plugin/.mcp.json | 9 +++ .claude-plugin/plugin.json | 2 +- .codex-plugin/README.md | 73 +++++++++++++++++++ .codex-plugin/hooks.json | 37 ++++++++++ .codex-plugin/hooks/mempal-precompact-hook.sh | 15 ++++ .../hooks/mempal-session-start-hook.sh | 15 ++++ .codex-plugin/hooks/mempal-stop-hook.sh | 15 ++++ .codex-plugin/plugin.json | 35 +++++++++ .codex-plugin/skills/help/SKILL.md | 13 ++++ .codex-plugin/skills/init/SKILL.md | 13 ++++ .codex-plugin/skills/mine/SKILL.md | 13 ++++ .codex-plugin/skills/search/SKILL.md | 13 ++++ .codex-plugin/skills/status/SKILL.md | 13 ++++ .github/workflows/bump-plugin-version.yml | 6 +- mempalace/cli.py | 4 +- mempalace/hooks_cli.py | 31 +++++++- 17 files changed, 320 insertions(+), 7 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .claude-plugin/.mcp.json create mode 100644 .codex-plugin/README.md create mode 100644 .codex-plugin/hooks.json create mode 100644 .codex-plugin/hooks/mempal-precompact-hook.sh create mode 100644 .codex-plugin/hooks/mempal-session-start-hook.sh create mode 100644 .codex-plugin/hooks/mempal-stop-hook.sh create mode 100644 .codex-plugin/plugin.json create mode 100644 .codex-plugin/skills/help/SKILL.md create mode 100644 .codex-plugin/skills/init/SKILL.md create mode 100644 .codex-plugin/skills/mine/SKILL.md create mode 100644 .codex-plugin/skills/search/SKILL.md create mode 100644 .codex-plugin/skills/status/SKILL.md diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..58223a0 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "mempalace", + "interface": { + "displayName": "MemPalace" + }, + "plugins": [ + { + "name": "mempalace", + "source": { + "source": "local", + "path": "./.codex-plugin" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "NONE" + }, + "category": "Coding" + } + ] +} diff --git a/.claude-plugin/.mcp.json b/.claude-plugin/.mcp.json new file mode 100644 index 0000000..b1e81ed --- /dev/null +++ b/.claude-plugin/.mcp.json @@ -0,0 +1,9 @@ +{ + "mempalace": { + "command": "python3", + "args": [ + "-m", + "mempalace.mcp_server" + ] + } +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 482e77a..488f6f3 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -7,7 +7,7 @@ }, "license": "MIT", "commands": [], - "mcp": { + "mcpServers": { "mempalace": { "command": "python3", "args": [ diff --git a/.codex-plugin/README.md b/.codex-plugin/README.md new file mode 100644 index 0000000..a7ec1de --- /dev/null +++ b/.codex-plugin/README.md @@ -0,0 +1,73 @@ +# MemPalace - Codex CLI Plugin + +Give your AI a persistent memory -- mine projects and conversations into a searchable palace backed by ChromaDB, with 19 MCP tools, auto-save hooks, and guided skills. + +## Prerequisites + +- Python 3.10+ +- Codex CLI installed and configured +- `pip install mempalace` + +## Installation + +### Local Install + +1. Copy or symlink the `.codex-plugin` directory into your project root: + +```bash +cp -r .codex-plugin /path/to/your/project/.codex-plugin +``` + +2. Verify the plugin is detected: + +```bash +codex --plugins +``` + +3. Initialize your palace: + +```bash +codex /init +``` + +### Git Install + +1. Clone the MemPalace repository: + +```bash +git clone https://github.com/milla-jovovich/mempalace.git +cd mempalace +``` + +2. Install the Python package: + +```bash +pip install -e . +``` + +3. The `.codex-plugin` directory is already in the repo root. Codex CLI will detect it automatically when you run Codex from inside the repository. + +4. Initialize your palace: + +```bash +codex /init +``` + +## Available Skills + +| Skill | Description | +|-------|-------------| +| `/help` | Show available commands and usage tips | +| `/init` | Initialize a new memory palace | +| `/search` | Semantic search across all mined memories | +| `/mine` | Mine a project or conversation into your palace | +| `/status` | Show palace status, room counts, and health | + +## Hooks + +The plugin includes an auto-save hook that runs on session stop, automatically preserving conversation context into your palace. + +## Support + +- Repository: https://github.com/milla-jovovich/mempalace +- Issues: https://github.com/milla-jovovich/mempalace/issues diff --git a/.codex-plugin/hooks.json b/.codex-plugin/hooks.json new file mode 100644 index 0000000..1c235f2 --- /dev/null +++ b/.codex-plugin/hooks.json @@ -0,0 +1,37 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "./hooks/mempal-session-start-hook.sh" + } + ] + } + ], + "Stop": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "./hooks/mempal-stop-hook.sh" + } + ] + } + ], + "PreCompact": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "./hooks/mempal-precompact-hook.sh" + } + ] + } + ] + } +} diff --git a/.codex-plugin/hooks/mempal-precompact-hook.sh b/.codex-plugin/hooks/mempal-precompact-hook.sh new file mode 100644 index 0000000..46af49d --- /dev/null +++ b/.codex-plugin/hooks/mempal-precompact-hook.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + +# Capture stdin (hook input from Codex) +INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-precompact-hook-$$.json") +cat > "$INPUT_FILE" + +# Pipe to Python CLI with codex harness +cat "$INPUT_FILE" | python3 -m mempalace hook run --hook precompact --harness codex +EXIT_CODE=$? + +# Cleanup +rm -f "$INPUT_FILE" 2>/dev/null +exit $EXIT_CODE diff --git a/.codex-plugin/hooks/mempal-session-start-hook.sh b/.codex-plugin/hooks/mempal-session-start-hook.sh new file mode 100644 index 0000000..7c7b5c2 --- /dev/null +++ b/.codex-plugin/hooks/mempal-session-start-hook.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + +# Capture stdin (hook input from Codex) +INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-session-start-hook-$$.json") +cat > "$INPUT_FILE" + +# Pipe to Python CLI with codex harness +cat "$INPUT_FILE" | python3 -m mempalace hook run --hook session-start --harness codex +EXIT_CODE=$? + +# Cleanup +rm -f "$INPUT_FILE" 2>/dev/null +exit $EXIT_CODE diff --git a/.codex-plugin/hooks/mempal-stop-hook.sh b/.codex-plugin/hooks/mempal-stop-hook.sh new file mode 100644 index 0000000..2d38932 --- /dev/null +++ b/.codex-plugin/hooks/mempal-stop-hook.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" + +# Capture stdin (hook input from Codex) +INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-stop-hook-$$.json") +cat > "$INPUT_FILE" + +# Pipe to Python CLI with codex harness +cat "$INPUT_FILE" | python3 -m mempalace hook run --hook stop --harness codex +EXIT_CODE=$? + +# Cleanup +rm -f "$INPUT_FILE" 2>/dev/null +exit $EXIT_CODE diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..f040014 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,35 @@ +{ + "name": "mempalace", + "version": "3.0.0", + "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", + "author": { "name": "milla-jovovich" }, + "homepage": "https://github.com/milla-jovovich/mempalace", + "repository": "https://github.com/milla-jovovich/mempalace", + "license": "MIT", + "keywords": ["memory", "ai", "rag", "mcp", "chromadb", "palace", "search"], + "skills": "./skills/", + "hooks": "./hooks.json", + "mcpServers": { + "mempalace": { + "command": "python3", + "args": ["-m", "mempalace.mcp_server"] + } + }, + "interface": { + "displayName": "MemPalace", + "shortDescription": "AI memory system for Codex", + "longDescription": "Give your AI a persistent memory — mine projects and conversations into a searchable palace backed by ChromaDB, with 19 MCP tools, auto-save hooks, and guided skills.", + "developerName": "milla-jovovich", + "category": "Coding", + "capabilities": ["Interactive", "Read", "Write"], + "websiteURL": "https://github.com/milla-jovovich/mempalace", + "privacyPolicyURL": "https://github.com/milla-jovovich/mempalace", + "termsOfServiceURL": "https://github.com/milla-jovovich/mempalace", + "defaultPrompt": [ + "Search my memories for recent decisions", + "Mine this project into my memory palace", + "Show my palace status and room counts" + ], + "brandColor": "#7C3AED" + } +} diff --git a/.codex-plugin/skills/help/SKILL.md b/.codex-plugin/skills/help/SKILL.md new file mode 100644 index 0000000..d0e8f43 --- /dev/null +++ b/.codex-plugin/skills/help/SKILL.md @@ -0,0 +1,13 @@ +--- +name: help +description: Show MemPalace help — available commands, usage tips, and getting started guidance. +allowed-tools: Bash, Read +--- + +# MemPalace Help + +Run the following command and follow the returned instructions step by step: + +```bash +mempalace instructions help +``` diff --git a/.codex-plugin/skills/init/SKILL.md b/.codex-plugin/skills/init/SKILL.md new file mode 100644 index 0000000..cc0e2f9 --- /dev/null +++ b/.codex-plugin/skills/init/SKILL.md @@ -0,0 +1,13 @@ +--- +name: init +description: Initialize a new MemPalace — guided setup for your AI memory palace with ChromaDB backend. +allowed-tools: Bash, Read, Write, Edit +--- + +# MemPalace Init + +Run the following command and follow the returned instructions step by step: + +```bash +mempalace instructions init +``` diff --git a/.codex-plugin/skills/mine/SKILL.md b/.codex-plugin/skills/mine/SKILL.md new file mode 100644 index 0000000..1a94e29 --- /dev/null +++ b/.codex-plugin/skills/mine/SKILL.md @@ -0,0 +1,13 @@ +--- +name: mine +description: Mine a project or conversation into your MemPalace — extract and store memories for later retrieval. +allowed-tools: Bash, Read, Glob, Grep +--- + +# MemPalace Mine + +Run the following command and follow the returned instructions step by step: + +```bash +mempalace instructions mine +``` diff --git a/.codex-plugin/skills/search/SKILL.md b/.codex-plugin/skills/search/SKILL.md new file mode 100644 index 0000000..4d5bf4b --- /dev/null +++ b/.codex-plugin/skills/search/SKILL.md @@ -0,0 +1,13 @@ +--- +name: search +description: Search your MemPalace — semantic search across all mined memories, projects, and conversations. +allowed-tools: Bash, Read +--- + +# MemPalace Search + +Run the following command and follow the returned instructions step by step: + +```bash +mempalace instructions search +``` diff --git a/.codex-plugin/skills/status/SKILL.md b/.codex-plugin/skills/status/SKILL.md new file mode 100644 index 0000000..617d3be --- /dev/null +++ b/.codex-plugin/skills/status/SKILL.md @@ -0,0 +1,13 @@ +--- +name: status +description: Show MemPalace status — room counts, storage usage, and palace health. +allowed-tools: Bash, Read +--- + +# MemPalace Status + +Run the following command and follow the returned instructions step by step: + +```bash +mempalace instructions status +``` diff --git a/.github/workflows/bump-plugin-version.yml b/.github/workflows/bump-plugin-version.yml index d91c026..43adc8b 100644 --- a/.github/workflows/bump-plugin-version.yml +++ b/.github/workflows/bump-plugin-version.yml @@ -32,11 +32,15 @@ jobs: run: | jq --arg v "${{ steps.version.outputs.version }}" '.plugins[0].version = $v' .claude-plugin/marketplace.json > tmp.json && mv tmp.json .claude-plugin/marketplace.json + - name: Sync codex plugin.json + run: | + jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' .codex-plugin/plugin.json > tmp.json && mv tmp.json .codex-plugin/plugin.json + - name: Commit and push run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json + git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json .codex-plugin/plugin.json if ! git diff --staged --quiet; then git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" git push diff --git a/mempalace/cli.py b/mempalace/cli.py index 9d2465b..0a24abf 100644 --- a/mempalace/cli.py +++ b/mempalace/cli.py @@ -475,13 +475,13 @@ def main(): p_hook_run.add_argument( "--hook", required=True, - choices=["stop", "precompact"], + choices=["session-start", "stop", "precompact"], help="Hook name to run", ) p_hook_run.add_argument( "--harness", required=True, - choices=["claude-code"], + choices=["claude-code", "codex"], help="Harness type (determines stdin JSON format)", ) diff --git a/mempalace/hooks_cli.py b/mempalace/hooks_cli.py index 6e0ec0a..6b4bb0f 100644 --- a/mempalace/hooks_cli.py +++ b/mempalace/hooks_cli.py @@ -1,9 +1,9 @@ """ -Hook logic for MemPalace — Python implementation of stop and precompact hooks. +Hook logic for MemPalace — Python implementation of session-start, stop, and precompact hooks. Reads JSON from stdin, outputs JSON to stdout. -Supported hooks: stop, precompact -Supported harnesses: claude-code (extensible to cursor, gemini, etc.) +Supported hooks: session-start, stop, precompact +Supported harnesses: claude-code, codex (extensible to cursor, gemini, etc.) """ import json @@ -105,10 +105,20 @@ def _parse_claude_code_input(data: dict) -> dict: } +def _parse_codex_input(data: dict) -> dict: + """Parse stdin JSON for the codex harness.""" + return { + "session_id": _sanitize_session_id(str(data.get("session_id", "unknown"))), + "stop_hook_active": data.get("stop_hook_active", False), + "transcript_path": str(data.get("transcript_path", "")), + } + + def _parse_harness_input(data: dict, harness: str) -> dict: """Parse stdin JSON according to the harness type.""" parsers = { "claude-code": _parse_claude_code_input, + "codex": _parse_codex_input, } parser = parsers.get(harness) if parser is None: @@ -163,6 +173,20 @@ def hook_stop(data: dict, harness: str): _output({}) +def hook_session_start(data: dict, harness: str): + """Session start hook: initialize session tracking state.""" + parsed = _parse_harness_input(data, harness) + session_id = parsed["session_id"] + + _log(f"SESSION START for session {session_id}") + + # Initialize session state directory + STATE_DIR.mkdir(parents=True, exist_ok=True) + + # Pass through — no blocking on session start + _output({}) + + def hook_precompact(data: dict, harness: str): """Precompact hook: always block with comprehensive save instruction.""" parsed = _parse_harness_input(data, harness) @@ -196,6 +220,7 @@ def run_hook(hook_name: str, harness: str): data = {} hooks = { + "session-start": hook_session_start, "stop": hook_stop, "precompact": hook_precompact, } From b23210831440969e821ae83963df39f5423deb1e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 16:10:56 +0000 Subject: [PATCH 12/24] chore: bump version to 3.0.3 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 27 ++++++++++++++++++++++----- mempalace/version.py | 2 +- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 4ee6b5f..3d45661 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.2", + "version": "3.0.3", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 488f6f3..f0a3505 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.2", + "version": "3.0.3", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index f040014..586141c 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,18 +1,31 @@ { "name": "mempalace", - "version": "3.0.0", + "version": "3.0.3", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", - "author": { "name": "milla-jovovich" }, + "author": { + "name": "milla-jovovich" + }, "homepage": "https://github.com/milla-jovovich/mempalace", "repository": "https://github.com/milla-jovovich/mempalace", "license": "MIT", - "keywords": ["memory", "ai", "rag", "mcp", "chromadb", "palace", "search"], + "keywords": [ + "memory", + "ai", + "rag", + "mcp", + "chromadb", + "palace", + "search" + ], "skills": "./skills/", "hooks": "./hooks.json", "mcpServers": { "mempalace": { "command": "python3", - "args": ["-m", "mempalace.mcp_server"] + "args": [ + "-m", + "mempalace.mcp_server" + ] } }, "interface": { @@ -21,7 +34,11 @@ "longDescription": "Give your AI a persistent memory — mine projects and conversations into a searchable palace backed by ChromaDB, with 19 MCP tools, auto-save hooks, and guided skills.", "developerName": "milla-jovovich", "category": "Coding", - "capabilities": ["Interactive", "Read", "Write"], + "capabilities": [ + "Interactive", + "Read", + "Write" + ], "websiteURL": "https://github.com/milla-jovovich/mempalace", "privacyPolicyURL": "https://github.com/milla-jovovich/mempalace", "termsOfServiceURL": "https://github.com/milla-jovovich/mempalace", diff --git a/mempalace/version.py b/mempalace/version.py index dba0e3f..065663a 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.2" +__version__ = "3.0.3" From 8ac0026d2468dd198a4a66dbb1c71a3fb5ec052a Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 19:15:25 +0300 Subject: [PATCH 13/24] fix: update command descriptions to specify 'generic mempalace skill' --- .claude-plugin/commands/help.md | 2 +- .claude-plugin/commands/init.md | 2 +- .claude-plugin/commands/mine.md | 2 +- .claude-plugin/commands/search.md | 2 +- .claude-plugin/commands/status.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/commands/help.md b/.claude-plugin/commands/help.md index d1c1418..2f56339 100644 --- a/.claude-plugin/commands/help.md +++ b/.claude-plugin/commands/help.md @@ -3,4 +3,4 @@ description: Show comprehensive MemPalace help — available skills, MCP tools, allowed-tools: Bash, Read --- -Invoke the mempalace skill (using the Skill tool) with the `help` command, then follow its instructions. +Invoke the generic mempalace skill (using the Skill tool) with the `help` command, then follow its instructions. diff --git a/.claude-plugin/commands/init.md b/.claude-plugin/commands/init.md index a96a64a..ff27562 100644 --- a/.claude-plugin/commands/init.md +++ b/.claude-plugin/commands/init.md @@ -3,4 +3,4 @@ description: Set up MemPalace — install the package, initialize a palace, conf allowed-tools: Bash, Read, Write, Edit, Glob, Grep --- -Invoke the mempalace skill (using the Skill tool) with the `init` command, then follow its instructions. +Invoke the generic mempalace skill (using the Skill tool) with the `init` command, then follow its instructions. diff --git a/.claude-plugin/commands/mine.md b/.claude-plugin/commands/mine.md index 6fa554b..edac2b0 100644 --- a/.claude-plugin/commands/mine.md +++ b/.claude-plugin/commands/mine.md @@ -4,4 +4,4 @@ argument-hint: Path to project or conversation export to mine. allowed-tools: Bash, Read, Write, Edit, Glob, Grep --- -Invoke the mempalace skill (using the Skill tool) with the `mine` command, then follow its instructions. +Invoke the generic mempalace skill (using the Skill tool) with the `mine` command, then follow its instructions. diff --git a/.claude-plugin/commands/search.md b/.claude-plugin/commands/search.md index 092d8bb..9fe8c34 100644 --- a/.claude-plugin/commands/search.md +++ b/.claude-plugin/commands/search.md @@ -4,4 +4,4 @@ argument-hint: Search query, optionally with wing/room filters. allowed-tools: Bash, Read --- -Invoke the mempalace skill (using the Skill tool) with the `search` command, then follow its instructions. +Invoke the generic mempalace skill (using the Skill tool) with the `search` command, then follow its instructions. diff --git a/.claude-plugin/commands/status.md b/.claude-plugin/commands/status.md index 66732e0..a87f27b 100644 --- a/.claude-plugin/commands/status.md +++ b/.claude-plugin/commands/status.md @@ -3,4 +3,4 @@ description: Show the current state of your memory palace — wings, rooms, draw allowed-tools: Bash, Read --- -Invoke the mempalace skill (using the Skill tool) with the `status` command, then follow its instructions. +Invoke the generic mempalace skill (using the Skill tool) with the `status` command, then follow its instructions. From 4de5229f10bbf9d31a988feeeecd52f5df4a1c8d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 16:15:37 +0000 Subject: [PATCH 14/24] chore: bump version to 3.0.4 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3d45661..25173c3 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.3", + "version": "3.0.4", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index f0a3505..2176807 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.3", + "version": "3.0.4", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 586141c..5d4fefc 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.3", + "version": "3.0.4", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index 065663a..30d60af 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.3" +__version__ = "3.0.4" From 34c8f8c1b8e70178c15e335a9729c9cf9d0035a1 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 19:33:16 +0300 Subject: [PATCH 15/24] fix: update README for marketplace installation and improve hooks_cli file encoding --- .claude-plugin/README.md | 9 ++------- mempalace/hooks_cli.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.claude-plugin/README.md b/.claude-plugin/README.md index 6754626..0212980 100644 --- a/.claude-plugin/README.md +++ b/.claude-plugin/README.md @@ -11,13 +11,8 @@ A Claude Code plugin that gives your AI a persistent memory system. Mine project ### Claude Code Marketplace ```bash -claude plugin add mempalace -``` - -### Git - -```bash -claude plugin add --git https://github.com/milla-jovovich/mempalace +claude plugin marketplace add milla-jovovich/mempalace +claude plugin install --scope user mempalace ``` ### Local Clone diff --git a/mempalace/hooks_cli.py b/mempalace/hooks_cli.py index 6b4bb0f..7d46d1a 100644 --- a/mempalace/hooks_cli.py +++ b/mempalace/hooks_cli.py @@ -46,7 +46,7 @@ def _count_human_messages(transcript_path: str) -> int: return 0 count = 0 try: - with open(path) as f: + with open(path, encoding="utf-8", errors="replace") as f: for line in f: try: entry = json.loads(line) From 1e251cbf36ec74bc8e9c9dffc3a1a2e243a6001d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 16:33:29 +0000 Subject: [PATCH 16/24] chore: bump version to 3.0.5 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 25173c3..aba08d2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.4", + "version": "3.0.5", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 2176807..d5d2dce 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.4", + "version": "3.0.5", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 5d4fefc..09565c8 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.4", + "version": "3.0.5", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index 30d60af..e4c9989 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.4" +__version__ = "3.0.5" From 1888b5688340e7c26f3d1d719f51910371c2ad16 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 20:00:16 +0300 Subject: [PATCH 17/24] chore: bump version to 3.0.4 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4862873..98a4bd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mempalace" -version = "3.0.0" +version = "3.0.4" description = "Give your AI a memory — mine projects and conversations into a searchable palace. No API key required." readme = "README.md" requires-python = ">=3.9" From 019d852707205ec394ce5b0d3dcf6a65038b0fa5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 17:00:30 +0000 Subject: [PATCH 18/24] chore: bump version to 3.0.6 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index aba08d2..844276c 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.5", + "version": "3.0.6", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index d5d2dce..e6f6495 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.5", + "version": "3.0.6", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 09565c8..f11a89a 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.5", + "version": "3.0.6", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index e4c9989..466ad05 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.5" +__version__ = "3.0.6" From 0975b1d23f264f13a0b73e602b66c525414271f1 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 20:04:10 +0300 Subject: [PATCH 19/24] fix: add syncing of pyproject.toml version during bump process --- .github/workflows/bump-plugin-version.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bump-plugin-version.yml b/.github/workflows/bump-plugin-version.yml index 43adc8b..0867b3c 100644 --- a/.github/workflows/bump-plugin-version.yml +++ b/.github/workflows/bump-plugin-version.yml @@ -36,11 +36,15 @@ jobs: run: | jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' .codex-plugin/plugin.json > tmp.json && mv tmp.json .codex-plugin/plugin.json + - name: Sync pyproject.toml + run: | + sed -i "s/^version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml + - name: Commit and push run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json .codex-plugin/plugin.json + git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json .codex-plugin/plugin.json pyproject.toml if ! git diff --staged --quiet; then git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" git push From 4f1434720c301d3ed8c25b4b0ef91038ea129d15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 17:04:25 +0000 Subject: [PATCH 20/24] chore: bump version to 3.0.7 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- pyproject.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 844276c..8d3dc71 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.6", + "version": "3.0.7", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e6f6495..8559c0a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.6", + "version": "3.0.7", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index f11a89a..807b3b9 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.6", + "version": "3.0.7", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index 466ad05..0da0a95 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.6" +__version__ = "3.0.7" diff --git a/pyproject.toml b/pyproject.toml index 98a4bd3..7716789 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mempalace" -version = "3.0.4" +version = "3.0.7" description = "Give your AI a memory — mine projects and conversations into a searchable palace. No API key required." readme = "README.md" requires-python = ">=3.9" From e47fa1b5bf3f6bb9e6f6921f042dc2a1022a2e74 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 20:17:23 +0300 Subject: [PATCH 21/24] refactor: consolidate hook scripts and fixed issue from review --- .codex-plugin/README.md | 2 +- .codex-plugin/hooks.json | 6 +-- .codex-plugin/hooks/mempal-hook.sh | 9 ++++ .codex-plugin/hooks/mempal-precompact-hook.sh | 15 ------- .../hooks/mempal-session-start-hook.sh | 15 ------- .codex-plugin/hooks/mempal-stop-hook.sh | 15 ------- mempalace/hooks_cli.py | 45 ++++++++----------- 7 files changed, 32 insertions(+), 75 deletions(-) create mode 100644 .codex-plugin/hooks/mempal-hook.sh delete mode 100644 .codex-plugin/hooks/mempal-precompact-hook.sh delete mode 100644 .codex-plugin/hooks/mempal-session-start-hook.sh delete mode 100644 .codex-plugin/hooks/mempal-stop-hook.sh diff --git a/.codex-plugin/README.md b/.codex-plugin/README.md index a7ec1de..59e01e9 100644 --- a/.codex-plugin/README.md +++ b/.codex-plugin/README.md @@ -4,7 +4,7 @@ Give your AI a persistent memory -- mine projects and conversations into a searc ## Prerequisites -- Python 3.10+ +- Python 3.9+ - Codex CLI installed and configured - `pip install mempalace` diff --git a/.codex-plugin/hooks.json b/.codex-plugin/hooks.json index 1c235f2..46f7e66 100644 --- a/.codex-plugin/hooks.json +++ b/.codex-plugin/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "./hooks/mempal-session-start-hook.sh" + "command": "${CODEX_PLUGIN_ROOT}/hooks/mempal-hook.sh session-start" } ] } @@ -17,7 +17,7 @@ "hooks": [ { "type": "command", - "command": "./hooks/mempal-stop-hook.sh" + "command": "${CODEX_PLUGIN_ROOT}/hooks/mempal-hook.sh stop" } ] } @@ -28,7 +28,7 @@ "hooks": [ { "type": "command", - "command": "./hooks/mempal-precompact-hook.sh" + "command": "${CODEX_PLUGIN_ROOT}/hooks/mempal-hook.sh precompact" } ] } diff --git a/.codex-plugin/hooks/mempal-hook.sh b/.codex-plugin/hooks/mempal-hook.sh new file mode 100644 index 0000000..1cc0050 --- /dev/null +++ b/.codex-plugin/hooks/mempal-hook.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +HOOK_NAME="${1:?Usage: mempal-hook.sh }" +INPUT_FILE=$(mktemp) || { echo "Failed to create temp file" >&2; exit 1; } +cat > "$INPUT_FILE" +cat "$INPUT_FILE" | python3 -m mempalace hook run --hook "$HOOK_NAME" --harness codex +EXIT_CODE=$? +rm -f "$INPUT_FILE" 2>/dev/null +exit $EXIT_CODE diff --git a/.codex-plugin/hooks/mempal-precompact-hook.sh b/.codex-plugin/hooks/mempal-precompact-hook.sh deleted file mode 100644 index 46af49d..0000000 --- a/.codex-plugin/hooks/mempal-precompact-hook.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" - -# Capture stdin (hook input from Codex) -INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-precompact-hook-$$.json") -cat > "$INPUT_FILE" - -# Pipe to Python CLI with codex harness -cat "$INPUT_FILE" | python3 -m mempalace hook run --hook precompact --harness codex -EXIT_CODE=$? - -# Cleanup -rm -f "$INPUT_FILE" 2>/dev/null -exit $EXIT_CODE diff --git a/.codex-plugin/hooks/mempal-session-start-hook.sh b/.codex-plugin/hooks/mempal-session-start-hook.sh deleted file mode 100644 index 7c7b5c2..0000000 --- a/.codex-plugin/hooks/mempal-session-start-hook.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" - -# Capture stdin (hook input from Codex) -INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-session-start-hook-$$.json") -cat > "$INPUT_FILE" - -# Pipe to Python CLI with codex harness -cat "$INPUT_FILE" | python3 -m mempalace hook run --hook session-start --harness codex -EXIT_CODE=$? - -# Cleanup -rm -f "$INPUT_FILE" 2>/dev/null -exit $EXIT_CODE diff --git a/.codex-plugin/hooks/mempal-stop-hook.sh b/.codex-plugin/hooks/mempal-stop-hook.sh deleted file mode 100644 index 2d38932..0000000 --- a/.codex-plugin/hooks/mempal-stop-hook.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" - -# Capture stdin (hook input from Codex) -INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-stop-hook-$$.json") -cat > "$INPUT_FILE" - -# Pipe to Python CLI with codex harness -cat "$INPUT_FILE" | python3 -m mempalace hook run --hook stop --harness codex -EXIT_CODE=$? - -# Cleanup -rm -f "$INPUT_FILE" 2>/dev/null -exit $EXIT_CODE diff --git a/mempalace/hooks_cli.py b/mempalace/hooks_cli.py index 7d46d1a..fe6e4eb 100644 --- a/mempalace/hooks_cli.py +++ b/mempalace/hooks_cli.py @@ -53,8 +53,15 @@ def _count_human_messages(transcript_path: str) -> int: msg = entry.get("message", {}) if isinstance(msg, dict) and msg.get("role") == "user": content = msg.get("content", "") - if isinstance(content, str) and "" in content: - continue + if isinstance(content, str): + if "" in content: + continue + elif isinstance(content, list): + text = " ".join( + b.get("text", "") for b in content if isinstance(b, dict) + ) + if "" in text: + continue count += 1 except (json.JSONDecodeError, AttributeError): pass @@ -96,35 +103,19 @@ def _maybe_auto_ingest(): pass -def _parse_claude_code_input(data: dict) -> dict: - """Parse stdin JSON for the claude-code harness.""" - return { - "session_id": _sanitize_session_id(str(data.get("session_id", "unknown"))), - "stop_hook_active": data.get("stop_hook_active", False), - "transcript_path": str(data.get("transcript_path", "")), - } - - -def _parse_codex_input(data: dict) -> dict: - """Parse stdin JSON for the codex harness.""" - return { - "session_id": _sanitize_session_id(str(data.get("session_id", "unknown"))), - "stop_hook_active": data.get("stop_hook_active", False), - "transcript_path": str(data.get("transcript_path", "")), - } +SUPPORTED_HARNESSES = {"claude-code", "codex"} def _parse_harness_input(data: dict, harness: str) -> dict: """Parse stdin JSON according to the harness type.""" - parsers = { - "claude-code": _parse_claude_code_input, - "codex": _parse_codex_input, - } - parser = parsers.get(harness) - if parser is None: + if harness not in SUPPORTED_HARNESSES: print(f"Unknown harness: {harness}", file=sys.stderr) sys.exit(1) - return parser(data) + return { + "session_id": _sanitize_session_id(str(data.get("session_id", "unknown"))), + "stop_hook_active": data.get("stop_hook_active", False), + "transcript_path": str(data.get("transcript_path", "")), + } def hook_stop(data: dict, harness: str): @@ -135,7 +126,7 @@ def hook_stop(data: dict, harness: str): transcript_path = parsed["transcript_path"] # If already in a save cycle, let through (infinite-loop prevention) - if stop_hook_active in (True, "True", "true"): + if str(stop_hook_active).lower() in ("true", "1", "yes"): _output({}) return @@ -204,6 +195,7 @@ def hook_precompact(data: dict, harness: str): [sys.executable, "-m", "mempalace", "mine", mempal_dir], stdout=log_f, stderr=log_f, + timeout=60, ) except OSError: pass @@ -217,6 +209,7 @@ def run_hook(hook_name: str, harness: str): try: data = json.load(sys.stdin) except (json.JSONDecodeError, EOFError): + _log("WARNING: Failed to parse stdin JSON, proceeding with empty data") data = {} hooks = { From 67e21c582c0b77b703c4d5ef86b36a4f969a1c59 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 17:17:36 +0000 Subject: [PATCH 22/24] chore: bump version to 3.0.8 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- pyproject.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8d3dc71..c0abf8b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.7", + "version": "3.0.8", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8559c0a..648361c 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.7", + "version": "3.0.8", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 807b3b9..8892791 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.7", + "version": "3.0.8", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index 0da0a95..2ac6bef 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.7" +__version__ = "3.0.8" diff --git a/pyproject.toml b/pyproject.toml index 7716789..ff7e2f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mempalace" -version = "3.0.7" +version = "3.0.8" description = "Give your AI a memory — mine projects and conversations into a searchable palace. No API key required." readme = "README.md" requires-python = ">=3.9" From 9de302f881b01ba21c4833666f765d120083dc99 Mon Sep 17 00:00:00 2001 From: Tal Muskal Date: Wed, 8 Apr 2026 20:40:03 +0300 Subject: [PATCH 23/24] feat: update README and CI configuration, add tests for hooks functionality --- .claude-plugin/README.md | 4 +- .codex-plugin/README.md | 4 +- .github/workflows/ci.yml | 2 +- README.md | 18 ++++ pyproject.toml | 15 ++- tests/test_hooks_cli.py | 192 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 tests/test_hooks_cli.py diff --git a/.claude-plugin/README.md b/.claude-plugin/README.md index 0212980..fd98952 100644 --- a/.claude-plugin/README.md +++ b/.claude-plugin/README.md @@ -43,9 +43,11 @@ After installing the plugin, run the init command to complete setup (pip install MemPalace registers two hooks that run automatically: -- **Stop** -- Saves conversation context when a session ends. +- **Stop** -- Saves conversation context every 15 messages. - **PreCompact** -- Preserves important memories before context compaction. +Set the `MEMPAL_DIR` environment variable to a directory path to automatically run `mempalace mine` on that directory during each save trigger. + ## MCP Server The plugin automatically configures a local MCP server with 19 tools for storing, searching, and managing memories. No manual MCP setup is required -- `/mempalace:init` handles everything. diff --git a/.codex-plugin/README.md b/.codex-plugin/README.md index 59e01e9..57dbc34 100644 --- a/.codex-plugin/README.md +++ b/.codex-plugin/README.md @@ -65,7 +65,9 @@ codex /init ## Hooks -The plugin includes an auto-save hook that runs on session stop, automatically preserving conversation context into your palace. +The plugin includes auto-save hooks that run on session stop (every 15 messages) and before context compaction, automatically preserving conversation context into your palace. + +Set the `MEMPAL_DIR` environment variable to a directory path to automatically run `mempalace mine` on that directory during each save trigger. ## Support diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ccb15e..4cca6ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - run: pip install -e ".[dev]" - - run: python -m pytest tests/ -v + - run: python -m pytest tests/ -v --cov=mempalace --cov-report=term-missing --cov-fail-under=30 lint: runs-on: ubuntu-latest diff --git a/README.md b/README.md index d05952f..441f506 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,17 @@ Three mining modes: **projects** (code and docs), **convos** (conversation expor After the one-time setup (install → init → mine), you don't run MemPalace commands manually. Your AI uses it for you. There are two ways, depending on which AI you use. +### With Claude Code (recommended) + +Native marketplace install: + +```bash +claude plugin marketplace add milla-jovovich/mempalace +claude plugin install --scope user mempalace +``` + +Restart Claude Code, then type `/skills` to verify "mempalace" appears. + ### With Claude, ChatGPT, Cursor, Gemini (MCP-compatible tools) ```bash @@ -439,6 +450,11 @@ Letta charges $20–200/mo for agent-managed memory. MemPalace does it with a wi ## MCP Server ```bash +# Via plugin (recommended) +claude plugin marketplace add milla-jovovich/mempalace +claude plugin install --scope user mempalace + +# Or manually claude mcp add mempalace -- python -m mempalace.mcp_server ``` @@ -509,6 +525,8 @@ Two hooks for Claude Code that automatically save memories during work: } ``` +**Optional auto-ingest:** Set the `MEMPAL_DIR` environment variable to a directory path and the hooks will automatically run `mempalace mine` on that directory during each save trigger (background on stop, synchronous on precompact). + --- ## Benchmarks diff --git a/pyproject.toml b/pyproject.toml index ff7e2f1..a83ace6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,11 @@ Repository = "https://github.com/milla-jovovich/mempalace" mempalace = "mempalace:main" [project.optional-dependencies] -dev = ["pytest>=7.0", "ruff>=0.4.0"] +dev = ["pytest>=7.0", "pytest-cov>=4.0", "ruff>=0.4.0"] spellcheck = ["autocorrect>=2.0"] [dependency-groups] -dev = ["pytest>=7.0", "ruff>=0.4.0"] +dev = ["pytest>=7.0", "pytest-cov>=4.0", "ruff>=0.4.0"] [build-system] requires = ["hatchling"] @@ -64,3 +64,14 @@ quote-style = "double" [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.coverage.run] +source = ["mempalace"] + +[tool.coverage.report] +fail_under = 30 +show_missing = true +exclude_lines = [ + "if __name__", + "pragma: no cover", +] diff --git a/tests/test_hooks_cli.py b/tests/test_hooks_cli.py new file mode 100644 index 0000000..8eeffed --- /dev/null +++ b/tests/test_hooks_cli.py @@ -0,0 +1,192 @@ +import contextlib +import json +from pathlib import Path +from unittest.mock import patch + +from mempalace.hooks_cli import ( + SAVE_INTERVAL, + STOP_BLOCK_REASON, + PRECOMPACT_BLOCK_REASON, + _count_human_messages, + _sanitize_session_id, + hook_stop, + hook_session_start, + hook_precompact, +) + + +# --- _sanitize_session_id --- + + +def test_sanitize_normal_id(): + assert _sanitize_session_id("abc-123_XYZ") == "abc-123_XYZ" + + +def test_sanitize_strips_dangerous_chars(): + assert _sanitize_session_id("../../etc/passwd") == "etcpasswd" + + +def test_sanitize_empty_returns_unknown(): + assert _sanitize_session_id("") == "unknown" + assert _sanitize_session_id("!!!") == "unknown" + + +# --- _count_human_messages --- + + +def _write_transcript(path: Path, entries: list[dict]): + with open(path, "w", encoding="utf-8") as f: + for entry in entries: + f.write(json.dumps(entry) + "\n") + + +def test_count_human_messages_basic(tmp_path): + transcript = tmp_path / "t.jsonl" + _write_transcript(transcript, [ + {"message": {"role": "user", "content": "hello"}}, + {"message": {"role": "assistant", "content": "hi"}}, + {"message": {"role": "user", "content": "bye"}}, + ]) + assert _count_human_messages(str(transcript)) == 2 + + +def test_count_skips_command_messages(tmp_path): + transcript = tmp_path / "t.jsonl" + _write_transcript(transcript, [ + {"message": {"role": "user", "content": "status"}}, + {"message": {"role": "user", "content": "real question"}}, + ]) + assert _count_human_messages(str(transcript)) == 1 + + +def test_count_handles_list_content(tmp_path): + transcript = tmp_path / "t.jsonl" + _write_transcript(transcript, [ + {"message": {"role": "user", "content": [{"type": "text", "text": "hello"}]}}, + {"message": {"role": "user", "content": [{"type": "text", "text": "x"}]}}, + ]) + assert _count_human_messages(str(transcript)) == 1 + + +def test_count_missing_file(): + assert _count_human_messages("/nonexistent/path.jsonl") == 0 + + +def test_count_empty_file(tmp_path): + transcript = tmp_path / "t.jsonl" + transcript.write_text("") + assert _count_human_messages(str(transcript)) == 0 + + +def test_count_malformed_json_lines(tmp_path): + transcript = tmp_path / "t.jsonl" + transcript.write_text('not json\n{"message": {"role": "user", "content": "ok"}}\n') + assert _count_human_messages(str(transcript)) == 1 + + +# --- hook_stop --- + + +def _capture_hook_output(hook_fn, data, harness="claude-code", state_dir=None): + """Run a hook and capture its JSON stdout output.""" + import io + buf = io.StringIO() + patches = [patch("mempalace.hooks_cli._output", side_effect=lambda d: buf.write(json.dumps(d)))] + if state_dir: + patches.append(patch("mempalace.hooks_cli.STATE_DIR", state_dir)) + with contextlib.ExitStack() as stack: + for p in patches: + stack.enter_context(p) + hook_fn(data, harness) + return json.loads(buf.getvalue()) + + +def test_stop_hook_passthrough_when_active(tmp_path): + with patch("mempalace.hooks_cli.STATE_DIR", tmp_path): + result = _capture_hook_output( + hook_stop, + {"session_id": "test", "stop_hook_active": True, "transcript_path": ""}, + state_dir=tmp_path, + ) + assert result == {} + + +def test_stop_hook_passthrough_when_active_string(tmp_path): + with patch("mempalace.hooks_cli.STATE_DIR", tmp_path): + result = _capture_hook_output( + hook_stop, + {"session_id": "test", "stop_hook_active": "true", "transcript_path": ""}, + state_dir=tmp_path, + ) + assert result == {} + + +def test_stop_hook_passthrough_below_interval(tmp_path): + transcript = tmp_path / "t.jsonl" + _write_transcript(transcript, [ + {"message": {"role": "user", "content": f"msg {i}"}} + for i in range(SAVE_INTERVAL - 1) + ]) + result = _capture_hook_output( + hook_stop, + {"session_id": "test", "stop_hook_active": False, "transcript_path": str(transcript)}, + state_dir=tmp_path, + ) + assert result == {} + + +def test_stop_hook_blocks_at_interval(tmp_path): + transcript = tmp_path / "t.jsonl" + _write_transcript(transcript, [ + {"message": {"role": "user", "content": f"msg {i}"}} + for i in range(SAVE_INTERVAL) + ]) + result = _capture_hook_output( + hook_stop, + {"session_id": "test", "stop_hook_active": False, "transcript_path": str(transcript)}, + state_dir=tmp_path, + ) + assert result["decision"] == "block" + assert result["reason"] == STOP_BLOCK_REASON + + +def test_stop_hook_tracks_save_point(tmp_path): + transcript = tmp_path / "t.jsonl" + _write_transcript(transcript, [ + {"message": {"role": "user", "content": f"msg {i}"}} + for i in range(SAVE_INTERVAL) + ]) + data = {"session_id": "test", "stop_hook_active": False, "transcript_path": str(transcript)} + + # First call blocks + result = _capture_hook_output(hook_stop, data, state_dir=tmp_path) + assert result["decision"] == "block" + + # Second call with same count passes through (already saved) + result = _capture_hook_output(hook_stop, data, state_dir=tmp_path) + assert result == {} + + +# --- hook_session_start --- + + +def test_session_start_passes_through(tmp_path): + result = _capture_hook_output( + hook_session_start, + {"session_id": "test"}, + state_dir=tmp_path, + ) + assert result == {} + + +# --- hook_precompact --- + + +def test_precompact_always_blocks(tmp_path): + result = _capture_hook_output( + hook_precompact, + {"session_id": "test"}, + state_dir=tmp_path, + ) + assert result["decision"] == "block" + assert result["reason"] == PRECOMPACT_BLOCK_REASON From 43cf87315e9b6ee988f19d6d4c7e8a562c08c264 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Apr 2026 17:40:17 +0000 Subject: [PATCH 24/24] chore: bump version to 3.0.9 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- mempalace/version.py | 2 +- pyproject.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c0abf8b..b2b7ed2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.0.8", + "version": "3.0.9", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 648361c..a5d97af 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.8", + "version": "3.0.9", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 8892791..fb19f29 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.0.8", + "version": "3.0.9", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/mempalace/version.py b/mempalace/version.py index 2ac6bef..0a33c87 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.0.8" +__version__ = "3.0.9" diff --git a/pyproject.toml b/pyproject.toml index a83ace6..c19104a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mempalace" -version = "3.0.8" +version = "3.0.9" description = "Give your AI a memory — mine projects and conversations into a searchable palace. No API key required." readme = "README.md" requires-python = ">=3.9"