feat(example): migrate to infospace config with tooling integration (S3.1)

Add infospace.yaml declaring topic, disciplines, schemas, viability
thresholds. Integrate infospace tooling into process_chapters.py with
--infospace-status, --infospace-check, and --infospace-viability flags.

Initial check: 85 entities, 4/5 viable (coverage 0.36 < 0.50 — only
7/35 chapters processed so far).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 02:29:53 +01:00
parent d1c6e53754
commit 94cb2063af
2 changed files with 185 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
# Infospace: The Wealth of Nations through the Viable System Model
#
# This configuration declares the infospace built by processing
# Adam Smith's "The Wealth of Nations" (1776) through the lens of
# Stafford Beer's Viable System Model (VSM).
topic:
name: "The Wealth of Nations"
domain: "Classical Economics"
sources: artifacts/sources/
disciplines:
- name: "Viable System Model"
path: artifacts/vsm-reference/
schemas:
entity: schemas/economic-entity-schema-v1.0.md
mapping: schemas/vsm-mapping-schema-v1.0.md
analysis: schemas/chapter-analysis-schema-v1.0.md
competency_questions: |
1. How does Smith's division of labour map to VSM System 1 operations?
2. What mechanisms in WoN correspond to VSM coordination (System 2)?
3. Where does Smith describe self-organising regulation (System 3)?
4. What role does the "invisible hand" play as a System 4 mechanism?
5. How do Smith's views on government map to System 5 policy?
6. Is the WoN entity set viable as an explanatory framework?
viability:
redundancy_ratio:
max: 0.10
coverage_ratio:
min: 0.50
coherence_components:
max: 3
consistency_cycles:
max: 0
granularity_entropy:
min: 1.0
pipeline:
stages:
- name: extract-entities
template: templates/extract-entities.md
- name: map-to-vsm
template: templates/map-to-vsm.md
- name: synthesize-analysis
template: templates/synthesize-analysis.md
post_batch:
- name: assess-metrics
template: templates/assess-metrics.md

View File

@@ -856,6 +856,125 @@ class ChapterProcessor:
print(f" (No data yet: {e})")
# ── Infospace tooling integration ─────────────────────────────────
def _load_infospace(example_dir: Path):
"""Load infospace config and entities from the example directory."""
from markitect.infospace.config import load_infospace_config
from markitect.infospace.entity_parser import parse_entity_directory
config_path = example_dir / "infospace.yaml"
if not config_path.is_file():
print("Error: No infospace.yaml found. Create one first.")
sys.exit(1)
config = load_infospace_config(config_path)
entities_dir = example_dir / config.entities_dir
entities = parse_entity_directory(entities_dir) if entities_dir.is_dir() else []
return config, config_path, entities
def _run_infospace_status(example_dir: Path):
"""Show infospace status using the tooling layer."""
from markitect.infospace.state import build_state
config, config_path, entities = _load_infospace(example_dir)
state = build_state(config, entities=entities)
print(f"Infospace: {state.topic_name}")
print(f"Domain: {config.topic.domain}")
print(f"Entities: {state.entity_count}")
if state.domains:
print(f"Domains: {', '.join(state.domains)}")
if config.disciplines:
names = [d.name for d in config.disciplines]
print(f"Disciplines: {', '.join(names)}")
# Show processing progress
sources_dir = example_dir / "artifacts" / "sources"
total_chapters = len(list(sources_dir.glob("*.md")))
processed = len(list((example_dir / "output" / "analyses").glob("*-analysis.md")))
print(f"Chapters: {processed}/{total_chapters} processed")
def _run_infospace_check(example_dir: Path):
"""Run collection-level quality checks."""
from markitect.infospace.checks import run_all_checks
from markitect.infospace.history import record_check_results
config, config_path, entities = _load_infospace(example_dir)
if not entities:
print("No entities to check.")
return
print(f"Running collection checks on {len(entities)} entities...\n")
report = run_all_checks(entities=entities)
d = report.to_dict()
for concern_name, concern_data in d.items():
label = concern_data.get("concern", concern_name.upper())
print(f" {label}{concern_name}")
for k, v in concern_data.items():
if k == "concern":
continue
print(f" {k}: {v}")
print()
m = report.metrics()
if m:
print("Metrics summary:")
for k, v in sorted(m.items()):
print(f" {k}: {v:.4f}")
snap = record_check_results(report, config, example_dir, entity_count=len(entities))
print(f"\nRecorded snapshot {snap.snapshot_id}")
def _run_infospace_viability(example_dir: Path):
"""Show viability dashboard."""
from markitect.infospace.history import read_metrics_file
from markitect.infospace.state import build_state
config, config_path, entities = _load_infospace(example_dir)
if not config.viability:
print("No viability thresholds configured.")
return
metrics = read_metrics_file(example_dir / config.metrics_dir / "metrics.yaml")
if not metrics:
print("No metrics available. Run --infospace-check first.")
print("\nConfigured thresholds:")
for name, t in config.viability.items():
bounds = []
if t.min is not None:
bounds.append(f"min={t.min}")
if t.max is not None:
bounds.append(f"max={t.max}")
print(f" {name}: {', '.join(bounds)}")
return
state = build_state(config, entities=entities, metrics=metrics)
print(f"{'Metric':<30} {'Value':>8} {'Threshold':>15} {'Status':>8}")
print("-" * 63)
for r in state.viability_results:
bounds = []
if r.threshold.min is not None:
bounds.append(f"min={r.threshold.min}")
if r.threshold.max is not None:
bounds.append(f"max={r.threshold.max}")
status_str = "PASS" if r.passed else "FAIL"
print(f"{r.metric:<30} {r.value:>8.4f} {', '.join(bounds):>15} {status_str:>8}")
print()
if state.is_viable:
print(f"Viable: YES ({state.viability_pass_count}/{state.viability_total_count} thresholds met)")
else:
print(f"Viable: NO ({state.viability_pass_count}/{state.viability_total_count} thresholds met)")
def main():
parser = argparse.ArgumentParser(
description="Process Wealth of Nations chapters through VSM analysis pipeline"
@@ -869,6 +988,12 @@ def main():
group.add_argument("--stats", action="store_true", help="Show dependency statistics")
group.add_argument("--archive-entity", type=str, metavar="SLUG",
help="Archive an entity (move to archive/ with reason)")
group.add_argument("--infospace-status", action="store_true",
help="Show infospace status via infospace tooling")
group.add_argument("--infospace-check", action="store_true",
help="Run collection-level quality checks (C1-C5)")
group.add_argument("--infospace-viability", action="store_true",
help="Show viability dashboard")
parser.add_argument("--reason", type=str, default=None,
help="Reason for archiving (used with --archive-entity)")
@@ -930,6 +1055,15 @@ def main():
for ch in chapters:
processor.process_chapter(ch, auto_commit=not args.no_commit)
print()
elif args.infospace_status:
_run_infospace_status(example_dir)
return
elif args.infospace_check:
_run_infospace_check(example_dir)
return
elif args.infospace_viability:
_run_infospace_viability(example_dir)
return
processor.show_stats()