(self)
| 1532 | self.package_name = source_path.stem.lower().replace(" ", "_").replace("-", "_") |
| 1533 | |
| 1534 | def run(self) -> None: |
| 1535 | print(f"\n{'─'*60}") |
| 1536 | print(f" split_coder.py — splitting {self.source_path.name}") |
| 1537 | print(f" Output: {self.output_dir}") |
| 1538 | print(f"{'─'*60}\n") |
| 1539 | |
| 1540 | # ── Step 1: Analyze source ───────────────────────────────────────── |
| 1541 | print("[1/7] Analyzing source architecture...") |
| 1542 | analyzer = ArchitectureAnalyzer(self.source_path) |
| 1543 | nodes = analyzer.analyze() |
| 1544 | print(f" Found {len(nodes)} top-level nodes") |
| 1545 | |
| 1546 | # ── Step 2: Load existing manifest (for update mode) ─────────────── |
| 1547 | old_manifest = SplitManifest.load(self.output_dir) if self.update_mode else None |
| 1548 | if self.update_mode and old_manifest: |
| 1549 | print(f"[2/7] Loaded existing manifest ({len(old_manifest.nodes)} nodes)") |
| 1550 | else: |
| 1551 | print("[2/7] Starting fresh split") |
| 1552 | |
| 1553 | # ── Step 3: Route nodes to modules ──────────────────────────────── |
| 1554 | print("[3/7] Routing symbols to modules...") |
| 1555 | router = ModuleRouter(self.layout) |
| 1556 | router.assign_all(nodes) |
| 1557 | # Group by target module |
| 1558 | modules: Dict[str, List[NodeInfo]] = defaultdict(list) |
| 1559 | for node in nodes: |
| 1560 | modules[node.target_module].append(node) |
| 1561 | |
| 1562 | unclassified = modules.get("_unclassified.py", []) |
| 1563 | classified_count = len(nodes) - len(unclassified) - len(modules.get("_imports", [])) |
| 1564 | print(f" Classified {classified_count} symbols into {len(modules)} modules") |
| 1565 | if unclassified: |
| 1566 | print(f" ⚠ Unclassified: {[n.name for n in unclassified[:10]]}" |
| 1567 | + ("..." if len(unclassified) > 10 else "")) |
| 1568 | |
| 1569 | if self.dump_layout_flag: |
| 1570 | layout_out = {mod: [n.name for n in ns] for mod, ns in sorted(modules.items())} |
| 1571 | print("\n── Detected Layout ──") |
| 1572 | print(json.dumps(layout_out, indent=2)) |
| 1573 | print() |
| 1574 | |
| 1575 | # ── Step 4: Analyze cross-module dependencies ───────────────────── |
| 1576 | print("[4/7] Analyzing cross-module dependencies...") |
| 1577 | dep_analyzer = DependencyAnalyzer(analyzer, nodes) |
| 1578 | gen = CodeGenerator(analyzer, dep_analyzer, self.package_name, str(self.source_path)) |
| 1579 | # ── Step 5: Generate and write files ────────────────────────────── |
| 1580 | print("[5/7] Generating output files...") |
| 1581 | writer = FileWriter(self.output_dir, dry_run=self.dry_run) |
| 1582 | |
| 1583 | # Determine which modules to (re)generate |
| 1584 | old_node_map: Dict[str, str] = {} # name → source_hash |
| 1585 | if old_manifest: |
| 1586 | old_node_map = {e.name: e.source_hash for e in old_manifest.nodes} |
| 1587 | |
| 1588 | # Collect all non-empty target modules |
| 1589 | all_target_modules = [m for m in modules.keys() if m != "_imports"] |
| 1590 | all_target_modules.append(CodeGenerator.SOURCE_BRIDGE_MODULE) |
| 1591 | # Add empty __init__.py entries from layout |
no test coverage detected