From d8ccf867b95617adfc099c4b284abb71c198c987 Mon Sep 17 00:00:00 2001 From: Chris Chen Date: Fri, 1 May 2026 22:56:07 +0800 Subject: [PATCH] feat(cli): add mempalace export subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the existing mempalace.exporter.export_palace() function to argparse as a new top-level subcommand: mempalace export Dumps the palace as a browsable Markdown tree — top-level index.md plus one .md per room under each wing directory, with each drawer rendered as a heading + blockquoted verbatim content + metadata table. The exporter itself is unchanged; it already streams in 1k-drawer batches so memory stays bounded for large palaces, and no other commands change behaviour. Adds three matching tests in tests/test_cli.py covering handler arg forwarding, ~ expansion, and main() dispatch routing, mirroring the existing cmd_search test pattern. Updates CHANGELOG.md under [3.3.4] unreleased and adds a reference entry in website/reference/cli.md adjacent to mempalace status (both are read-only palace-inspection commands). Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + mempalace/cli.py | 19 +++++++++++++++++++ tests/test_cli.py | 40 ++++++++++++++++++++++++++++++++++++++++ website/reference/cli.md | 14 ++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f51968b3f..80c1df41f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added +- **New `mempalace export ` CLI subcommand.** Dumps the palace as a browsable Markdown tree — top-level `index.md` plus one `.md` per room under each wing directory, with each drawer rendered as a heading + blockquoted verbatim content + metadata table. Wires the existing `mempalace.exporter.export_palace()` function (which already streams in 1k-drawer batches to bound memory) to argparse; no changes to the exporter itself. Useful for sharing palace contents, offline reading, or feeding into other doc pipelines. (#TBD) - **`mempalace init` now prompts to mine the same directory.** After entity confirmation, room detection, and gitignore guard, `init` shows a one-line scope estimate (e.g. `~423 files (~12 MB) would be mined into this palace.`) computed from its existing corpus walk, then asks `Mine this directory now? [Y/n]` (default yes) and runs `mine()` in-process if accepted. The estimate fires before the prompt so users on a real corpus aren't surprised by a minutes-long ChromaDB write. Declining prints the exact `mempalace mine ` command for later. (#1181) - **New `--auto-mine` flag on `mempalace init`** for the non-interactive path (`mempalace init --auto-mine ` skips the mine prompt and runs mine directly). `--yes` retains its existing scope of entity auto-accept only and still prompts for the mine step, so existing scripted callers see no behaviour change; combining `--yes --auto-mine` gives a fully non-interactive setup. (#1181) - **Cross-wing topic tunnels.** When two wings have confirmed `TOPIC` labels in common (the LLM-refine bucket from `mempalace init --llm`), the miner now drops a symmetric tunnel between them at mine time so the palace graph reflects shared themes (frameworks, vendors, recurring concepts). Tunnels are routed through the existing `create_tunnel` storage so they share dedup and persistence with explicit tunnels. Topic tunnels are stored under a synthetic `topic:` room and tagged with `kind: "topic"` on the stored dict — this keeps them distinct from literal folder-derived rooms of the same name (a wing with both an `Angular` folder room and an `Angular` topic tunnel no longer collides at `follow_tunnels` read time) and gives LLMs scanning `list_tunnels` a visible discriminator. Threshold is configurable via `MEMPALACE_TOPIC_TUNNEL_MIN_COUNT` env var or `topic_tunnel_min_count` in `~/.mempalace/config.json` (default `1`). Manifest-dependency overlap and per-topic allow/deny lists remain out of scope. (#1180) diff --git a/mempalace/cli.py b/mempalace/cli.py index ca9798b444..9cc7483a5b 100644 --- a/mempalace/cli.py +++ b/mempalace/cli.py @@ -18,6 +18,7 @@ mempalace wake-up Show L0 + L1 wake-up context mempalace wake-up --wing my_app Wake-up for a specific project mempalace status Show what's been filed + mempalace export Export the palace as a Markdown tree Examples: mempalace init ~/projects/my_app @@ -579,6 +580,15 @@ def cmd_search(args): sys.exit(1) +def cmd_export(args): + """Export the palace as a browsable folder of Markdown files.""" + from .exporter import export_palace + + palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path + output_dir = os.path.expanduser(args.output_dir) + export_palace(palace_path=palace_path, output_dir=output_dir) + + def cmd_wakeup(args): """Show L0 (identity) + L1 (essential story) — the wake-up context.""" from .layers import MemoryStack @@ -1098,6 +1108,14 @@ def main(): p_search.add_argument("--room", default=None, help="Limit to one room") p_search.add_argument("--results", type=int, default=5, help="Number of results") + # export + p_export = sub.add_parser( + "export", help="Export the palace to a browsable Markdown tree" + ) + p_export.add_argument( + "output_dir", help="Directory to write Markdown files (created if missing)" + ) + # compress p_compress = sub.add_parser( "compress", help="Compress drawers using AAAK Dialect (~30x reduction)" @@ -1281,6 +1299,7 @@ def main(): "sweep": cmd_sweep, "mcp": cmd_mcp, "compress": cmd_compress, + "export": cmd_export, "wake-up": cmd_wakeup, "repair": cmd_repair, "repair-status": cmd_repair_status, diff --git a/tests/test_cli.py b/tests/test_cli.py index af7b39d0a6..9328cca99e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -10,6 +10,7 @@ from mempalace.cli import ( cmd_compress, + cmd_export, cmd_hook, cmd_init, cmd_instructions, @@ -80,6 +81,36 @@ def test_cmd_search_error_exits(mock_config_cls): assert exc_info.value.code == 1 +# ── cmd_export ───────────────────────────────────────────────────────── + + +@patch("mempalace.cli.MempalaceConfig") +def test_cmd_export_calls_export_palace(mock_config_cls): + """Default palace path comes from config; output_dir is forwarded as-is.""" + mock_config_cls.return_value.palace_path = "/fake/palace" + args = argparse.Namespace(palace=None, output_dir="/tmp/out") + with patch("mempalace.exporter.export_palace") as mock_export: + cmd_export(args) + mock_export.assert_called_once_with( + palace_path="/fake/palace", + output_dir="/tmp/out", + ) + + +@patch("mempalace.cli.MempalaceConfig") +def test_cmd_export_expands_user_paths(mock_config_cls): + """Both --palace and output_dir support ~ expansion.""" + import os + + args = argparse.Namespace(palace="~/my_palace", output_dir="~/out") + with patch("mempalace.exporter.export_palace") as mock_export: + cmd_export(args) + mock_export.assert_called_once_with( + palace_path=os.path.expanduser("~/my_palace"), + output_dir=os.path.expanduser("~/out"), + ) + + # ── cmd_instructions ─────────────────────────────────────────────────── @@ -562,6 +593,15 @@ def test_main_search_dispatches(): mock_cmd.assert_called_once() +def test_main_export_dispatches(): + with ( + patch("sys.argv", ["mempalace", "export", "/tmp/out"]), + patch("mempalace.cli.cmd_export") as mock_cmd, + ): + main() + mock_cmd.assert_called_once() + + def test_main_init_dispatches(): with ( patch("sys.argv", ["mempalace", "init", "/some/dir"]), diff --git a/website/reference/cli.md b/website/reference/cli.md index 8ee8cad422..0bbf56636b 100644 --- a/website/reference/cli.md +++ b/website/reference/cli.md @@ -124,6 +124,20 @@ Show what's been filed — drawer count, wing/room breakdown. mempalace status ``` +## `mempalace export` + +Export the palace as a browsable folder of Markdown files — a top-level `index.md` plus one `.md` per room under each wing directory. Useful for sharing, offline reading, or feeding into other doc tools. + +```bash +mempalace export +mempalace export ./palace_md +mempalace --palace ~/.mempalace/palace export ~/Documents/palace_export +``` + +| Option | Description | +|--------|-------------| +| `` | **Required.** Directory to write Markdown files (created if missing) | + ## `mempalace repair` Rebuild palace vector index from stored data. Fixes segfaults after database corruption.