generated from coulomb/repo-seed
session-memory: distribute entrypoint + live verify (WP-0007 T05)
python -m session_memory.distribute: reads approved catalog patterns, builds targets from repo->domain map x flavors, renders scoped per-flavor proposals (HITL) + active registry. Live verify against the real catalog: 12 renders across 5 repos, idempotent, provisional skipped. proposals/ gitignored (regenerated); active_patterns.json committed. README documents detect->curate-> distribute. Phase 3 finished; suite 126/126. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
89
session_memory/distribute/__main__.py
Normal file
89
session_memory/distribute/__main__.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""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())
|
||||
98
session_memory/distribute/active_patterns.json
Normal file
98
session_memory/distribute/active_patterns.json
Normal file
@@ -0,0 +1,98 @@
|
||||
[
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-problem-schema_thrash-schema_load",
|
||||
"repo": "ops-bridge",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-problem-tool_thrash-tool-bash",
|
||||
"repo": "state-hub",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "agentic-resources",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "grok",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "agentic-resources",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "can-you-assist",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "grok",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "can-you-assist",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "ops-bridge",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "grok",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "ops-bridge",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "state-hub",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "grok",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "state-hub",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "claude",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "the-custodian",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"flavor": "grok",
|
||||
"pattern_id": "sp-success-clean_pass-outcome",
|
||||
"repo": "the-custodian",
|
||||
"status": "proposed",
|
||||
"updated_at": "2026-06-07T13:20:58Z",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user