345 lines
12 KiB
Python
345 lines
12 KiB
Python
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/" "<feature-id>/<model-id>/",
|
|
)
|
|
|
|
|
|
SKILLS = {
|
|
"fesa-requirements-baseline": {
|
|
"description_terms": (
|
|
"Use when",
|
|
"FESA solver",
|
|
"requirements",
|
|
"acceptance criteria",
|
|
"verification matrix",
|
|
),
|
|
"body_terms": (
|
|
"docs/requirements/<feature-id>.md",
|
|
"Requirement Verification Matrix",
|
|
"shall",
|
|
"FESA-REQ-<FEATURE>-###",
|
|
"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/<feature-id>-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/<feature-id>-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/<feature-id>-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/<feature-id>-io.md",
|
|
"Abaqus Input Scope",
|
|
"Internal Model Contract",
|
|
"Output HDF5 Schema",
|
|
"FESA HDF5 to Reference CSV Comparison Schema",
|
|
"results.h5",
|
|
"reference/<model-id>/",
|
|
"*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/<feature-id>-reference-models.md",
|
|
"reference/<model-id>/",
|
|
"model.inp",
|
|
"metadata.json",
|
|
"<model-id>_displacements.csv",
|
|
"<model-id>_reactions.csv",
|
|
"<model-id>_internalforces.csv",
|
|
"<model-id>_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/<feature-id>-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/<feature-id>-reference-verification.md",
|
|
"ARTIFACT CHECK -> COMPARE -> CLASSIFY -> REPORT",
|
|
"results.h5",
|
|
"Abaqus reference CSV",
|
|
"reference/<model-id>/",
|
|
"<model-id>_displacements.csv",
|
|
"<model-id>_reactions.csv",
|
|
"<model-id>_internalforces.csv",
|
|
"<model-id>_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/<feature-id>-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/<feature-id>-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()
|