Files
lore-engine-poc-v3/scripts/05_migrate_planes.py
Lore Engine Dev f88616ed89 slice 6.6: Region↔Plane migration (script + 9 tests)
Adds the slice 6.6 migration as a library helper
(lore_engine_poc.migration: scan_codex_for_planes /
apply_plane_migration) plus a thin CLI wrapper at
scripts/05_migrate_planes.py.

Discriminator: frontmatter signals — plane: true,
tags contains plane, OR type: plane — promote an
entry to :Plane. Default Material Plane convention
remains mardonari.material (added by the 6.4
backfill). Idempotent: re-running on the same codex
produces the same graph state.

LAYER_OF edges are created only between co-referenced
Planes (markdown body [[X]] → Plane node X). Direction
follows docs/17-planes.md: (:Plane A)-[:LAYER_OF]->
(:Plane B) where A is the layer and B is the parent.
In the voldramir fixture, Voldramir(demiplane) LAYER_OF
Underdark(plane).

The script supports --dry-run (print planned changes,
exit 0) and --codex / --setting overrides.

Suite: 747 → 756 tests (+9). No regressions.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-19 15:12:20 -04:00

111 lines
3.3 KiB
Python

"""05_migrate_planes — promote codex Regions tagged as Planes.
Per ``docs/plan/exec/06-planes.md`` sub-slice 6.6, this script
reads the codex's markdown frontmatter and promotes any entry
with a ``:Plane`` signal to a ``:Plane`` node, creating
``LAYER_OF`` edges for the in-markdown cross-references.
Run:
python3 scripts/05_migrate_planes.py [--codex ROOT] [--setting ID] [--dry-run]
Discriminator (what makes an entry a Plane):
- ``plane: true`` in the frontmatter, OR
- ``plane`` in the entry's ``tags`` list, OR
- ``type: plane`` in the frontmatter.
The ``--dry-run`` flag prints the list of planned promotions
and exits without touching the graph. Default behaviour
applies the migration in-process: the codex's own graph
(built via ``load_graph``) is the one mutated, and a small
summary is printed.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from lore_engine_poc.graph_backend import InMemoryGraph
from lore_engine_poc.migration import (
apply_plane_migration,
scan_codex_for_planes,
)
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument(
"--codex",
default=str(ROOT / "lore_engine_poc" / "seed"),
help="Codex root (the directory containing 'Campaign Codex - Regions/').",
)
p.add_argument(
"--setting",
default="mardonari",
help="Setting id to materialise (default: mardonari).",
)
p.add_argument(
"--dry-run",
action="store_true",
help="Print the list of planned promotions and exit without writing.",
)
return p.parse_args()
def _print_plans(plans: list) -> None:
if not plans:
print(" (no plane promotions planned)")
return
for p in plans:
print(
f" + {p.entity_name:32s}{p.plane_id:36s} kind={p.plane_kind} "
f"({p.source_path})"
)
def main() -> int:
args = parse_args()
codex_root = Path(args.codex)
if not codex_root.is_dir():
print(f"[05_migrate_planes] codex not found: {codex_root}", file=sys.stderr)
return 1
plans = scan_codex_for_planes(codex_root, setting_id=args.setting)
print(f"[05_migrate_planes] scanned {codex_root}")
print(f"[05_migrate_planes] setting: {args.setting}")
print(f"[05_migrate_planes] planned {len(plans)} promotion(s):")
_print_plans(plans)
if args.dry_run:
print("[05_migrate_planes] --dry-run: no graph changes applied")
return 0
# Build a fresh in-memory graph. The migration is a
# stand-alone step — it does not depend on the
# ``01_ingest``-built graph. The slice 6.5 settings
# filter, slice 7's MCP tools, and slice 6.6's
# migration each materialise what they need; this
# keeps the script a clean re-run point.
g = InMemoryGraph()
summary = apply_plane_migration(g, plans, setting_id=args.setting)
print(
f"[05_migrate_planes] applied: "
f"setting_added={summary.setting_added} "
f"planes_added={summary.planes_added} "
f"layer_of_edges_added={summary.layer_of_edges_added}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())