scripts.migration.build_mapping
Reference › scripts › migration › build_mapping
scripts/migration/build_mapping.py parses section 2 ("Full module mapping") of MODULE-MIGRATION-MAP.md into a machine-readable table, mapping.json, of the form {"moves": [{old, new, line}], "count": N}. It belongs to the package restructuring described in architecture migration 2026-09. A human ran it as part of that migration; it has no functions to import and does its work when run. The two arguments are the map path and the output path, both with defaults.
Each list line of the form "old path in backticks, an arrow, target package in backticks" becomes one move. dotted turns a file path into a dotted module name; a package __init__ maps onto the target package itself, and other modules map to the target package plus their base name. Old names get an orchestrata_ultimate. prefix unless they already start with orchestrata_ultimate, orchestrata_ast or orchestrata_tools. Section 3 special moves, section 4 known-dead modules and the AST packages of section 6 are excluded. An assertion stops the run on a duplicate source module. It prints the number of moves parsed and the most common target packages.
At a glance
| Source | scripts/migration/build_mapping.py |
| Lines of code | 73 |
| Classes | 0 |
| Functions | 1 |
| Imports (internal) | none |
| Imported by | none |
| Tests that import it | none |
Class diagram
No classes; see Functions.
Dependency diagram
No internal imports and no importers.
API
Functions
| Function | Signature | Summary |
|---|---|---|
dotted |
dotted(path: str) -> str |
Source
#!/usr/bin/env python3
"""Parse MODULE-MIGRATION-MAP.md §2 into a machine-readable mapping table.
Output: mapping.json — {"moves": [{old, new, source_line}], "count": N}
§3 special moves and §4 known-dead modules are EXCLUDED (handled separately).
AST packages (§6) are excluded (own section).
"""
from __future__ import annotations
import json
import re
import sys
from collections import Counter
from pathlib import Path
MAP = Path(
sys.argv[1]
if len(sys.argv) > 1
else "/home/mohamed/workspace/orchestrata-review-2026-09-17/design/MODULE-MIGRATION-MAP.md"
)
OUT = Path(sys.argv[2] if len(sys.argv) > 2 else "mapping.json")
LINE = re.compile(r"^- `([^`]+\.py)` → `([^`]+)`")
def dotted(path: str) -> str:
return path.removesuffix(".py").replace("/", ".")
text = MAP.read_text()
# §2 only — §3 special-move lines share the `- \`x\` → \`y\`` shape (with a
# prose suffix on the target), and §5/§6 tables use backticks too.
section = text.split("## 2. Full module mapping", 1)[1].split("## 3.", 1)[0]
moves = []
for lineno, raw in enumerate(section.splitlines(), 1):
m = LINE.match(raw.strip())
if not m:
continue
src, target_pkg = m.groups()
if " — " in target_pkg: # special-move prose target, not a dotted package
continue
old = dotted(src)
base = old.rsplit(".", 1)[-1]
if base == "__init__":
# package __init__ maps onto the target package itself
new = target_pkg
else:
new = f"{target_pkg}.{base}"
old_full = (
old
if old.startswith(
("orchestrata_ultimate", "orchestrata_ast", "orchestrata_tools")
)
else f"orchestrata_ultimate.{old}"
)
moves.append({"old": old_full, "new": new, "line": lineno})
# dedupe guard
seen = {}
for mv in moves:
assert (
mv["old"] not in seen
), f"duplicate source: {mv['old']} (lines {seen.get(mv['old'])}, {mv['line']})"
seen[mv["old"]] = mv["line"]
OUT.write_text(json.dumps({"moves": moves, "count": len(moves)}, indent=1) + "\n")
print(f"parsed {len(moves)} moves -> {OUT}")
pkgs = Counter(
mv["new"].split(".")[1] for mv in moves if mv["new"].startswith("orchestrata.")
)
print("top targets:", pkgs.most_common(8))