Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- **New `mempalace export <dir>` 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 <dir>` command for later. (#1181)
- **New `--auto-mine` flag on `mempalace init`** for the non-interactive path (`mempalace init --auto-mine <dir>` 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:<name>` 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)
Expand Down
19 changes: 19 additions & 0 deletions mempalace/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <output_dir> Export the palace as a Markdown tree

Examples:
mempalace init ~/projects/my_app
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)"
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from mempalace.cli import (
cmd_compress,
cmd_export,
cmd_hook,
cmd_init,
cmd_instructions,
Expand Down Expand Up @@ -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 ───────────────────────────────────────────────────


Expand Down Expand Up @@ -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"]),
Expand Down
14 changes: 14 additions & 0 deletions website/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <output_dir>
mempalace export ./palace_md
mempalace --palace ~/.mempalace/palace export ~/Documents/palace_export
```

| Option | Description |
|--------|-------------|
| `<output_dir>` | **Required.** Directory to write Markdown files (created if missing) |

## `mempalace repair`

Rebuild palace vector index from stored data. Fixes segfaults after database corruption.
Expand Down