import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SKILLS_ROOT = ROOT / ".codex" / "skills" COMMON_SECTIONS = ( "## Inputs", "## Workflow", "## Output Contract", "## Boundaries", "## Quality Gate", "## Handoff", ) ACTIVE_CONTRACT_FILES = ( ROOT / "AGENTS.md", ROOT / "docs" / "ProjectInitialPlanNote.md", ROOT / "docs" / "PRD.md", ROOT / "docs" / "ARCHITECTURE.md", ROOT / "docs" / "ADR.md", ROOT / "docs" / "SOLVER_AGENT_DESIGN.md", ROOT / "docs" / "SOLVER_SKILL_DESIGN.md", ROOT / "docs" / "reference-models" / "README.md", ROOT / "docs" / "reference-verifications" / "README.md", ROOT / "docs" / "io-definitions" / "README.md", ROOT / "docs" / "implementation-plans" / "README.md", ROOT / "docs" / "physics-evaluations" / "README.md", ROOT / "docs" / "requirements" / "README.md", ROOT / ".codex" / "agents" / "reference-model-agent.toml", ROOT / ".codex" / "agents" / "reference-verification-agent.toml", ROOT / ".codex" / "agents" / "io-definition-agent.toml", ROOT / ".codex" / "agents" / "implementation-planning-agent.toml", ROOT / ".codex" / "agents" / "implementation-agent.toml", ROOT / ".codex" / "agents" / "physics-evaluation-agent.toml", ROOT / ".codex" / "agents" / "release-agent.toml", ROOT / ".codex" / "agents" / "requirement-agent.toml", ROOT / ".codex" / "agents" / "coordinator-agent.toml", ROOT / ".codex" / "skills" / "fesa-reference-models" / "SKILL.md", ROOT / ".codex" / "skills" / "fesa-reference-comparison" / "SKILL.md", ROOT / ".codex" / "skills" / "fesa-io-contract" / "SKILL.md", ROOT / ".codex" / "skills" / "fesa-physics-sanity" / "SKILL.md", ROOT / ".codex" / "skills" / "fesa-cpp-msvc-tdd" / "SKILL.md", ROOT / ".codex" / "skills" / "fesa-release-readiness" / "SKILL.md", ROOT / ".codex" / "skills" / "fesa-requirements-baseline" / "SKILL.md", ) STALE_REFERENCE_CONTRACT_PHRASES = ( "reference" ".h5", "stored reference " "HDF5", "reference " "HDF5 artifact", "results.h5 and " "reference" ".h5", "results.h5` and `reference" ".h5", "derived from reference" ".h5", "references/" "//", ) SKILLS = { "fesa-requirements-baseline": { "description_terms": ( "Use when", "FESA solver", "requirements", "acceptance criteria", "verification matrix", ), "body_terms": ( "docs/requirements/.md", "Requirement Verification Matrix", "shall", "FESA-REQ--###", "Verification Quantities", "Tolerance Policy", "Reference Artifact Requirements", "Do not implement C++ code.", ), }, "fesa-research-evidence": { "description_terms": ( "Use when", "FESA solver", "research", "FEM theory", "benchmarks", ), "body_terms": ( "docs/research/-research.md", "Source Inventory", "Source Reliability Tier", "Candidate Benchmarks", "Verification Relevance", "Applicability Limits", "Separate verified facts from inference.", ), }, "fesa-formulation-spec": { "description_terms": ( "Use when", "FESA FEM", "formulation", "element equations", "output recovery", ), "body_terms": ( "docs/formulations/-formulation.md", "Strong Form", "Weak or Variational Form", "Discretization", "Kinematics", "Element Equations", "Jacobian", "Output Recovery", "Do not design C++ APIs.", ), }, "fesa-numerical-review": { "description_terms": ( "Use when", "FESA FEM", "numerical review", "stability", "implementation planning", ), "body_terms": ( "docs/numerical-reviews/-review.md", "pass-for-implementation-planning", "rigid body modes", "patch test", "hourglass", "locking", "Jacobian", "Do not edit formulations directly.", ), }, "fesa-io-contract": { "description_terms": ( "Use when", "FESA solver", "Abaqus .inp", "HDF5", "CSV", "I/O", ), "body_terms": ( "docs/io-definitions/-io.md", "Abaqus Input Scope", "Internal Model Contract", "Output HDF5 Schema", "FESA HDF5 to Reference CSV Comparison Schema", "results.h5", "reference//", "*NODE", "*ELEMENT", "*MATERIAL", "*BOUNDARY", "*STEP", "Do not implement parsers.", ), }, "fesa-reference-models": { "description_terms": ( "Use when", "FESA", "reference model", "Abaqus input", "CSV", ), "body_terms": ( "docs/reference-models/-reference-models.md", "reference//", "model.inp", "metadata.json", "_displacements.csv", "_reactions.csv", "_internalforces.csv", "_stresses.csv", "Coverage Matrix", "Do not generate or modify Abaqus reference CSV files.", ), }, "fesa-cpp-msvc-tdd": { "description_terms": ( "Use when", "FESA solver", "C++", "MSVC", "TDD", ), "body_terms": ( "docs/implementation-plans/-implementation-plan.md", "RED -> GREEN -> VERIFY", "python -m unittest discover -s scripts -p \"test_*.py\"", "python scripts/validate_workspace.py", "ctest", "configure | compile | link | test | reference-comparison", "Do not change requirements.", ), }, "fesa-reference-comparison": { "description_terms": ( "Use when", "FESA solver", "HDF5", "reference CSV", "tolerance", "comparison", ), "body_terms": ( "docs/reference-verifications/-reference-verification.md", "ARTIFACT CHECK -> COMPARE -> CLASSIFY -> REPORT", "results.h5", "Abaqus reference CSV", "reference//", "_displacements.csv", "_reactions.csv", "_internalforces.csv", "_stresses.csv", "max absolute error", "max relative error", "RMS error", "missing rows", "extra rows", "pass-for-physics-evaluation", "Do not change tolerance policies.", ), }, "fesa-physics-sanity": { "description_terms": ( "Use when", "FESA solver", "physical plausibility", "equilibrium", "physics", ), "body_terms": ( "docs/physics-evaluations/-physics-evaluation.md", "global equilibrium", "reaction consistency", "displacement direction", "symmetry", "element force balance", "model coverage", "pass-for-release-agent", "Do not approve release readiness.", ), }, "fesa-release-readiness": { "description_terms": ( "Use when", "FESA solver", "release readiness", "release notes", "known limitations", ), "body_terms": ( "docs/releases/-release.md", "GATE AUDIT -> TRACEABILITY CHECK -> RELEASE DOCUMENTATION -> RELEASE VERDICT", "ready-for-release", "Known Limitations", "Release Notes Draft", "pass-for-reference-verification", "pass-for-physics-evaluation", "pass-for-release-agent", "Do not publish, deploy, package, tag, commit, or externally release anything unless the user explicitly asks.", ), }, } def read_skill(skill_name): return (SKILLS_ROOT / skill_name / "SKILL.md").read_text(encoding="utf-8") def parse_frontmatter(text): lines = text.splitlines() if not lines or lines[0] != "---": raise AssertionError("SKILL.md must start with YAML frontmatter") fields = {} for line in lines[1:]: if line == "---": return fields key, sep, value = line.partition(":") if not sep: raise AssertionError(f"Invalid frontmatter line: {line}") fields[key.strip()] = value.strip() raise AssertionError("SKILL.md frontmatter must be closed") class FesaSolverSkillTests(unittest.TestCase): def test_all_solver_skill_files_exist_with_required_frontmatter(self): for skill_name, spec in SKILLS.items(): with self.subTest(skill=skill_name): skill_path = SKILLS_ROOT / skill_name / "SKILL.md" self.assertTrue(skill_path.exists(), f"{skill_name} SKILL.md is missing") fields = parse_frontmatter(read_skill(skill_name)) self.assertEqual(set(fields), {"name", "description"}) self.assertEqual(fields["name"], skill_name) for term in spec["description_terms"]: self.assertIn(term, fields["description"]) def test_all_solver_skills_define_common_contract_sections(self): for skill_name in SKILLS: with self.subTest(skill=skill_name): body = read_skill(skill_name) for section in COMMON_SECTIONS: self.assertIn(section, body) self.assertIn("AGENTS.md", body) self.assertIn("docs/SOLVER_AGENT_DESIGN.md", body) self.assertNotIn("docs/SOLVER_SKILL_DESIGN.md", body) def test_solver_skills_define_skill_specific_contracts(self): for skill_name, spec in SKILLS.items(): with self.subTest(skill=skill_name): body = read_skill(skill_name) for term in spec["body_terms"]: self.assertIn(term, body) def test_solver_skills_have_openai_ui_metadata(self): for skill_name in SKILLS: with self.subTest(skill=skill_name): metadata = SKILLS_ROOT / skill_name / "agents" / "openai.yaml" self.assertTrue(metadata.exists(), f"{skill_name} openai.yaml is missing") text = metadata.read_text(encoding="utf-8") self.assertIn("interface:", text) self.assertIn("display_name:", text) self.assertIn("short_description:", text) self.assertIn("default_prompt:", text) self.assertIn(f"${skill_name}", text) def test_active_contracts_do_not_require_reference_hdf5(self): for path in ACTIVE_CONTRACT_FILES: with self.subTest(path=str(path.relative_to(ROOT))): text = path.read_text(encoding="utf-8") for stale_phrase in STALE_REFERENCE_CONTRACT_PHRASES: self.assertNotIn(stale_phrase, text) if __name__ == "__main__": unittest.main()