#!/usr/bin/env python3 """Static contracts for the large Bash ods CLI. These checks intentionally avoid executing ods-cli. They are a decomposition guardrail: before the CLI is split into smaller files, the public command help, main dispatch table, and command implementation functions should agree. """ from __future__ import annotations import re from pathlib import Path ROOT_DIR = Path(__file__).resolve().parents[2] ODS_CLI = ROOT_DIR / "ods-cli" COMMANDS = { "gpu": ("cmd_gpu", "gpu [status|topology|assignment|validate|reassign]"), "status": ("cmd_status", "status [--json]"), "status-json": ("cmd_status_json", "status-json"), "list": ("cmd_list", "list [--json]"), "enable": ("cmd_enable", "enable "), "disable": ("cmd_disable", "disable "), "purge": ("cmd_purge", "purge "), "preset": ("cmd_preset", "preset "), "mode": ("cmd_mode", "mode [local|cloud|hybrid]"), "model": ("cmd_model", "model [current|list|swap]"), "stt": ("cmd_stt", "stt [current|status|download]"), "backup": ("cmd_backup", "backup [options]"), "restore": ("cmd_restore", "restore [backup_id]"), "rollback": ("cmd_rollback", "rollback"), "logs": ("cmd_logs", "logs "), "restart": ("cmd_restart", "restart [service]"), "repair": ("cmd_repair", ("repair|fix [voice]", "repair|fix [voice|hermes-workers]")), "start": ("cmd_start", "start [service]"), "stop": ("cmd_stop", "stop [service]"), "update": ("cmd_update", "update [--force]"), "shell": ("cmd_shell", "shell "), "config": ("cmd_config", "config [show|edit|validate]"), "chat": ("cmd_chat", 'chat ""'), "benchmark": ("cmd_benchmark", "benchmark"), "doctor": ("cmd_doctor", "doctor [report|--json]"), "audit": ("cmd_audit", "audit [extensions]"), "template": ("cmd_template", "template [action]"), "agent": ("cmd_agent", "agent [action]"), } def fail(message: str) -> None: print(f"[FAIL] {message}") raise SystemExit(1) def require(pattern: str, text: str, message: str) -> None: if not re.search(pattern, text, flags=re.MULTILINE): fail(message) def dispatch_pattern(command: str, function_name: str) -> str: """Match a main-case branch where command may have aliases.""" token = re.escape(command) return ( rf"^\s*(?:[A-Za-z0-9_-]+\|)*{token}" rf"(?:\|[A-Za-z0-9_-]+)*\)\s*(?:shift;\s*)?{function_name}\b" ) def main() -> int: if not ODS_CLI.exists(): fail(f"Missing ods CLI: {ODS_CLI}") text = ODS_CLI.read_text(encoding="utf-8") require(r"^#!/usr/bin/env bash$", text, "ods-cli should remain a Bash entrypoint") require( r"Usage: ods \[options\]", text, "ods-cli help must expose the top-level usage line", ) require( r'^case "\$\{1:-help\}" in$', text, "ods-cli must keep a top-level command dispatch case", ) require(r"^cmd_help\(\) \{$", text, "ods-cli must define cmd_help") require( r'"\$_ods_mode" != "cloud"[\s\S]*"\$_external_lemonade_active" != "true"[\s\S]*docker-compose\.cloud\.yml', text, "ods-cli must reject stale cloud compose caches in local/managed modes", ) for command, (function_name, help_snippet) in COMMANDS.items(): require( rf"^{function_name}\(\) \{{", text, f"{command} is missing implementation function {function_name}", ) help_snippets = (help_snippet,) if isinstance(help_snippet, str) else help_snippet if not any(snippet in text for snippet in help_snippets): expected = " or ".join(help_snippets) fail(f"{command} is missing help entry: {expected}") require( dispatch_pattern(command, function_name), text, f"{command} is missing main dispatch to {function_name}", ) print("[PASS] ods-cli static command contract") return 0 if __name__ == "__main__": raise SystemExit(main())