diff --git a/tests/test_agent_skill_workflow_contract.py b/tests/test_agent_skill_workflow_contract.py new file mode 100644 index 0000000..8697733 --- /dev/null +++ b/tests/test_agent_skill_workflow_contract.py @@ -0,0 +1,221 @@ +from pathlib import Path +import tomllib + + +ROOT = Path(__file__).resolve().parents[1] +AGENT_DIR = ROOT / ".codex" / "agents" +SKILL_DIR = ROOT / ".codex" / "skills" + +EXPECTED_AGENTS = { + "coordinator-agent", + "correction-agent", + "formulation-agent", + "implementation-agent", + "implementation-planning-agent", + "io-definition-agent", + "numerical-review-agent", + "physics-evaluation-agent", + "release-agent", + "requirement-agent", + "research-agent", +} + +EXPECTED_SKILLS = { + "fem-theory-query", + "fesa-cpp-msvc-tdd", + "fesa-formulation-spec", + "fesa-io-contract", + "fesa-numerical-review", + "fesa-physics-sanity", + "fesa-release-readiness", + "fesa-requirements-baseline", + "fesa-research-evidence", +} + +LEGACY_OUTPUT_DIRS = { + "coordination", + "requirements", + "research", + "formulations", + "numerical-reviews", + "io-definitions", + "reference-models", + "implementation-plans", + "build-test-reports", + "corrections", + "reference-verifications", + "physics-evaluations", + "releases", +} + +FEATURE_FILES = { + "linear-static-3d-euler-beam": { + "coordination.md", + "requirements.md", + "research.md", + "formulation.md", + "numerical-review.md", + "reference-model.md", + "io.md", + "implementation-plan.md", + "implementation-report.md", + "build-test.md", + "reference-comparison.md", + "physics-evaluation.md", + "release.md", + }, + "linear-static-mitc4-shell": { + "coordination.md", + "requirements.md", + "research.md", + "formulation.md", + "numerical-review.md", + "reference-model.md", + "io.md", + "implementation-plan.md", + "build-test.md", + "reference-comparison.md", + "physics-evaluation.md", + "release.md", + }, +} + +LIVE_CONTRACT_FILES = ( + ROOT / "AGENTS.md", + ROOT / "docs" / "SOLVER_AGENT_DESIGN.md", + ROOT / "docs" / "SOLVER_SKILL_DESIGN.md", +) + +RETIRED_TOKENS = ( + "reference-model-agent", + "reference model agent", + "build-test-executor-agent", + "build/test executor agent", + "reference-verification-agent", + "reference verification agent", + "fesa-reference-models", + "fesa-reference-comparison", + "docs/coordination/", + "docs/requirements/", + "docs/research/", + "docs/formulations/", + "docs/numerical-reviews/", + "docs/io-definitions/", + "docs/reference-models/", + "docs/implementation-plans/", + "docs/build-test-reports/", + "docs/corrections/", + "docs/reference-verifications/", + "docs/physics-evaluations/", + "docs/releases/", +) + + +def read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def live_contract_text() -> str: + paths = list(LIVE_CONTRACT_FILES) + paths.extend(sorted(AGENT_DIR.glob("*.toml"))) + paths.extend(sorted(SKILL_DIR.glob("*/SKILL.md"))) + return "\n".join(read(path) for path in paths) + + +def test_agent_inventory_is_consolidated(): + actual = {path.stem for path in AGENT_DIR.glob("*.toml")} + assert actual == EXPECTED_AGENTS + + +def test_project_skill_inventory_is_consolidated(): + actual = { + path.name + for path in SKILL_DIR.iterdir() + if path.is_dir() and (path / "SKILL.md").is_file() + } + assert actual == EXPECTED_SKILLS + + +def test_existing_feature_artifacts_are_bundled(): + docs_dir = ROOT / "docs" + for legacy_dir in LEGACY_OUTPUT_DIRS: + assert not (docs_dir / legacy_dir).exists(), legacy_dir + + for feature_id, expected_files in FEATURE_FILES.items(): + feature_dir = docs_dir / feature_id + actual_files = {path.name for path in feature_dir.glob("*.md")} + assert actual_files == expected_files + + +def test_numerical_review_absorbs_reference_model_contract(): + assert not (AGENT_DIR / "reference-model-agent.toml").exists() + assert not (SKILL_DIR / "fesa-reference-models").exists() + text = read(SKILL_DIR / "fesa-numerical-review" / "SKILL.md").lower() + for marker in ( + "numerical-review.md", + "reference-model.md", + "reference case inventory", + "source identity", + "row precheck", + "tolerance", + "pass-for-io-definition", + ): + assert marker in text + + +def test_implementation_absorbs_build_test_and_reference_comparison(): + assert not (AGENT_DIR / "build-test-executor-agent.toml").exists() + assert not (AGENT_DIR / "reference-verification-agent.toml").exists() + assert not (SKILL_DIR / "fesa-reference-comparison").exists() + text = read(SKILL_DIR / "fesa-cpp-msvc-tdd" / "SKILL.md").lower() + for marker in ( + "implementation-report.md", + "build-test.md", + "reference-comparison.md", + "artifact check -> compare -> classify -> report", + "pass-for-physics-evaluation", + ): + assert marker in text + + +def test_agent_hierarchy_is_explicit(): + coordinator = read(AGENT_DIR / "coordinator-agent.toml").lower() + assert "main agent" in coordinator + assert "docs//coordination.md" in coordinator + assert "worklist" in coordinator + assert "sub-agent dispatch" in coordinator + + for path in sorted(AGENT_DIR.glob("*.toml")): + if path.name == "coordinator-agent.toml": + continue + text = read(path).lower() + assert "sub-agent" in text, path.name + assert "coordinator agent" in text, path.name + + +def test_live_contracts_have_no_retired_names_or_paths(): + text = live_contract_text().lower() + assert "docs//" in text + for token in RETIRED_TOKENS: + assert token not in text, token + + +def test_live_guidance_declares_eight_stage_workflow(): + for path in LIVE_CONTRACT_FILES: + text = read(path) + assert "8단계" in text, path + assert "docs//" in text, path + + +def test_agent_toml_and_skill_ui_metadata_are_parseable(): + for path in sorted(AGENT_DIR.glob("*.toml")): + parsed = tomllib.loads(read(path)) + assert parsed["name"] == path.stem + assert parsed["model_reasoning_effort"] == "extra high" + + for skill_name in EXPECTED_SKILLS: + skill_dir = SKILL_DIR / skill_name + skill_text = read(skill_dir / "SKILL.md") + ui_text = read(skill_dir / "agents" / "openai.yaml") + assert f"name: {skill_name}" in skill_text + assert f"${skill_name}" in ui_text