import json import os import subprocess import sys from pathlib import Path import pytest from infospace_bench import InfospaceError, add_artifact, create_infospace from infospace_bench.semantics import ( list_entities, list_relations, parse_entity_artifact, parse_relation_artifact, ) ENTITY = """# Division of Labour ## Definition Increasing productivity by splitting work into specialized tasks. ## Source Chapter Book I, Chapter 1 ## Context Smith introduces the pin factory as an example of this mechanism. ## Economic Domain Production ## Original Wording The greatest improvement in the productive powers of labour. ## Modern Interpretation Specialization improves throughput by reducing switching costs. """ INVALID_ENTITY = """# Thin Entity ## Context This artifact is missing its definition. """ RELATION = """# Division of Labour enables Market Extent ## Subject Division of Labour ## Predicate is limited by ## Object Market Extent ## Relation Type constrains ## VSM Channel S1 -> S4 ## Evidence Book I, Chapter 3 connects specialization to market size. ## Feedback Role Part of the market expansion loop. """ def cli_env() -> dict[str, str]: env = os.environ.copy() env["PYTHONPATH"] = "src:/home/worsch/markitect-tool/src" return env def test_parse_entity_artifact_extracts_legacy_sections(tmp_path: Path) -> None: path = tmp_path / "division.md" path.write_text(ENTITY, encoding="utf-8") entity = parse_entity_artifact("entity/division.md", path) assert entity.artifact_id == "entity/division.md" assert entity.slug == "division-of-labour" assert entity.title == "Division of Labour" assert entity.definition_word_count == 8 assert entity.domain == "Production" assert entity.has_original_wording is True assert entity.section_slugs == [ "definition", "source-chapter", "context", "economic-domain", "original-wording", "modern-interpretation", ] def test_parse_entity_artifact_reports_missing_required_sections(tmp_path: Path) -> None: path = tmp_path / "thin.md" path.write_text(INVALID_ENTITY, encoding="utf-8") with pytest.raises(InfospaceError) as raised: parse_entity_artifact("entity/thin.md", path) assert raised.value.code == "invalid_entity_artifact" assert raised.value.detail["missing_sections"] == ["definition"] def test_parse_relation_artifact_links_entity_endpoints(tmp_path: Path) -> None: path = tmp_path / "relation.md" path.write_text(RELATION, encoding="utf-8") entity_ids = { "division-of-labour": "entity/division.md", "market-extent": "entity/market.md", } relation = parse_relation_artifact("relation/division-market.md", path, entity_ids) assert relation.slug == "division-of-labour-enables-market-extent" assert relation.subject_slug == "division-of-labour" assert relation.object_slug == "market-extent" assert relation.subject_entity_id == "entity/division.md" assert relation.object_entity_id == "entity/market.md" assert relation.is_feedback_member is True def test_parse_relation_artifact_reports_unresolved_endpoint(tmp_path: Path) -> None: path = tmp_path / "relation.md" path.write_text(RELATION, encoding="utf-8") with pytest.raises(InfospaceError) as raised: parse_relation_artifact("relation/division-market.md", path, {}) assert raised.value.code == "unresolved_relation_endpoint" assert raised.value.detail["missing_slugs"] == [ "division-of-labour", "market-extent", ] def test_list_entities_and_relations_from_manifest(tmp_path: Path) -> None: infospace = create_infospace(tmp_path, "pilot", name="Pilot") division = tmp_path / "division.md" division.write_text(ENTITY, encoding="utf-8") market = tmp_path / "market.md" market.write_text( ENTITY.replace("Division of Labour", "Market Extent").replace( "Production", "Exchange" ), encoding="utf-8", ) relation = tmp_path / "relation.md" relation.write_text(RELATION, encoding="utf-8") add_artifact(infospace.root, division, kind="entity", title="Division") add_artifact(infospace.root, market, kind="entity", title="Market") add_artifact(infospace.root, relation, kind="relation", title="Relation") assert [entity.slug for entity in list_entities(infospace.root)] == [ "division-of-labour", "market-extent", ] assert [item.relation_type for item in list_relations(infospace.root)] == [ "constrains" ] def test_cli_entities_and_relations_output_json(tmp_path: Path) -> None: infospace = create_infospace(tmp_path, "pilot", name="Pilot") division = tmp_path / "division.md" division.write_text(ENTITY, encoding="utf-8") market = tmp_path / "market.md" market.write_text( ENTITY.replace("Division of Labour", "Market Extent").replace( "Production", "Exchange" ), encoding="utf-8", ) relation = tmp_path / "relation.md" relation.write_text(RELATION, encoding="utf-8") add_artifact(infospace.root, division, kind="entity", title="Division") add_artifact(infospace.root, market, kind="entity", title="Market") add_artifact(infospace.root, relation, kind="relation", title="Relation") entities = subprocess.run( [sys.executable, "-m", "infospace_bench", "entities", str(infospace.root)], check=False, env=cli_env(), text=True, capture_output=True, ) relations = subprocess.run( [sys.executable, "-m", "infospace_bench", "relations", str(infospace.root)], check=False, env=cli_env(), text=True, capture_output=True, ) assert entities.returncode == 0, entities.stderr assert relations.returncode == 0, relations.stderr assert json.loads(entities.stdout)["entities"][0]["slug"] == "division-of-labour" assert json.loads(relations.stdout)["relations"][0]["subject_entity_id"] == ( "entity/division.md" )