context loading, path resolution, form state, dynamic rules, and provider-neutral assessment runner/cache boundary

This commit is contained in:
2026-05-04 13:52:29 +02:00
parent eccf1874fb
commit 8361f9ea45
29 changed files with 2809 additions and 65 deletions

View File

@@ -65,6 +65,7 @@ from markitect_tool.reference import (
load_namespaces,
resolve_reference,
)
from markitect_tool.runtime import evaluate_form_state, load_runtime_context_file
from markitect_tool.schema import load_schema_file, validate_markdown_file, validate_schema
from markitect_tool.template import (
MissingTemplateVariable,
@@ -1466,17 +1467,68 @@ def contract_validate(contract_file: Path, output_format: str) -> None:
default="text",
show_default=True,
)
def contract_check(file: Path, contract_file: Path, output_format: str) -> None:
@click.option(
"--context",
"context_file",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help="YAML or JSON runtime context used for field prefill and dynamic rules.",
)
def contract_check(
file: Path,
contract_file: Path,
output_format: str,
context_file: Path | None,
) -> None:
"""Check a Markdown file against a Markdown document contract."""
try:
result = check_markdown_file(file, contract_file)
result = check_markdown_file(file, contract_file, context_path=context_file)
except ContractLoaderError as exc:
raise click.ClickException(str(exc)) from exc
_emit_diagnostic_result(result.to_dict(), output_format)
raise click.exceptions.Exit(0 if result.valid else 1)
@contract.command("form-state")
@click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--contract",
"contract_file",
required=True,
type=click.Path(exists=True, dir_okay=False, path_type=Path),
)
@click.option(
"--context",
"context_file",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help="YAML or JSON runtime context used for field prefill and dynamic rules.",
)
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def contract_form_state(
file: Path,
contract_file: Path,
context_file: Path | None,
output_format: str,
) -> None:
"""Evaluate UI-neutral form state for a document contract."""
try:
document = parse_markdown_file(file)
contract_definition = load_contract_file(contract_file)
context = load_runtime_context_file(context_file) if context_file else None
form_state = evaluate_form_state(document, contract_definition, context)
except ContractLoaderError as exc:
raise click.ClickException(str(exc)) from exc
_emit_form_state(form_state.to_dict(), output_format)
raise click.exceptions.Exit(0 if form_state.valid else 1)
def _emit_result(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
@@ -1511,6 +1563,31 @@ def _emit_diagnostic_result(data: dict, output_format: str) -> None:
click.echo(f" guidance: {diagnostic['guidance']}")
def _emit_form_state(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
elif output_format == "yaml":
click.echo(yaml.safe_dump(data, sort_keys=False))
else:
click.echo("valid" if data.get("valid") else "invalid")
for field in data.get("fields", []):
value = field.get("value", "<missing>") if field.get("exists") else "<missing>"
flags = []
if field.get("required"):
flags.append("required")
if field.get("visible") is False:
flags.append("hidden")
if field.get("enabled") is False:
flags.append("disabled")
suffix = f" ({', '.join(flags)})" if flags else ""
click.echo(f"- {field['id']}: {value} [{field.get('origin', 'unknown')}]{suffix}")
for diagnostic in data.get("diagnostics", []):
click.echo(
f" [{diagnostic['severity']}] {diagnostic['code']}: "
f"{diagnostic['message']}"
)
def _emit_metrics(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))