Fix CLI help/runtime imports and add doctor command (#4)
* Make core CLI help robust and intuitive * Package runtime support modules for core CLI * Add CLI help and doctor tests * Fix CLI trace help and pack listing * Export language pack listing helper * Bootstrap repo root for console runtime imports * Align trace formatter with Proposition schema * Cover real trace payload formatting
This commit is contained in:
parent
7f302760ef
commit
09c3664773
4 changed files with 370 additions and 139 deletions
421
core/cli.py
421
core/cli.py
|
|
@ -1,180 +1,323 @@
|
|||
"""core CLI — single entry point for the versor engine.
|
||||
|
||||
Subcommands
|
||||
-----------
|
||||
core chat Start interactive chat session (delegates to python -m chat)
|
||||
core test [args] Run pytest suite with sane defaults
|
||||
core check Run ruff on the whole project
|
||||
core trace <text> Trace one turn: show field condition, dialogue role, proposition
|
||||
core oov <token> Ground a single unknown token and show the constructed versor info
|
||||
core pack list List mounted language packs
|
||||
core pack verify <id> Verify a language pack checksum
|
||||
"""
|
||||
"""Command line interface for the CORE versor engine."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import NoReturn
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _run(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(args, check=check, text=True)
|
||||
# The `core` console script may be installed through stale editable metadata while
|
||||
# this repo is moving quickly. Ensure sibling top-level packages such as
|
||||
# alignment/, morphology/, and sensorium/ are importable from the checked-out
|
||||
# source tree before any runtime imports execute.
|
||||
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(_REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_REPO_ROOT))
|
||||
|
||||
|
||||
def _die(msg: str) -> NoReturn:
|
||||
print(f"error: {msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
DESCRIPTION = "CORE versor engine command suite."
|
||||
EPILOG = "Examples:\n core chat\n core trace \"word beginning truth\"\n core trace --pack en_minimal_v1 --json \"word beginning truth\"\n core oov covenant\n core pack list\n core pack verify en_minimal_v1\n core test tests/test_alignment_graph.py -q"
|
||||
|
||||
|
||||
def _usage() -> NoReturn:
|
||||
print(__doc__)
|
||||
sys.exit(0)
|
||||
def _run(*args: str, check: bool = False) -> int:
|
||||
"""Run a child command and return its exit code."""
|
||||
completed = subprocess.run(args, check=check, text=True)
|
||||
return int(completed.returncode)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Subcommands
|
||||
# ---------------------------------------------------------------------------
|
||||
def _die(message: str, *, code: int = 2) -> NoReturn:
|
||||
print(f"error: {message}", file=sys.stderr)
|
||||
raise SystemExit(code)
|
||||
|
||||
def cmd_chat(argv: list[str]) -> None:
|
||||
|
||||
def _print_runtime_import_hint(exc: BaseException) -> NoReturn:
|
||||
_die(
|
||||
"runtime import failed. Run `core doctor` to inspect packaging. Root cause: "
|
||||
f"{exc.__class__.__name__}: {exc}",
|
||||
code=1,
|
||||
)
|
||||
|
||||
|
||||
def cmd_chat(args: argparse.Namespace) -> int:
|
||||
"""Launch the readline REPL backed by ChatRuntime."""
|
||||
_run(sys.executable, "-m", "chat", *argv, check=False)
|
||||
return _run(sys.executable, "-m", "chat", *args.args)
|
||||
|
||||
|
||||
def cmd_test(argv: list[str]) -> None:
|
||||
"""Run the pytest suite. Extra args are forwarded to pytest."""
|
||||
def cmd_test(args: argparse.Namespace) -> int:
|
||||
"""Run pytest. Extra args are forwarded unchanged."""
|
||||
default_args = ["-q", "--tb=short"]
|
||||
_run(sys.executable, "-m", "pytest", *(argv or default_args), check=False)
|
||||
return _run(sys.executable, "-m", "pytest", *(args.args or default_args))
|
||||
|
||||
|
||||
def cmd_check(argv: list[str]) -> None:
|
||||
"""Run ruff over the project source."""
|
||||
targets = argv or [
|
||||
"algebra", "ingest", "field", "vocab", "vault", "persona",
|
||||
"generate", "session", "chat", "core", "language_packs", "tests",
|
||||
def cmd_check(args: argparse.Namespace) -> int:
|
||||
"""Run ruff over selected project paths."""
|
||||
targets = args.paths or [
|
||||
"algebra",
|
||||
"alignment",
|
||||
"chat",
|
||||
"core",
|
||||
"field",
|
||||
"generate",
|
||||
"ingest",
|
||||
"language_packs",
|
||||
"morphology",
|
||||
"persona",
|
||||
"sensorium",
|
||||
"session",
|
||||
"vault",
|
||||
"vocab",
|
||||
"tests",
|
||||
]
|
||||
_run(sys.executable, "-m", "ruff", "check", *targets, check=False)
|
||||
return _run(sys.executable, "-m", "ruff", "check", *targets)
|
||||
|
||||
|
||||
def cmd_trace(argv: list[str]) -> None:
|
||||
"""Trace one chat turn and print field telemetry."""
|
||||
if not argv:
|
||||
_die("usage: core trace <text>")
|
||||
text = " ".join(argv)
|
||||
def _runtime_for_trace(pack: list[str] | None):
|
||||
try:
|
||||
from chat.runtime import ChatRuntime
|
||||
except Exception as exc: # pragma: no cover - exercised by CLI in broken envs
|
||||
_print_runtime_import_hint(exc)
|
||||
pack_arg: str | tuple[str, ...]
|
||||
pack_arg = tuple(pack) if pack else ("en_minimal_v1", "he_logos_micro_v1", "grc_logos_micro_v1")
|
||||
try:
|
||||
return ChatRuntime(pack_arg)
|
||||
except Exception as exc:
|
||||
_die(
|
||||
"failed to initialize ChatRuntime. Check mounted language packs with "
|
||||
"`core pack list` and `core pack verify <pack_id>`. Root cause: "
|
||||
f"{exc.__class__.__name__}: {exc}",
|
||||
code=1,
|
||||
)
|
||||
|
||||
from chat.runtime import ChatRuntime
|
||||
rt = ChatRuntime()
|
||||
resp = rt.chat(text)
|
||||
|
||||
print(f"input : {text}")
|
||||
print(f"surface : {resp.surface}")
|
||||
print(f"dialogue_role : {resp.dialogue_role}")
|
||||
print(f"versor_cond : {resp.versor_condition:.2e}")
|
||||
p = resp.proposition
|
||||
print(f"proposition : {p.surface!r}")
|
||||
print(f" frame_id : {p.frame_id}")
|
||||
print(f" subject : {p.subject_surface!r}")
|
||||
print(f" predicate : {p.predicate_surface!r}")
|
||||
if hasattr(p, "object_surface") and p.object_surface:
|
||||
print(f" object : {p.object_surface!r}")
|
||||
def _trace_payload(text: str, resp: Any, runtime: Any) -> dict[str, Any]:
|
||||
import numpy as np
|
||||
print(f" relation_norm: {float(np.linalg.norm(p.relation)):.4f}")
|
||||
|
||||
session = rt.session
|
||||
print(f"vault_entries : {len(session.vault)}")
|
||||
oov = getattr(session.vocab, "unknown_token_log", [])
|
||||
if oov:
|
||||
print(f"oov_grounded : {len(oov)} token(s)")
|
||||
for entry in oov:
|
||||
proposition = resp.proposition
|
||||
payload: dict[str, Any] = {
|
||||
"input": text,
|
||||
"surface": resp.surface,
|
||||
"dialogue_role": str(resp.dialogue_role),
|
||||
"versor_condition": float(resp.versor_condition),
|
||||
"proposition": {
|
||||
"surface": proposition.surface,
|
||||
"frame_id": proposition.frame_id,
|
||||
"subject": proposition.subject,
|
||||
"predicate": proposition.predicate,
|
||||
"object": proposition.object_,
|
||||
"relation_norm": float(np.linalg.norm(proposition.relation)),
|
||||
},
|
||||
"vault_entries": len(runtime.session.vault),
|
||||
"oov_grounded": list(getattr(runtime.session.vocab, "unknown_token_log", [])),
|
||||
}
|
||||
return payload
|
||||
|
||||
|
||||
def _print_trace(payload: dict[str, Any]) -> None:
|
||||
print(f"input : {payload['input']}")
|
||||
print(f"surface : {payload['surface']}")
|
||||
print(f"dialogue_role : {payload['dialogue_role']}")
|
||||
print(f"versor_cond : {payload['versor_condition']:.2e}")
|
||||
proposition = payload["proposition"]
|
||||
print(f"proposition : {proposition['surface']!r}")
|
||||
print(f" frame_id : {proposition['frame_id']}")
|
||||
print(f" subject : {proposition['subject']!r}")
|
||||
print(f" predicate : {proposition['predicate']!r}")
|
||||
if proposition.get("object"):
|
||||
print(f" object : {proposition['object']!r}")
|
||||
print(f" relation_norm: {proposition['relation_norm']:.4f}")
|
||||
print(f"vault_entries : {payload['vault_entries']}")
|
||||
oov_entries = payload["oov_grounded"]
|
||||
if oov_entries:
|
||||
print(f"oov_grounded : {len(oov_entries)} token(s)")
|
||||
for entry in oov_entries:
|
||||
print(f" {entry}")
|
||||
|
||||
|
||||
def cmd_oov(argv: list[str]) -> None:
|
||||
"""Ground a single unknown token and show the constructed versor info."""
|
||||
if not argv:
|
||||
_die("usage: core oov <token>")
|
||||
token = argv[0]
|
||||
def cmd_trace(args: argparse.Namespace) -> int:
|
||||
"""Trace one chat turn and print field telemetry."""
|
||||
text = " ".join(args.text).strip()
|
||||
if not text:
|
||||
_die("trace requires input text. Try: core trace \"word beginning truth\"")
|
||||
|
||||
from chat.runtime import ChatRuntime
|
||||
from algebra.versor import versor_condition
|
||||
rt = ChatRuntime()
|
||||
vocab = rt.session.vocab
|
||||
|
||||
# Try known first
|
||||
runtime = _runtime_for_trace(args.pack)
|
||||
try:
|
||||
v = vocab.get_versor(token)
|
||||
print(f"{token!r} is already in the manifold")
|
||||
import numpy as np
|
||||
print(f" versor_cond: {versor_condition(v):.2e}")
|
||||
return
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Ground it
|
||||
from ingest.gate import inject
|
||||
state = inject([token], vocab)
|
||||
|
||||
import numpy as np
|
||||
print(f"{token!r} — grounded as transient")
|
||||
print(f" versor_cond : {versor_condition(state.F):.2e}")
|
||||
oov_log = getattr(vocab, "unknown_token_log", [])
|
||||
if oov_log:
|
||||
last = oov_log[-1]
|
||||
print(f" root_used : {last.get('root_used', '?')}")
|
||||
print(f" ops_applied : {last.get('operators_applied', [])}")
|
||||
|
||||
|
||||
def cmd_pack(argv: list[str]) -> None:
|
||||
"""Manage language packs."""
|
||||
if not argv:
|
||||
_die("usage: core pack [list | verify <pack_id>]")
|
||||
|
||||
sub = argv[0]
|
||||
|
||||
if sub == "list":
|
||||
from language_packs import list_packs
|
||||
packs = list_packs()
|
||||
if not packs:
|
||||
print("no compiled packs found")
|
||||
for pid in packs:
|
||||
print(pid)
|
||||
|
||||
elif sub == "verify":
|
||||
if len(argv) < 2:
|
||||
_die("usage: core pack verify <pack_id>")
|
||||
pack_id = argv[1]
|
||||
_run(sys.executable, "-m", "language_packs", "verify", pack_id, check=False)
|
||||
response = runtime.chat(text, max_tokens=args.max_tokens)
|
||||
except Exception as exc:
|
||||
_die(f"trace failed: {exc.__class__.__name__}: {exc}", code=1)
|
||||
|
||||
payload = _trace_payload(text, response, runtime)
|
||||
if args.json:
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
else:
|
||||
_die(f"unknown pack subcommand: {sub!r}")
|
||||
_print_trace(payload)
|
||||
return 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
def cmd_oov(args: argparse.Namespace) -> int:
|
||||
"""Ground a single unknown token and show constructed versor info."""
|
||||
try:
|
||||
from algebra.versor import versor_condition
|
||||
from chat.runtime import ChatRuntime
|
||||
except Exception as exc: # pragma: no cover - exercised by CLI in broken envs
|
||||
_print_runtime_import_hint(exc)
|
||||
|
||||
_COMMANDS = {
|
||||
"chat": cmd_chat,
|
||||
"test": cmd_test,
|
||||
"check": cmd_check,
|
||||
"trace": cmd_trace,
|
||||
"oov": cmd_oov,
|
||||
"pack": cmd_pack,
|
||||
}
|
||||
runtime = ChatRuntime(tuple(args.pack) if args.pack else ("en_minimal_v1", "he_logos_micro_v1", "grc_logos_micro_v1"))
|
||||
vocab = runtime.session.vocab
|
||||
|
||||
try:
|
||||
versor = vocab.get_versor(args.token)
|
||||
except KeyError:
|
||||
from ingest.gate import inject
|
||||
|
||||
state = inject([args.token], vocab)
|
||||
print(f"{args.token!r} — grounded as transient")
|
||||
print(f" versor_cond : {versor_condition(state.F):.2e}")
|
||||
oov_log = getattr(vocab, "unknown_token_log", [])
|
||||
if oov_log:
|
||||
last = oov_log[-1]
|
||||
print(f" root_used : {last.get('root_used', '?')}")
|
||||
print(f" ops_applied : {last.get('operators_applied', [])}")
|
||||
else:
|
||||
print(f"{args.token!r} is already in the manifold")
|
||||
print(f" versor_cond: {versor_condition(versor):.2e}")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = sys.argv[1:]
|
||||
if not args or args[0] in {"-h", "--help"}:
|
||||
_usage()
|
||||
sub = args[0]
|
||||
if sub not in _COMMANDS:
|
||||
_die(f"unknown subcommand {sub!r}. Try: {', '.join(_COMMANDS)}")
|
||||
_COMMANDS[sub](args[1:])
|
||||
def cmd_pack_list(args: argparse.Namespace) -> int:
|
||||
"""List compiled language packs."""
|
||||
from language_packs import list_packs
|
||||
|
||||
packs = list_packs()
|
||||
if not packs:
|
||||
print("no compiled packs found")
|
||||
return 0
|
||||
for pack_id in packs:
|
||||
print(pack_id)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_pack_verify(args: argparse.Namespace) -> int:
|
||||
"""Verify one language pack checksum."""
|
||||
return _run(sys.executable, "-m", "language_packs", "verify", args.pack_id)
|
||||
|
||||
|
||||
def cmd_doctor(args: argparse.Namespace) -> int:
|
||||
"""Inspect import/package health for the CLI runtime path."""
|
||||
checks = [
|
||||
("algebra", "algebra"),
|
||||
("alignment", "alignment.graph"),
|
||||
("chat", "chat.runtime"),
|
||||
("language_packs", "language_packs"),
|
||||
("morphology", "morphology.registry"),
|
||||
("sensorium", "sensorium.protocol"),
|
||||
]
|
||||
ok = True
|
||||
print(f"repo_root: {_REPO_ROOT}")
|
||||
for label, module_name in checks:
|
||||
try:
|
||||
__import__(module_name)
|
||||
except Exception as exc:
|
||||
ok = False
|
||||
print(f"FAIL {label:<14} {module_name}: {exc.__class__.__name__}: {exc}")
|
||||
else:
|
||||
print(f"OK {label:<14} {module_name}")
|
||||
|
||||
if args.packs:
|
||||
try:
|
||||
from language_packs import list_packs
|
||||
|
||||
packs = list_packs()
|
||||
except Exception as exc:
|
||||
ok = False
|
||||
print(f"FAIL packs language_packs.list_packs: {exc.__class__.__name__}: {exc}")
|
||||
else:
|
||||
print("packs:")
|
||||
if packs:
|
||||
for pack_id in packs:
|
||||
print(f" {pack_id}")
|
||||
else:
|
||||
print(" none found")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="core",
|
||||
description=DESCRIPTION,
|
||||
epilog=EPILOG,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument("--version", action="store_true", help="print package version and exit")
|
||||
subparsers = parser.add_subparsers(dest="command", metavar="command")
|
||||
|
||||
chat = subparsers.add_parser("chat", help="start the interactive chat REPL")
|
||||
chat.add_argument("args", nargs=argparse.REMAINDER, help="arguments forwarded to python -m chat")
|
||||
chat.set_defaults(func=cmd_chat)
|
||||
|
||||
test = subparsers.add_parser("test", help="run pytest with sane defaults")
|
||||
test.add_argument("args", nargs=argparse.REMAINDER, help="arguments forwarded to pytest")
|
||||
test.set_defaults(func=cmd_test)
|
||||
|
||||
check = subparsers.add_parser("check", help="run ruff check")
|
||||
check.add_argument("paths", nargs="*", help="optional paths to check")
|
||||
check.set_defaults(func=cmd_check)
|
||||
|
||||
trace = subparsers.add_parser(
|
||||
"trace",
|
||||
help="trace one chat turn with field telemetry",
|
||||
description="trace one chat turn with field telemetry",
|
||||
)
|
||||
trace.add_argument("--pack", action="append", help="language pack to mount; repeat for multiple packs")
|
||||
trace.add_argument("--max-tokens", type=int, default=32, help="maximum generated tokens; default: 32")
|
||||
trace.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
||||
trace.add_argument("text", nargs=argparse.REMAINDER, help="input text to trace")
|
||||
trace.set_defaults(func=cmd_trace)
|
||||
|
||||
oov = subparsers.add_parser("oov", help="ground or inspect one token")
|
||||
oov.add_argument("--pack", action="append", help="language pack to mount; repeat for multiple packs")
|
||||
oov.add_argument("token", help="token to inspect or ground")
|
||||
oov.set_defaults(func=cmd_oov)
|
||||
|
||||
pack = subparsers.add_parser("pack", help="inspect and verify language packs")
|
||||
pack_sub = pack.add_subparsers(dest="pack_command", metavar="pack-command", required=True)
|
||||
pack_list = pack_sub.add_parser("list", help="list compiled packs")
|
||||
pack_list.set_defaults(func=cmd_pack_list)
|
||||
pack_verify = pack_sub.add_parser("verify", help="verify a pack checksum")
|
||||
pack_verify.add_argument("pack_id", help="pack id, e.g. en_minimal_v1")
|
||||
pack_verify.set_defaults(func=cmd_pack_verify)
|
||||
|
||||
doctor = subparsers.add_parser("doctor", help="check runtime imports and packaging health")
|
||||
doctor.add_argument("--packs", action="store_true", help="also list discovered language packs")
|
||||
doctor.set_defaults(func=cmd_doctor)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def _print_version() -> None:
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
|
||||
print(version("core-versor"))
|
||||
except Exception:
|
||||
print("core-versor unknown")
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||
if args.version:
|
||||
_print_version()
|
||||
return 0
|
||||
func = getattr(args, "func", None)
|
||||
if func is None:
|
||||
parser.print_help()
|
||||
return 0
|
||||
return int(func(args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
raise SystemExit(main())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ linguistic manifolds: surface forms, morphology, grammar attractors,
|
|||
cross-language resonances, and holonomy resonance proofs.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from .schema import (
|
||||
AlignmentEdge,
|
||||
GrammarAttractor,
|
||||
|
|
@ -16,6 +18,23 @@ from .schema import (
|
|||
MorphologyEntry,
|
||||
OOVPolicy,
|
||||
)
|
||||
|
||||
_DATA_DIR = Path(__file__).parent / "data"
|
||||
|
||||
|
||||
def list_packs() -> list[str]:
|
||||
"""Return available compiled language-pack ids."""
|
||||
if not _DATA_DIR.exists():
|
||||
return []
|
||||
return sorted(
|
||||
path.name
|
||||
for path in _DATA_DIR.iterdir()
|
||||
if path.is_dir()
|
||||
and (path / "manifest.json").exists()
|
||||
and (path / "lexicon.jsonl").exists()
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AlignmentEdge",
|
||||
"GrammarAttractor",
|
||||
|
|
@ -25,6 +44,7 @@ __all__ = [
|
|||
"LexicalEntry",
|
||||
"MorphologyEntry",
|
||||
"OOVPolicy",
|
||||
"list_packs",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,16 @@ core = "core.cli:main"
|
|||
where = ["."]
|
||||
include = [
|
||||
"algebra*",
|
||||
"alignment*",
|
||||
"chat*",
|
||||
"core*",
|
||||
"field*",
|
||||
"generate*",
|
||||
"ingest*",
|
||||
"language_packs*",
|
||||
"morphology*",
|
||||
"persona*",
|
||||
"sensorium*",
|
||||
"session*",
|
||||
"vault*",
|
||||
"vocab*",
|
||||
|
|
|
|||
65
tests/test_cli.py
Normal file
65
tests/test_cli.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.cli import build_parser, main
|
||||
|
||||
|
||||
def test_top_level_help_exits_without_runtime_import(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
build_parser().parse_args(["-h"])
|
||||
assert excinfo.value.code == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "CORE versor engine command suite" in out
|
||||
assert "core trace" in out
|
||||
|
||||
|
||||
def test_trace_help_exits_without_runtime_import(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
build_parser().parse_args(["trace", "-h"])
|
||||
assert excinfo.value.code == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "trace one chat turn" in out
|
||||
assert "--pack" in out
|
||||
assert "--json" in out
|
||||
|
||||
|
||||
def test_main_without_args_prints_help(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main([]) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "CORE versor engine command suite" in out
|
||||
assert "doctor" in out
|
||||
|
||||
|
||||
def test_trace_requires_text_before_runtime_initialization(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
main(["trace"])
|
||||
assert excinfo.value.code == 2
|
||||
err = capsys.readouterr().err
|
||||
assert "trace requires input text" in err
|
||||
|
||||
|
||||
def test_doctor_imports_runtime_support_modules(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["doctor"]) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "OK alignment" in out
|
||||
assert "OK morphology" in out
|
||||
assert "OK sensorium" in out
|
||||
|
||||
|
||||
def test_trace_formats_real_runtime_payload(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["trace", "--pack", "en_minimal_v1", "word", "beginning", "truth"]) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "input : word beginning truth" in out
|
||||
assert "proposition" in out
|
||||
assert "subject" in out
|
||||
assert "predicate" in out
|
||||
|
||||
|
||||
def test_trace_json_formats_real_runtime_payload(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["trace", "--pack", "en_minimal_v1", "--json", "word", "beginning", "truth"]) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert '"input": "word beginning truth"' in out
|
||||
assert '"proposition"' in out
|
||||
assert '"subject"' in out
|
||||
assert '"predicate"' in out
|
||||
Loading…
Reference in a new issue