diff --git a/harness/tests/test_session_documents.py b/harness/tests/test_session_documents.py new file mode 100644 index 00000000..c132b217 --- /dev/null +++ b/harness/tests/test_session_documents.py @@ -0,0 +1,57 @@ +"""Tests for `tht session documents --json` and build_documents.""" +import json + +from typer.testing import CliRunner + +from tht.config import DatabaseConfig +from tht.session.store import build_documents, create_session +from tht.cli.session_cmd import session_app + + +def _db(): + return DatabaseConfig( + database="testdb", user="u", password="p", # noqa: S106 + **{"schema": "public"}, + ) + + +def test_build_documents_always_has_original_question(tmp_path): + m = create_session("quante ablazioni nel 2024", _db(), tmp_path) + docs = build_documents(m, tmp_path / m.id) + keys = [d["key"] for d in docs] + assert keys[0] == "question" + assert docs[0]["format"] == "text" + assert docs[0]["content"] == "quante ablazioni nel 2024" + # question.md is written by create_session -> revised_question present + assert "revised_question" in keys + + +def test_build_documents_includes_existing_artifacts_only(tmp_path): + m = create_session("q", _db(), tmp_path) + sdir = tmp_path / m.id + (sdir / "sql_final.sql").write_text("SELECT 1") + (sdir / "schema_linking.json").write_text('{"question":"q","candidates":[]}') + docs = {d["key"]: d for d in build_documents(m, sdir)} + assert docs["sql"]["format"] == "sql" + assert docs["sql"]["content"] == "SELECT 1" + assert docs["schema_linking"]["format"] == "schema-linking" + assert "validation_report" not in docs # not written + + +def test_cli_documents_json(tmp_path, monkeypatch): + m = create_session("q", _db(), tmp_path) + from tht.cli import session_cmd + + class FakePaths: + sessions = tmp_path + + class FakeCfg: + paths = FakePaths() + database = _db() + + monkeypatch.setattr(session_cmd, "_load_config_or_exit", lambda _: FakeCfg()) + res = CliRunner().invoke(session_app, ["documents", m.id, "--json"]) + assert res.exit_code == 0, res.output + docs = json.loads(res.output) + assert isinstance(docs, list) + assert docs[0]["key"] == "question" diff --git a/harness/tht/cli/session_cmd.py b/harness/tht/cli/session_cmd.py index 067a8b75..d2defdc5 100644 --- a/harness/tht/cli/session_cmd.py +++ b/harness/tht/cli/session_cmd.py @@ -218,6 +218,25 @@ def delete_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT) typer.secho(f"OK: sessione {session_id} eliminata.", fg=typer.colors.GREEN) +@session_app.command("documents") +def documents_cmd( + session_id: str = typer.Argument(...), + json_out: bool = typer.Option(False, "--json", help="Emetti JSON su stdout (pristine)."), + config: Path = CONFIG_OPT, +) -> None: + """Documenti di sola lettura della sessione (domanda, rivista, schema, SQL, report, decisioni).""" + from tht.session.store import build_documents + + cfg = _load_config_or_exit(config) + manifest = load_session_or_exit(cfg, session_id) + docs = build_documents(manifest, session_dir(cfg, session_id)) + if json_out: + typer.echo(json.dumps(docs, ensure_ascii=False, indent=2)) + return + for d in docs: + typer.echo(f"[{d['phase']}] {d['title']} ({d['format']})") + + def session_problems(cfg, session_id: str) -> list[str]: """Problemi del Blocco 3 (decisioni + schema_linking). Riusato da check e finalize.""" import json diff --git a/harness/tht/session/store.py b/harness/tht/session/store.py index 4cbb635c..cd36c57f 100644 --- a/harness/tht/session/store.py +++ b/harness/tht/session/store.py @@ -164,3 +164,27 @@ def delete_session(session_id: str, sessions_root: Path) -> None: """Hard-delete the session directory. SessionError if it does not exist.""" load_session(session_id, sessions_root) # raises SessionError if absent shutil.rmtree(sessions_root / session_id) + + +def build_documents(manifest: SessionManifest, session_dir: Path) -> list[dict]: + """Ordered, read-only document bundle for the UI panel. Only documents that exist + on disk are returned. CTE artifacts (F6) are intentionally excluded (intermediate).""" + docs: list[dict] = [{ + "phase": "—", "key": "question", "title": "Domanda originale", + "format": "text", "content": manifest.question, + }] + spec = [ + ("question.md", "F3", "revised_question", "Domanda rivista", "markdown"), + ("schema_linking.json", "F4", "schema_linking", "Schema linking", "schema-linking"), + ("sql_final.sql", "F7", "sql", "SQL finale", "sql"), + ("validation_report.md", "finalize", "validation_report", "Report di validazione", "markdown"), + ("review_decisions.jsonl", "—", "decisions", "Decisioni", "decisions"), + ] + for filename, phase, key, title, fmt in spec: + path = session_dir / filename + if path.exists(): + docs.append({ + "phase": phase, "key": key, "title": title, + "format": fmt, "content": path.read_text(), + }) + return docs