"""Integration: every `tht ...` command the gate invokes must exist in the Typer CLI. Root cause of the Blocco 1 critical bugs: the gate (tht-gate.js) and the Python CLI were ported separately and never run together, so the gate called commands/flags that did not exist (`phase advance --if-ready`, `cte plan` without `--name`, `set-question --session` on a positional arg). This test extracts every `["group","sub",...,"--flag"]` array literal from tht-gate.js and asserts, via `tht --help`, that the subcommand exists (exit 0) and that each long flag used is offered. It makes that whole class of drift impossible to reintroduce silently. """ from __future__ import annotations import re import subprocess import sys from pathlib import Path import pytest _ROOT = Path(__file__).resolve().parent.parent.parent _GATE = _ROOT / ".pi" / "extensions" / "tht-gate.js" _THT = Path(sys.executable).parent / "tht" # The command groups the gate drives. Anything else in an array literal is data, not a CLI call. _GROUPS = {"phase", "session", "cte", "decision", "memory", "search", "schema", "vector"} # Flags that are framework/JS artifacts, never real CLI options (skip from the check). _SKIP_FLAGS: set[str] = set() def _extract_invocations() -> list[tuple[str, str, list[str], str]]: """Returns (group, subcommand, long_flags, raw) for each gate CLI call site.""" src = _GATE.read_text() # array literal opening with two string literals: ["group", "sub" ... pattern = re.compile(r'\[\s*"([a-z-]+)"\s*,\s*"([a-z][a-z-]*)"((?:\s*,\s*[^\]\[]+?)?)\]') out: list[tuple[str, str, list[str], str]] = [] for m in pattern.finditer(src): group, sub, tail = m.group(1), m.group(2), m.group(3) if group not in _GROUPS: continue flags = [f for f in re.findall(r'"(--[a-z][a-z-]*)"', tail) if f not in _SKIP_FLAGS] out.append((group, sub, flags, m.group(0))) return out _INVOCATIONS = _extract_invocations() def test_gate_invokes_at_least_the_known_commands(): """Guards the extractor itself: if it silently matches nothing, the test is useless.""" pairs = {(g, s) for g, s, _, _ in _INVOCATIONS} assert ("phase", "advance") in pairs assert ("cte", "plan") in pairs assert ("session", "set-question") in pairs assert ("decision", "add") in pairs @pytest.mark.skipif(not _THT.exists(), reason="tht CLI not installed in this venv") @pytest.mark.parametrize("group,sub,flags,raw", _INVOCATIONS, ids=lambda v: v if isinstance(v, str) else None) def test_gate_call_site_matches_cli(group: str, sub: str, flags: list[str], raw: str): res = subprocess.run( [str(_THT), group, sub, "--help"], capture_output=True, text=True, cwd=str(_ROOT), ) assert res.returncode == 0, ( f"gate calls `tht {group} {sub}` but it does not exist in the CLI.\n" f"call site: {raw}\nstderr: {res.stderr}" ) help_text = res.stdout + res.stderr for flag in flags: assert flag in help_text, ( f"gate passes `{flag}` to `tht {group} {sub}` but the CLI does not offer it.\n" f"call site: {raw}" )