44import sys
55from pathlib import Path
66
7- # Docs to scan for file path references
7+ # Docs to scan for file path references (all hand-written docs with code refs)
88DOCS = [
99 "docs/documentation/contributing.md" ,
1010 "docs/documentation/gpuParallelization.md" ,
1111 "docs/documentation/running.md" ,
1212 "docs/documentation/case.md" ,
1313 "docs/documentation/equations.md" ,
14+ "docs/documentation/testing.md" ,
15+ "docs/documentation/getting-started.md" ,
16+ "docs/documentation/docker.md" ,
17+ "docs/documentation/troubleshooting.md" ,
18+ "docs/documentation/visualization.md" ,
19+ "docs/documentation/expectedPerformance.md" ,
1420 ".github/copilot-instructions.md" ,
1521]
1622
@@ -386,6 +392,143 @@ def check_page_refs(repo_root: Path) -> list[str]:
386392 return errors
387393
388394
395+ def check_physics_docs_coverage (repo_root : Path ) -> list [str ]:
396+ """Check that all check methods with enforcement calls have PHYSICS_DOCS entries."""
397+ toolchain_dir = str (repo_root / "toolchain" )
398+ if toolchain_dir not in sys .path :
399+ sys .path .insert (0 , toolchain_dir )
400+ try :
401+ from mfc .case_validator import PHYSICS_DOCS # pylint: disable=import-outside-toplevel
402+ from mfc .params .ast_analyzer import analyze_case_validator # pylint: disable=import-outside-toplevel
403+ except ImportError :
404+ return []
405+
406+ # Methods that are purely structural/mechanical and don't need physics docs
407+ skip = {
408+ "check_parameter_types" , # type validation, not physics
409+ "check_output_format" , # output format selection
410+ "check_restart" , # restart file logistics
411+ "check_parallel_io_pre_process" , # parallel I/O settings
412+ "check_misc_pre_process" , # miscellaneous pre-process flags
413+ "check_bc_patches" , # boundary patch geometry
414+ "check_grid_stretching" , # grid stretching parameters
415+ "check_qbmm_pre_process" , # QBMM pre-process settings
416+ "check_probe_integral_output" , # probe/integral output settings
417+ "check_finite_difference" , # fd_order value validation
418+ "check_flux_limiter" , # output dimension requirements
419+ "check_liutex_post" , # output dimension requirements
420+ "check_momentum_post" , # output dimension requirements
421+ "check_velocity_post" , # output dimension requirements
422+ "check_surface_tension_post" , # output feature dependency
423+ "check_no_flow_variables" , # output variable selection
424+ "check_partial_domain" , # output format settings
425+ "check_perturb_density" , # parameter pairing validation
426+ "check_qm" , # output dimension requirements
427+ "check_chemistry" , # runtime Cantera validation only
428+ }
429+
430+ validator_path = repo_root / "toolchain" / "mfc" / "case_validator.py"
431+ analysis = analyze_case_validator (validator_path )
432+ rules = analysis ["rules" ]
433+
434+ # Find methods that have at least one prohibit/warn call
435+ methods_with_rules = {r .method for r in rules }
436+
437+ errors = []
438+ for method in sorted (methods_with_rules ):
439+ if method in PHYSICS_DOCS :
440+ continue
441+ if method in skip :
442+ continue
443+ errors .append (
444+ f" { method } has validation rules but no PHYSICS_DOCS entry."
445+ " Fix: add entry to PHYSICS_DOCS in case_validator.py"
446+ " or add to skip set in lint_docs.py"
447+ )
448+
449+ return errors
450+
451+
452+ # Important Python identifiers in contributing.md mapped to files where they must exist.
453+ # If an identifier is renamed or removed, this check catches the stale doc reference.
454+ _CONTRIBUTING_IDENTIFIERS = {
455+ "PHYSICS_DOCS" : "toolchain/mfc/case_validator.py" ,
456+ "CONSTRAINTS" : "toolchain/mfc/params/definitions.py" ,
457+ "DEPENDENCIES" : "toolchain/mfc/params/definitions.py" ,
458+ "REGISTRY" : "toolchain/mfc/params/__init__.py" ,
459+ }
460+
461+
462+ def check_identifier_refs (repo_root : Path ) -> list [str ]:
463+ """Check that important identifiers referenced in contributing.md still exist."""
464+ doc_path = repo_root / "docs" / "documentation" / "contributing.md"
465+ if not doc_path .exists ():
466+ return []
467+
468+ text = _strip_code_blocks (doc_path .read_text (encoding = "utf-8" ))
469+ errors = []
470+
471+ for identifier , source_file in _CONTRIBUTING_IDENTIFIERS .items ():
472+ # Check identifier is actually referenced in the doc
473+ if f"`{ identifier } " not in text :
474+ continue
475+ source_path = repo_root / source_file
476+ if not source_path .exists ():
477+ errors .append (
478+ f" contributing.md references `{ identifier } ` in { source_file } "
479+ f" but { source_file } does not exist"
480+ )
481+ continue
482+ source_text = source_path .read_text (encoding = "utf-8" )
483+ if identifier not in source_text :
484+ errors .append (
485+ f" contributing.md references `{ identifier } ` but it was not"
486+ f" found in { source_file } . Fix: update the docs or the identifier"
487+ )
488+
489+ return errors
490+
491+
492+ # Match ./mfc.sh <command> patterns (the subcommand name)
493+ _CLI_CMD_RE = re .compile (r"\./mfc\.sh\s+([a-z][a-z_-]*)" )
494+
495+
496+ def check_cli_refs (repo_root : Path ) -> list [str ]:
497+ """Check that ./mfc.sh commands referenced in docs exist in the CLI schema."""
498+ toolchain_dir = str (repo_root / "toolchain" )
499+ if toolchain_dir not in sys .path :
500+ sys .path .insert (0 , toolchain_dir )
501+ try :
502+ from mfc .cli .commands import MFC_CLI_SCHEMA # pylint: disable=import-outside-toplevel
503+ except ImportError :
504+ return []
505+
506+ valid_commands = {cmd .name for cmd in MFC_CLI_SCHEMA .commands }
507+ # Also accept "init" (shell function) and "load" (shell function)
508+ valid_commands .update ({"init" , "load" })
509+
510+ errors = []
511+ doc_path = repo_root / "docs" / "documentation" / "running.md"
512+ if not doc_path .exists ():
513+ return []
514+
515+ text = _strip_code_blocks (doc_path .read_text (encoding = "utf-8" ))
516+ seen = set ()
517+ for match in _CLI_CMD_RE .finditer (text ):
518+ cmd = match .group (1 )
519+ if cmd in seen or cmd in valid_commands :
520+ seen .add (cmd )
521+ continue
522+ seen .add (cmd )
523+ errors .append (
524+ f" running.md references './mfc.sh { cmd } ' but '{ cmd } '"
525+ " is not a known CLI command."
526+ " Fix: update the command name or remove the reference"
527+ )
528+
529+ return errors
530+
531+
389532def main ():
390533 repo_root = Path (__file__ ).resolve ().parents [2 ]
391534
@@ -397,6 +540,9 @@ def main():
397540 all_errors .extend (check_math_syntax (repo_root ))
398541 all_errors .extend (check_doxygen_percent (repo_root ))
399542 all_errors .extend (check_section_anchors (repo_root ))
543+ all_errors .extend (check_physics_docs_coverage (repo_root ))
544+ all_errors .extend (check_identifier_refs (repo_root ))
545+ all_errors .extend (check_cli_refs (repo_root ))
400546
401547 if all_errors :
402548 print ("Doc reference check failed:" )
0 commit comments