feat(infospace): add composition model for discipline binding (S2.6)
Discipline resolution, viability checking, entity access, stale mapping detection, and binding management. CLI commands: bind-discipline, disciplines, stale-mappings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -419,3 +419,106 @@ def history_diff(date_a: str, date_b: str, config_path: Optional[str]):
|
||||
|
||||
diff = diff_snapshots(snap_a, snap_b)
|
||||
click.echo(diff.summary())
|
||||
|
||||
|
||||
# ── bind-discipline ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@infospace_commands.command(name="bind-discipline")
|
||||
@click.argument("discipline_path")
|
||||
@click.option("--name", required=True, help="Name for the discipline.")
|
||||
@click.option("--config", "config_path", default=None, help="Path to infospace.yaml.")
|
||||
def bind_discipline_cmd(discipline_path: str, name: str, config_path: Optional[str]):
|
||||
"""Bind a discipline infospace to the current infospace."""
|
||||
cfg, cfg_path = _load_config_or_exit(config_path)
|
||||
root = cfg_path.parent
|
||||
|
||||
from markitect.infospace.composition import bind_discipline
|
||||
|
||||
status = bind_discipline(cfg, name=name, path=discipline_path, root=root)
|
||||
|
||||
if status.error:
|
||||
click.echo(f"Error: {status.error}", err=True)
|
||||
raise SystemExit(1)
|
||||
|
||||
# Persist updated config
|
||||
save_infospace_config(cfg, cfg_path)
|
||||
|
||||
click.echo(f"Bound discipline '{name}' from {discipline_path}")
|
||||
click.echo(f" Entities: {status.entity_count}")
|
||||
if status.has_config:
|
||||
viable_str = "YES" if status.is_viable else "NO"
|
||||
click.echo(f" Viable: {viable_str}")
|
||||
|
||||
|
||||
# ── disciplines ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@infospace_commands.command()
|
||||
@click.option("--config", "config_path", default=None, help="Path to infospace.yaml.")
|
||||
def disciplines(config_path: Optional[str]):
|
||||
"""List bound disciplines and their viability status."""
|
||||
cfg, cfg_path = _load_config_or_exit(config_path)
|
||||
root = cfg_path.parent
|
||||
|
||||
if not cfg.disciplines:
|
||||
click.echo("No disciplines bound.")
|
||||
return
|
||||
|
||||
from markitect.infospace.composition import check_discipline_status
|
||||
|
||||
click.echo(f"{'Name':<30} {'Entities':>8} {'Viable':>8} {'Path'}")
|
||||
click.echo("-" * 70)
|
||||
for binding in cfg.disciplines:
|
||||
status = check_discipline_status(binding, root)
|
||||
viable_str = "YES" if status.is_viable else ("NO" if status.has_config else "?")
|
||||
click.echo(
|
||||
f"{status.name:<30} {status.entity_count:>8} {viable_str:>8} {status.path}"
|
||||
)
|
||||
if status.error:
|
||||
click.echo(f" Error: {status.error}")
|
||||
|
||||
|
||||
# ── stale-mappings ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@infospace_commands.command(name="stale-mappings")
|
||||
@click.option("--config", "config_path", default=None, help="Path to infospace.yaml.")
|
||||
def stale_mappings(config_path: Optional[str]):
|
||||
"""Check for stale mappings due to discipline changes."""
|
||||
cfg, cfg_path = _load_config_or_exit(config_path)
|
||||
root = cfg_path.parent
|
||||
|
||||
if not cfg.disciplines:
|
||||
click.echo("No disciplines bound — no mappings to check.")
|
||||
return
|
||||
|
||||
from markitect.infospace.composition import find_stale_mappings
|
||||
|
||||
# Try to load mapping references from output
|
||||
mapping_refs = _load_mapping_references(cfg, root)
|
||||
|
||||
stale = find_stale_mappings(cfg, root, mapping_references=mapping_refs)
|
||||
|
||||
if not stale:
|
||||
click.echo("No stale mappings detected.")
|
||||
return
|
||||
|
||||
click.echo(f"Found {len(stale)} stale mapping(s):\n")
|
||||
for s in stale:
|
||||
click.echo(f" {s.entity_slug} -> {s.discipline_entity}")
|
||||
click.echo(f" {s.reason}")
|
||||
|
||||
|
||||
def _load_mapping_references(
|
||||
cfg: InfospaceConfig, root: Path
|
||||
) -> Optional[dict]:
|
||||
"""Try to load mapping references from YAML file in output dir."""
|
||||
mapping_file = root / cfg.metrics_dir / "mapping-references.yaml"
|
||||
if not mapping_file.is_file():
|
||||
return None
|
||||
import yaml
|
||||
data = yaml.safe_load(mapping_file.read_text(encoding="utf-8"))
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user