"""Distribute entrypoint (T05): catalog -> per-flavor proposals (HITL). python -m session_memory.distribute [--config PATH] [--repo R] [--flavor F] [--json] Reads approved / distribution-ready Solution Patterns from the Pattern Catalog and renders them into per-flavor **proposals** (never auto-applied) scoped by repo/domain, recording what is proposed where in the active-pattern registry. Targets are the repo->domain map in ``config.toml`` crossed with the known distributor flavors; each pattern's own ``Scope`` filters where it actually lands. """ from __future__ import annotations import argparse import json import os from ..curate.catalog import Catalog from ..ingest import _expand, load_config from .proposals import ActiveRegistry, Target, propose from .registry import all_flavors def build_targets(config: dict, repo_filter=None, flavor_filter=None) -> list[Target]: repo_map = config.get("repo_domain_map", {}) flavors = [flavor_filter] if flavor_filter else all_flavors() targets = [] for repo, domain in repo_map.items(): if repo_filter and repo != repo_filter: continue for flavor in flavors: targets.append(Target(repo=repo, domain=domain, flavor=flavor)) return targets def run_distribute(config: dict, *, repo_filter=None, flavor_filter=None): cur = config.get("curate", {}) dist = config.get("distribute", {}) catalog = Catalog(_expand(cur.get("catalog_dir", "session_memory/catalog"))) patterns = catalog.list() targets = build_targets(config, repo_filter, flavor_filter) registry = ActiveRegistry(_expand(dist.get("active_registry", "session_memory/distribute/active_patterns.json"))) out_dir = _expand(dist.get("proposals_dir", "session_memory/proposals")) return propose(patterns, targets, out_dir, registry) def _summary(res) -> str: by_repo = {} for repo, flavor, pid, _ in res.proposals: by_repo.setdefault(repo, []).append(f"{pid}[{flavor}]") lines = [f"# Distribute proposals ({len(res.proposals)} renders, " f"{len(res.files_written)} files)"] for repo in sorted(by_repo): lines.append(f" {repo}: {', '.join(sorted(by_repo[repo]))}") if res.skipped_not_distributable: lines.append(f" skipped (not distribution-ready): " f"{len(set(res.skipped_not_distributable))} pattern(s)") if not res.proposals: lines.append(" (no approved/distribution-ready patterns matched any target)") return "\n".join(lines) def main(argv=None) -> int: here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ap = argparse.ArgumentParser(description="Distribute approved patterns as per-flavor proposals.") ap.add_argument("--config", default=os.path.join(here, "config.toml")) ap.add_argument("--repo", default=None, help="limit to one target repo") ap.add_argument("--flavor", default=None, help="limit to one flavor") ap.add_argument("--json", action="store_true") args = ap.parse_args(argv) config = load_config(args.config) res = run_distribute(config, repo_filter=args.repo, flavor_filter=args.flavor) if args.json: print(json.dumps({ "proposals": [{"repo": r, "flavor": f, "pattern_id": p, "path": path} for r, f, p, path in res.proposals], "files_written": res.files_written, "skipped": sorted(set(res.skipped_not_distributable)), }, indent=2)) else: print(_summary(res)) return 0 if __name__ == "__main__": raise SystemExit(main())