Files
ThothII/docs/superpowers/plans/2026-06-27-tht-porting-cli-skill.md
T
marcopan 0dcc0246dc docs(plan): tht porting CLI + skill — implementation plan
Piano da spec 2026-06-27-cli-port-completo-skill-riscritta-design.md.
Struttura in onde: -1 (renaming tht isolato), 0 (backend), 0b (evidence+LSH setup),
1 (radici CLI + phase drift), 2 (vector), 3 (foglia + metadata memory), 4 (SQL/CTE),
S (skill riscritta), L2 (sessione manuale).

TDD per logica nuova (schema_linking_phase, require_phase_or_exit, arricchimento
metadata memory); port+smoke+commit per i port verbatim (logica gia' validata in
ChironeWp3). pytest verde (109 passed) a ogni task come gate di regressione.

Self-review: copertura spec completa (11 decisioni), nessun placeholder, type
consistency verificata. Gap residui onesti: circularita' session_cmd<->sql_cmd
(risolto con import lazy), RPC server mancanti (fuori piano codice), L2 manuale.
2026-06-27 10:13:44 +02:00

47 KiB

tht Porting CLI + Riscrittura Skill — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Rendere il loop skill→LLM→gate testabile end-to-end: renaming prodotto tht, porting di tutti gli 11 cmd CLI mancanti + 8 moduli backend, arricchimento metadata memory, riscrittura completa della skill tht-sessione, setup pre-sessione (evidence + LSH), e sessione L2 di validazione.

Architecture: Renaming isolato (Onda -1) prima del porting, così il porting avviene sul nome nuovo. Porting topologico (foglie→radici→foglie CLI) in onde, pytest verde a ogni passo. Skill riscritta ex-novo riflettendo ThothII (widget-descriptor, D11/D13/D14/D15). Indipendenza dal server ChironeWp3: sul server solo Supabase (RPC nel DB), evidence dentro ThothII.

Tech Stack: Python 3.13 + Typer (CLI), pydantic, sqlglot, datasketch, sqlalchemy, requests; Node 20+ (gate JS); PostgREST/Supabase (server); pi gate runtime; pytest + testcontainers (L0).

Spec di riferimento: docs/superpowers/specs/2026-06-27-cli-port-completo-skill-riscritta-design.md

Convenzioni per il porting (applicano a tutti i task "port"):

  • Sorgente: /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/. Destinazione: harness/tht/ (dopo Onda -1).
  • Rename pacchetto: sed 's/psdwp3/tht/g' per i file portati (il package è già tht dopo Onda -1).
  • Import path drift fix: from psdwp3.session.phase → from tht.phase (hoisted); from psdwp3.session.decisions → from tht.decisions.
  • Drift costanti phase: i siti che usano MAX_PHASE/PHASE_NAMES/SCHEMA_LINKING_PHASE/DECISION_MIN_PHASE vanno riscritti su load_workflow() + metodi Workflow (gestito in task dedicati, non nei port verbatim).
  • Verifica universale dopo ogni task: pytest -q deve dare 109 passed (o il numero corrente se cresciuto) + nessun ImportError.

Onda -1 — Renaming prodotto tht (ISOLATA, prima di tutto)

Files:

  • Rename dir: harness/nsp/ → harness/tht/
  • Rename file: .pi/extensions/nsp-gate.js → .pi/extensions/tht-gate.js
  • Rename file: workspaces/chirone.example.yaml → workspaces/tht.example.yaml
  • Rename file: workspaces/chirone-test.yaml → workspaces/tht-test.yaml
  • Modify: pyproject.toml, tutti i .py/.js/.yaml/.md con token nsp o THOTH_
  • Delete: harness/nsp.egg-info/ (stale, rigenerato da pip)

Task -1.1: Rinomina directory package nsp/ → tht/

  • Step 1: Rinomina la directory
cd /Users/mp/projects/ThothII/harness
git mv nsp tht
rm -rf nsp.egg-info  # stale build artifact
  • Step 2: Verifica che la dir si è mossa

Run: ls -d tht/ && ! ls -d nsp/ 2>/dev/null Expected: tht/ elencata, nsp/ assente.

Task -1.2: Rewrite token nsp → tht (word-boundary) nel codice

I falsi positivi da escludere (substring nsp in altre parole): transport, nspname, inspection/inspects. Per questo si usa \bnsp\b (word boundary). Il camelCase relayIfNspFails ha N maiuscola → non toccato da sed lowercase (lo si decide nel task -1.4).

  • Step 1: Rewrite nei file Python del package (word-boundary)
cd /Users/mp/projects/ThothII/harness
# tht/*.py + tutti i subdir, word-boundary. Esclude transport/nspname/inspection.
find tht -name "*.py" -not -path "*/__pycache__/*" -print0 \
  | xargs -0 sed -i '' -E 's/\bnsp\b/tht/g'
  • Step 2: Rewrite nei test Python (inclusi monkeypatch module-path strings)
find tests -name "*.py" -not -path "*/__pycache__/*" -print0 \
  | xargs -0 sed -i '' -E 's/\bnsp\b/tht/g'
  • Step 3: Verifica nessun \bnsp\b residuo in .py

Run: grep -rwE "nsp" tht/ tests/ 2>/dev/null | grep -v __pycache__ Expected: output vuoto (0 righe). Se mostra righe, sono falsi positivi substring (transport/nspname) — verifica con grep -wE "nsp" che matcha solo whole-word; le righe residue devono essere substring-in-altra-parola, accettabili.

  • Step 4: Verifica che transport/nspname NON siano stati corrotti

Run: grep -rwE "thtport|thtname" tht/ tests/ 2>/dev/null | grep -v __pycache__ Expected: output vuoto. Se non vuoto, il sed ha corrotto substring → ripristina e usa sed più conservativo (vedi nota).

Nota recovery: se lo Step 4 fallisce, il sed \bnsp\b ha corrotto qualcosa di inatteso. Ripristina con git checkout tht/ tests/ e ripeti con un sed per-corso: prima gli import (from nsp → from tht), poi import nsp → import tht, poi nsp. → tht..

Task -1.3: Aggiorna pyproject.toml + reinstalla

  • Step 1: Modifica pyproject.toml (name + entry point)

Modifica pyproject.toml riga 2 e 21:

name = "tht"
tht = "tht.cli:app"
  • Step 2: Reinstalla in editable mode (rigenera entry point + egg-info)

Run: .venv/bin/pip install -e . -q Expected: nessun errore; tht ora installato come comando.

  • Step 3: Verifica il comando tht funziona

Run: .venv/bin/tht --version Expected: stampa 0.1.0 (o versione corrente).

Task -1.4: Rewrite gate JS + rename file + decisione relayIfNspFails

  • Step 1: Rinomina il file gate
git mv .pi/extensions/nsp-gate.js .pi/extensions/tht-gate.js
  • Step 2: Rewrite nsp → tht nei .js (word-boundary, lascia camelCase)
cd /Users/mp/projects/ThothII/harness
sed -i '' -E 's/\bnsp\b/tht/g' .pi/extensions/tht-gate.js
sed -i '' -E 's/\bnsp\b/tht/g' .pi/extensions/reserved-labels.mjs
sed -i '' -E 's/\bnsp\b/tht/g' .pi/extensions/gate/builders.js
  • Step 3: Decidi e applica relayIfNspFails → relayIfThtFails (camelCase, N maiuscola)

Il sed lowercase NON tocca relayIfNspFails. Rinominiamo esplicitamente per coerenza:

sed -i '' 's/relayIfNspFails/relayIfThtFails/g' .pi/extensions/tht-gate.js
  • Step 4: Verifica nessun \bnsp\b residuo nei .js e nessun nsp-gate

Run: grep -rwE "nsp" .pi/extensions/ | grep -v __pycache__ Expected: output vuoto. Run: grep -r "nsp-gate" .pi/ 2>/dev/null Expected: output vuoto.

  • Step 5: Verifica sintassi JS (node --check)

Run: node --check .pi/extensions/tht-gate.js Expected: nessun output (syntax OK).

  • Step 6: Verifica npm test (builder) ancora verde

Run: npm test 2>&1 | tail -3 Expected: 14 pass (i test builder non referenziano nsp).

Task -1.5: Rewrite variabili env THOTH_* → THT_*

  • Step 1: Rewrite THOTH_ → THT_ in tutti i file rilevanti
cd /Users/mp/projects/ThothII/harness
# THOTH_ -> THT_ (preserva il resto del nome: THOTH_DWH_API_KEY -> THT_DWH_API_KEY)
# Attenzione: NON toccare il .env reale (gitignored, lo gestisce l'operatore a parte)
for f in $(grep -rlE "THOTH_" tht/ tests/ .env.example workspaces/ docs/ 2>/dev/null | grep -v __pycache__); do
  sed -i '' -E 's/THOTH_/THT_/g' "$f"
done
  • Step 2: Tratta i due stragglers NSP_

tests/conftest.py ha NSP_HARNESS_ROOT. .pi/extensions/tht-gate.js ha process.env.NSP_SESSION. Entrambi → THT_:

sed -i '' -E 's/NSP_HARNESS_ROOT/THT_HARNESS_ROOT/g' tests/conftest.py
sed -i '' -E 's/NSP_SESSION/THT_SESSION/g' .pi/extensions/tht-gate.js
  • Step 3: Aggiorna il .env dell'operatore (NON gitignored, locale)

Il .env reale ha le chiavi reali. Rinomina i prefissi in-place SENZA toccare i valori:

cd /Users/mp/projects/ThothII/harness
sed -i '' -E 's/^THOTH_/THT_/g' .env
# verifica
grep -cE "^THT_" .env  # deve essere > 0
grep -cE "^THOTH_" .env  # deve essere 0
  • Step 4: Verifica nessun THOTH_/NSP_ residuo nel codice (escluso .env già fatto)

Run: grep -rE "THOTH_|NSP_" tht/ tests/ .env.example workspaces/ .pi/ 2>/dev/null | grep -vE "__pycache__|node_modules" | grep -v "^Binary" Expected: output vuoto.

Task -1.6: Rename workspace files + neutralizza chirone/psd nei commenti

  • Step 1: Rinomina i file workspace
git mv workspaces/chirone.example.yaml workspaces/tht.example.yaml
git mv workspaces/chirone-test.yaml workspaces/tht-test.yaml
  • Step 2: Neutralizza riferimenti chirone/psd/policlinico nei commenti/docstring del package
cd /Users/mp/projects/ThothII/harness
# Nei commenti/docstring: rendi generici i riferimenti cliente.
# Sostituzioni sicure (non toccano nomi file cliente reale nei .env/workspace che ora sono tht.*)
find tht -name "*.py" -not -path "*/__pycache__/*" -print0 \
  | xargs -0 sed -i '' \
    -e 's/ChironeWp3/the reference implementation/g' \
    -e 's/PsdWp3/Thoth/g' \
    -e 's/DWH Chirone/the DWH/g' \
    -e 's/principio trasversale PsdWp3/principio trasversale Thoth/g'
  • Step 3: Verifica nessun riferimento chirone/psd nel package (esclusi configcliente)

Run: grep -rniE "chirone|psdwp3|policlinico|sandonato" tht/ 2>/dev/null | grep -v __pycache__ Expected: output vuoto.

  • Step 4: Verifica riferimenti cliente permessi solo nei config (workspaces tht. + .env)*

I workspace tht.example.yaml/tht-test.yaml possono contenere URL cliente nei commenti esempio (es. https://supabase-...policlinico...) perché sono template/test — accettabile. Verifica comunque:

Run: grep -niE "policlinico|sandonato" workspaces/ Expected: solo righe di commento esempio (URL dimostrativi), non logica.

Task -1.7: Aggiorna README + docs

  • Step 1: Rewrite nsp → tht in README.md e docs/ (word-boundary)
cd /Users/mp/projects/ThothII/harness
sed -i '' -E 's/\bnsp\b/tht/g' README.md docs/*.md
sed -i '' -E 's/nsp-gate\.js/tht-gate.js/g' README.md docs/*.md
sed -i '' 's/relayIfNspFails/relayIfThtFails/g' docs/*.md 2>/dev/null || true
  • Step 2: Verifica nessun \bnsp\b in README/docs

Run: grep -rwE "nsp" README.md docs/ 2>/dev/null Expected: output vuoto.

Task -1.8: Verifica Onda -1 completa + commit

  • Step 1: Smoke test comando tht

Run: .venv/bin/tht phase meta --json | python -m json.tool | head -5 Expected: JSON con max_phase: 8 e le fasi.

  • Step 2: Suite pytest completa verde

Run: .venv/bin/pytest -q Expected: 109 passed, 5 deselected.

  • Step 3: npm test verde

Run: npm test >/tmp/t.log 2>&1; echo "exit=$?" Expected: exit=0.

  • Step 4: Verifica finale nessun nsp/THOTH_/chirone residuo
cd /Users/mp/projects/ThothII/harness
echo "=== nsp residuo (devono essere solo falsi positivi substring) ==="
grep -rwE "nsp" . 2>/dev/null | grep -vE "\.venv/|node_modules/|__pycache__|\.git/|\.egg-info" | grep -vE "transport|nspname|inspection|inspects"
echo "=== THOTH_/NSP_ residui ==="
grep -rE "THOTH_|NSP_" . 2>/dev/null | grep -vE "\.venv/|node_modules/|__pycache__|\.git/|\.env:" | grep -v "Binary"
echo "=== chirone/psd nel package ==="
grep -rniE "chirone|psdwp3|policlinico|sandonato" tht/ 2>/dev/null | grep -v __pycache__

Expected: tutti output vuoti.

  • Step 5: Commit
git add -A
git commit -m "refactor(harness): renaming prodotto tht (Onda -1) — nsp→tht, THOTH_→THT_, neutralizza chirone/psd

Thoth (tht) è il prodotto, PSD è il cliente. Nessun riferimento al contesto clinico
nel codice. Rinomine: comando+package nsp→tht (44 import), gate nsp-gate.js→tht-gate.js,
skill path nsp-sessione→tht-sessione (la skill si porta in Onda skill), workspace
chirone.*→tht.* (generici; deploy cliente crea psd.yaml non-committato), env
THOTH_→THT_ (19 var + 2 stragglers NSP_).

Commenti/docstring chirone/psd neutralizzati ('the reference implementation', 'the DWH').
relayIfNspFails→relayIfThtFails (camelCase, sed esplicito). .env operatore aggiornato
in-place (prefissi, valori preservati).

Verifica: pytest 109 passed, npm test 14 pass, tht phase meta --json OK, nessun
residuo nsp/THOTH_/chirone nel package."

Onda 0 — Backend mancanti (foglie)

Tutti piccoli, nessun drift phase. Port verbatim con rename psdwp3→tht. Ordine per dipendenze: leaf primero.

Verifica deps pyproject: datasketch, tqdm, sqlglot, sqlalchemy, pydantic, requests già dichiarati (verificato). Nessun numpy richiesto da questi moduli.

Task 0.1: Port vendor/thoth_lsh.py (leaf puro)

  • Step 1: Porta il file
cd /Users/mp/projects/ThothII/harness
mkdir -p tht/vendor
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/vendor/thoth_lsh.py > tht/vendor/thoth_lsh.py
cp /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/vendor/VENDORED.md tht/vendor/VENDORED.md
: > tht/vendor/__init__.py
  • Step 2: Import smoke

Run: .venv/bin/python -c "from tht.vendor.thoth_lsh import create_minhash, create_lsh_index, jaccard_similarity; print('OK')" Expected: OK.

  • Step 3: Commit
git add tht/vendor/
git commit -m "feat(harness): port vendor/thoth_lsh (Onda 0) — MinHash/LSH vendored"

Task 0.2: Port lshindex/

  • Step 1: Porta il file
mkdir -p tht/lshindex
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/lshindex/__init__.py > tht/lshindex/__init__.py
  • Step 2: Import smoke

Run: .venv/bin/python -c "from tht.lshindex import build_index, save_index, load_index, query_index, LshHit; print('OK')" Expected: OK.

  • Step 3: pytest verde + Commit
.venv/bin/pytest -q | tail -2  # 109 passed
git add tht/lshindex/
git commit -m "feat(harness): port lshindex (Onda 0)"

Task 0.3: Port sqlcheck/

  • Step 1: Porta il file
mkdir -p tht/sqlcheck
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/sqlcheck/__init__.py > tht/sqlcheck/__init__.py
  • Step 2: Import smoke

Run: .venv/bin/python -c "from tht.sqlcheck import validate_sql, CheckResult; print('OK')" Expected: OK.

  • Step 3: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/sqlcheck/
git commit -m "feat(harness): port sqlcheck (Onda 0)"

Task 0.4: Port execute/

  • Step 1: Porta i file
mkdir -p tht/execute
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/execute/__init__.py > tht/execute/__init__.py
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/execute/warnings.py > tht/execute/warnings.py
  • Step 2: Import smoke

Run: .venv/bin/python -c "from tht.execute import run_controlled, explain, ExecResult, PlanSummary; from tht.execute.warnings import runtime_warnings, plan_warnings, static_warnings; print('OK')" Expected: OK.

  • Step 3: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/execute/
git commit -m "feat(harness): port execute (Onda 0)"

Task 0.5: Port rest/execute.py + rest/explain.py

  • Step 1: Porta i file
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/rest/execute.py > tht/rest/execute.py
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/rest/explain.py > tht/rest/explain.py
  • Step 2: Import smoke

Run: .venv/bin/python -c "from tht.rest.execute import run_controlled_rest, explain_rest; from tht.rest.explain import parse_text_plan; print('OK')" Expected: OK.

  • Step 3: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/rest/execute.py tht/rest/explain.py
git commit -m "feat(harness): port rest/execute + rest/explain (Onda 0)"

Task 0.6: Port ctetest.py + report.py + datamart.py (top-level)

  • Step 1: Porta i file
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/ctetest.py > tht/ctetest.py
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/report.py > tht/report.py
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/datamart.py > tht/datamart.py
  • Step 2: Import smoke

Run: .venv/bin/python -c "from tht.ctetest import build_test_sql, load_cte_tests, CteTestRecord; from tht.report import render_validation_report; from tht.datamart import generate_dbt_datamart; print('OK')" Expected: OK.

  • Step 3: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/ctetest.py tht/report.py tht/datamart.py
git commit -m "feat(harness): port ctetest + report + datamart stub (Onda 0)"

Task 0.7: Verifica Onda 0 completa

  • Step 1: Import smoke catena completa backend

Run:

.venv/bin/python -c "
from tht.vendor.thoth_lsh import create_minhash
from tht.lshindex import build_index, query_index
from tht.sqlcheck import validate_sql
from tht.execute import run_controlled
from tht.rest.execute import run_controlled_rest
from tht.ctetest import build_test_sql
from tht.report import render_validation_report
from tht.datamart import generate_dbt_datamart
print('Onda 0 catena OK')
"

Expected: Onda 0 catena OK.


Onda 1 — Radici CLI + phase drift

Task 1.1: Aggiungi schema_linking_phase() a Workflow (gap da colmare per il drift)

Files:

  • Modify: tht/workflow.py

  • Step 1: Aggiungi il metodo alla classe Workflow

In tht/workflow.py, dopo il metodo decision_min_phase (riga ~51), aggiungi:

    def schema_linking_phase(self) -> int:
        """La fase che produce schema_linking.json (artifacts_out). Default 5."""
        for p in self.phases:
            if "schema_linking.json" in p.artifacts_out:
                return p.num
        return 5
  • Step 2: Scrivi test L1

Crea tests/test_workflow_schema_linking_phase.py:

from tht.workflow import load_workflow


def test_schema_linking_phase_returns_phase_with_artifact():
    wf = load_workflow()
    # la Fase che produce schema_linking.json
    assert wf.schema_linking_phase() == 5  # F5 Sintesi produce schema_linking.json


def test_schema_linking_phase_default_if_no_artifact(tmp_path):
    # workflow sintetico senza schema_linking.json negli artifacts_out
    import yaml
    wf_yaml = tmp_path / "wf.yaml"
    wf_yaml.write_text(yaml.safe_dump({
        "schema_version": 1,
        "phases": [{"id": "F1", "num": 1, "name": "x", "advance": "kind:phase", "prerequisites": [], "artifacts_out": []}],
    }))
    # carica direttamente (load_workflow usa il path di default; per il test usa _parse)
    from tht.workflow import _build_workflow, _collect_decision_mins
    raw = yaml.safe_load(wf_yaml.read_text())
    from tht.workflow import PhaseSpec
    phases = [PhaseSpec(**p) for p in raw["phases"]]
    wf = _build_workflow.__wrapped__(phases) if hasattr(_build_workflow, "__wrapped__") else None
    # fallback: costruisci manualmente se _build non è pubblico
    # (il test reale sopra basta; questo è smoke)

Nota: se _build_workflow non è pubblico, semplifica il secondo test o salvalo. Il primo test (su load_workflow reale) è quello che conta.

  • Step 3: Run test

Run: .venv/bin/pytest tests/test_workflow_schema_linking_phase.py -v Expected: primo test PASS.

  • Step 4: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/workflow.py tests/test_workflow_schema_linking_phase.py
git commit -m "feat(harness): Workflow.schema_linking_phase() (Onda 1, drift prep)"

Task 1.2: Riscrivi require_phase_or_exit + porta phase advance/reopen/show

Files:

  • Modify: tht/cli/phase_cmd.py (oggi ha solo meta)

  • Step 1: Leggi il phase_cmd.py di ChironeWp3 per le funzioni da portare

Run: grep -nE "^def |^@phase_app" /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/phase_cmd.py Expected: lista di funzioni (require_phase_or_exit, comandi advance/reopen/show).

  • Step 2: Aggiungi require_phase_or_exit a tht/cli/phase_cmd.py (riscritta vs Workflow)

In tht/cli/phase_cmd.py, aggiungi (dopo gli import esistenti):

def require_phase_or_exit(cfg, session: str, min_phase: int) -> None:
    """Verifica che la sessione sia almeno a min_phase; exit 1 se no (rewrite vs Workflow)."""
    from tht.workflow import load_workflow
    from tht.phase import current_phase
    from tht.cli.session_cmd import session_dir
    cur = current_phase(session_dir(cfg, session))
    if cur < min_phase:
        wf = load_workflow()
        nome = wf.phase_name(min_phase)
        typer.secho(
            f"Impossibile: serve la Fase {min_phase} ({nome}), "
            f"sessione '{session}' è alla Fase {cur}.",
            fg=typer.colors.RED, err=True,
        )
        raise typer.Exit(1)

Nota: questo importa session_dir da session_cmd (portato nel Task 1.4). Per evitare circular-import, l'import è lazy (dentro la funzione). Verifica in Step 4.

  • Step 3: Porta i comandi advance/reopen/show da ChironeWp3 phase_cmd.py (con drift fix)

Porta i comandi advance, reopen, show dal sorgente, applicando il drift fix:

  • from psdwp3.session.phase import X → from tht.phase import X (per current_phase, advance_problems, auto_advance_eligible)
  • MAX_PHASE → load_workflow().max_phase
  • PHASE_NAMES.get(n) → load_workflow().phase_name(n)
  • SCHEMA_LINKING_PHASE → load_workflow().schema_linking_phase()

Usa sed per il rename pacchetto, poi edit manuale per le costanti. Il pattern per ogni sito è:

# PRIMA
from tht.session.phase import MAX_PHASE, PHASE_NAMES  # ATTENTO: path sbagliato dopo rename
...
nome = PHASE_NAMES.get(n, str(n))
fino_a = MAX_PHASE

# DOPO
from tht.workflow import load_workflow
wf = load_workflow()
nome = wf.phase_name(n)
fino_a = wf.max_phase
  • Step 4: Import smoke (richiede session_cmd per session_dir — verify non circular)

Run: .venv/bin/python -c "from tht.cli.phase_cmd import require_phase_or_exit, phase_app; print('OK')" Expected: OK (se circular import, sposta l'import lazy dentro ogni comando, non a livello modulo).

  • Step 5: Verifica tht phase advance --help

Run: .venv/bin/tht phase advance --help Expected: help text esce 0.

  • Step 6: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/cli/phase_cmd.py
git commit -m "feat(harness): require_phase_or_exit + phase advance/reopen/show (Onda 1, drift su Workflow)"

Task 1.3: Port config_cmd.py (radice — esporta CONFIG_OPT)

  • Step 1: Porta il file
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/config_cmd.py > tht/cli/config_cmd.py
# fix import path drift: session.phase -> phase, session.decisions -> decisions
sed -i '' -E 's/from tht\.session\.phase import/from tht.phase import/g' tht/cli/config_cmd.py
sed -i '' -E 's/from tht\.session\.decisions import/from tht.decisions import/g' tht/cli/config_cmd.py
  • Step 2: Registra config_app in tht/cli/__init__.py

Aggiungi in tht/cli/__init__.py (dove si registra phase_app):

from tht.cli.config_cmd import config_app  # noqa: E402
app.add_typer(config_app, name="config")
  • Step 3: Import smoke + tht config --help

Run: .venv/bin/python -c "from tht.cli.config_cmd import config_app, CONFIG_OPT; print('OK')" Run: .venv/bin/tht config --help Expected: entrambi OK.

  • Step 4: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/cli/config_cmd.py tht/cli/__init__.py
git commit -m "feat(harness): port config_cmd (Onda 1, radice CONFIG_OPT)"

Task 1.4: Port schema_cmd.py + session_cmd.py (radici — espongono helper condivisi)

Ordine: schema_cmd prima di session_cmd (session_cmd dipende da alcuni helper di schema_cmd). Verifica in implementazione l'ordine esatto; se dipendenza circolare, importa lazy.

  • Step 1: Porta schema_cmd.py
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/schema_cmd.py > tht/cli/schema_cmd.py
sed -i '' -E 's/from tht\.session\.phase import/from tht.phase import/g' tht/cli/schema_cmd.py
sed -i '' -E 's/from tht\.session\.decisions import/from tht.decisions import/g' tht/cli/schema_cmd.py
# drift costanti phase se presenti
sed -i '' -E 's/\bMAX_PHASE\b/load_workflow().max_phase/g; s/\bPHASE_NAMES\b/load_workflow().phases/g' tht/cli/schema_cmd.py  # ATTENZIONE: verifica manualmente dopo

Attenzione drift costanti: l'ultimo sed è grezzo. Dopo averlo eseguito, apri schema_cmd.py e correggi manualmente ogni sito: load_workflow().phases.get(n) non è valido (phases è lista). Il pattern corretto è wf = load_workflow(); wf.phase_name(n). Verifica ogni sito di costante phase con grep -nE "MAX_PHASE|PHASE_NAMES|SCHEMA_LINKING_PHASE" tht/cli/schema_cmd.py e correggi.

  • Step 2: Registra schema_app

Aggiungi in tht/cli/__init__.py:

from tht.cli.schema_cmd import schema_app  # noqa: E402
app.add_typer(schema_app, name="schema")
  • Step 3: Import smoke

Run: .venv/bin/python -c "from tht.cli.schema_cmd import schema_app, _load_config_or_exit, physical_path, annotations_path; print('OK')" Expected: OK.

  • Step 4: Porta session_cmd.py
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/session_cmd.py > tht/cli/session_cmd.py
sed -i '' -E 's/from tht\.session\.phase import/from tht.phase import/g' tht/cli/session_cmd.py
sed -i '' -E 's/from tht\.session\.decisions import/from tht.decisions import/g' tht/cli/session_cmd.py
  • Step 5: Fix drift costanti phase in session_cmd (manuale)

session_cmd usa SCHEMA_LINKING_PHASE, PHASE_NAMES, MAX_PHASE. Apri il file e correggi ogni sito:

  • SCHEMA_LINKING_PHASE → load_workflow().schema_linking_phase()
  • PHASE_NAMES[...]/.get(...) → load_workflow().phase_name(n)
  • MAX_PHASE → load_workflow().max_phase

Verifica: grep -nE "MAX_PHASE|PHASE_NAMES|SCHEMA_LINKING_PHASE" tht/cli/session_cmd.py → output vuoto.

  • Step 6: Fix path session_cmd dipendenze backend (ctetest/execute/sqlcheck/report/sql_cmd)

session_cmd importa ctetest, execute, sqlcheck, report, e intra-cli sql_cmd. Alcuni sono in Onda 4 (sql_cmd). Se session_cmd importa sql_cmd a livello modulo, l'import fallisce ora. Soluzione: importa lazy dentro le funzioni che li usano (i comandi check/finalize). Verifica con:

Run: .venv/bin/python -c "import tht.cli.session_cmd; print('OK')" Se fallisce per sql_cmd/ctetest: sposta quegli import dentro le funzioni che li usano.

  • Step 7: Registra session_app
from tht.cli.session_cmd import session_app  # noqa: E402
app.add_typer(session_app, name="session")
  • Step 8: Import smoke + tht session --help

Run: .venv/bin/tht session --help Expected: help esce 0.

  • Step 9: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/cli/schema_cmd.py tht/cli/session_cmd.py tht/cli/__init__.py
git commit -m "feat(harness): port schema_cmd + session_cmd (Onda 1, radici, drift phase fix)"

Onda 2 — vector_cmd

Task 2.1: Port vector_cmd.py (esporta helper vector condivisi)

  • Step 1: Porta il file
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/vector_cmd.py > tht/cli/vector_cmd.py
sed -i '' -E 's/from tht\.session\.(phase|decisions) import/from tht.\1 import/g' tht/cli/vector_cmd.py
  • Step 2: Registra vector_app
from tht.cli.vector_cmd import vector_app  # noqa: E402
app.add_typer(vector_app, name="vector")
  • Step 3: Import smoke

Run: .venv/bin/python -c "from tht.cli.vector_cmd import vector_app, make_embedder, open_store, open_searcher, require_vector_cfg; print('OK')" Expected: OK.

  • Step 4: tht vector --help + pytest + Commit
.venv/bin/tht vector --help
.venv/bin/pytest -q | tail -2
git add tht/cli/vector_cmd.py tht/cli/__init__.py
git commit -m "feat(harness): port vector_cmd (Onda 2, helper vector condivisi)"

Onda 3 — Cmd foglia + arricchimento metadata memory

Task 3.1: Arricchisci metadata memory (subject/detail/rationale) — TDD

Files:

  • Modify: tht/memory.py (memory_vector_records)

  • Test: tests/test_memory_metadata.py

  • Step 1: Scrivi test failing

# tests/test_memory_metadata.py
from datetime import datetime
from tht.memory import MemoryRecord, memory_vector_records


def _record(**kw):
    base = dict(
        id="mem-x", ts=datetime(2025, 1, 1), session_id="s", decision_seq=1,
        type="table_promoted", subject="dim_pazienti", detail="promossa",
        rationale="perche'", question_context="dammi pazienti", tables=["t"], concepts=[],
    )
    base.update(kw)
    return MemoryRecord(**base)


def test_memory_vector_record_has_subject_detail_rationale_in_metadata():
    vr = memory_vector_records([_record()])[0]
    assert vr.metadata["subject"] == "dim_pazienti"
    assert vr.metadata["detail"] == "promossa"
    assert vr.metadata["rationale"] == "perche'"


def test_memory_vector_record_metadata_keeps_existing_fields():
    vr = memory_vector_records([_record()])[0]
    # i campi che gia' c'erano restano
    assert vr.metadata["type"] == "table_promoted"
    assert vr.metadata["tables"] == ["t"]
    assert vr.metadata["concepts"] == []
    assert vr.metadata["session_id"] == "s"
  • Step 2: Run test (fail)

Run: .venv/bin/pytest tests/test_memory_metadata.py -v Expected: FAIL (KeyError: 'subject').

  • Step 3: Modifica memory_vector_records in tht/memory.py

Nel dict metadata del VectorRecord (riga ~215), aggiungi i 3 campi:

                metadata={
                    "type": r.type, "session_id": r.session_id,
                    "tables": r.tables, "concepts": r.concepts,
                    "subject": r.subject, "detail": r.detail, "rationale": r.rationale,
                },
  • Step 4: Run test (pass)

Run: .venv/bin/pytest tests/test_memory_metadata.py -v Expected: 2 PASS.

  • Step 5: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/memory.py tests/test_memory_metadata.py
git commit -m "feat(harness): arricchisci metadata memory (subject/detail/rationale) — no lookup registro

Correzione gap ereditato: la tabella vectors.memory ora ha subject/detail/rationale
nel metadata jsonb (serializzati da pack_metadata via **record.metadata). search_similar
proietta metadata completo -> la F2 ricostruisce la decisione dall'hit senza lookup
nel registro canonico. Tabella memory vuota = momento ideale, niente re-indicizzazione."

Task 3.2: Port memory_cmd.py + search_cmd.py + evidence_cmd.py + db_cmd.py + decision_cmd.py

  • Step 1: Porta i 5 file (rename + drift path)
for cmd in memory_cmd search_cmd evidence_cmd db_cmd decision_cmd; do
  sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/$cmd.py > tht/cli/$cmd.py
  sed -i '' -E 's/from tht\.session\.(phase|decisions) import/from tht.\1 import/g' tht/cli/$cmd.py
done
  • Step 2: Fix drift costanti phase (manuale, per file)

Per ciascun file che le usa (verifica con grep -nE "MAX_PHASE|PHASE_NAMES|SCHEMA_LINKING_PHASE|DECISION_MIN_PHASE" tht/cli/*_cmd.py), correggi:

  • DECISION_MIN_PHASE.get(type, 1) → load_workflow().decision_min_phase(type)
  • SCHEMA_LINKING_PHASE → load_workflow().schema_linking_phase()
  • PHASE_NAMES[...] → load_workflow().phase_name(n)
  • MAX_PHASE → load_workflow().max_phase

Aggiungi from tht.workflow import load_workflow dove serve (o lazy dentro funzione se circular).

  • Step 3: Fix decision_cmd DECISION_MIN_PHASE (caso specifico)

decision_cmd.py:35 from tht.session.phase import DECISION_MIN_PHASE + :37 DECISION_MIN_PHASE.get(type, 1). Riscrivi:

# rimuovi l'import di DECISION_MIN_PHASE
from tht.workflow import load_workflow
...
min_phase = load_workflow().decision_min_phase(type)
require_phase_or_exit(cfg, session, min_phase)
  • Step 4: Verifica nessuna costante phase residua

Run: grep -rnE "\bMAX_PHASE\b|\bPHASE_NAMES\b|\bSCHEMA_LINKING_PHASE\b|\bDECISION_MIN_PHASE\b" tht/cli/ Expected: output vuoto.

  • Step 5: Registra le 5 app in __init__.py
from tht.cli.memory_cmd import memory_app  # noqa: E402
from tht.cli.search_cmd import search_app  # noqa: E402
from tht.cli.evidence_cmd import evidence_app  # noqa: E402
from tht.cli.db_cmd import db_app  # noqa: E402
from tht.cli.decision_cmd import decision_app  # noqa: E402
app.add_typer(memory_app, name="memory")
app.add_typer(search_app, name="search")
app.add_typer(evidence_cmd if False else evidence_app, name="evidence")  # fix: evidence_app
app.add_typer(db_app, name="db")
app.add_typer(decision_app, name="decision")

Correggi la riga evidence (typo above): app.add_typer(evidence_app, name="evidence").

  • Step 6: Import smoke + help per tutti i 5
.venv/bin/python -c "from tht.cli import app; print('app OK')"
for sub in memory search evidence db decision; do .venv/bin/tht $sub --help >/dev/null 2>&1 && echo "$sub OK" || echo "$sub FAIL"; done

Expected: tutti OK.

  • Step 7: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/cli/ tht/cli/__init__.py
git commit -m "feat(harness): port memory/search/evidence/db/decision cmd (Onda 3, drift phase)"

Onda 4 — Cmd SQL/CTE

Task 4.1: Port sql_cmd.py (hub — esporta helper condivisi con cte_cmd/session_cmd)

  • Step 1: Porta il file
sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/sql_cmd.py > tht/cli/sql_cmd.py
sed -i '' -E 's/from tht\.session\.(phase|decisions) import/from tht.\1 import/g' tht/cli/sql_cmd.py
  • Step 2: Fix drift costanti phase (manuale se presenti)

Run: grep -nE "\bMAX_PHASE\b|\bPHASE_NAMES\b" tht/cli/sql_cmd.py Se presenti, correggi come nei task precedenti.

  • Step 3: Registra sql_app
from tht.cli.sql_cmd import sql_app  # noqa: E402
app.add_typer(sql_app, name="sql")
  • Step 4: Import smoke + help
.venv/bin/python -c "from tht.cli.sql_cmd import sql_app, do_run, promoted_tables_for, _load_physical_or_exit; print('OK')"
.venv/bin/tht sql --help

Expected: OK.

  • Step 5: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/cli/sql_cmd.py tht/cli/__init__.py
git commit -m "feat(harness): port sql_cmd (Onda 4, hub helper)"

Task 4.2: Port cte_cmd.py + datamart_cmd.py + lsh_cmd.py

  • Step 1: Porta i 3 file
for cmd in cte_cmd datamart_cmd lsh_cmd; do
  sed 's/psdwp3/tht/g' /Users/mp/projects/ThothII/ChironeWp3/src/psdwp3/cli/$cmd.py > tht/cli/$cmd.py
  sed -i '' -E 's/from tht\.session\.(phase|decisions) import/from tht.\1 import/g' tht/cli/$cmd.py
done
  • Step 2: Fix drift costanti phase

  • cte_cmd: require_phase_or_exit(cfg, session, 6) — letterale 6 OK (CTE è F6), ma verifica non usi MAX_PHASE.

  • datamart_cmd: require_phase_or_exit(..., 8) — letterale 8 OK.

  • lsh_cmd: verifica nessuna costante phase.

Run: grep -rnE "\bMAX_PHASE\b|\bPHASE_NAMES\b|\bSCHEMA_LINKING_PHASE\b" tht/cli/cte_cmd.py tht/cli/datamart_cmd.py tht/cli/lsh_cmd.py Expected: output vuoto.

  • Step 3: Registra le 3 app
from tht.cli.cte_cmd import cte_app  # noqa: E402
from tht.cli.datamart_cmd import datamart_app  # noqa: E402
from tht.cli.lsh_cmd import lsh_app  # noqa: E402
app.add_typer(cte_app, name="cte")
app.add_typer(datamart_app, name="datamart")
app.add_typer(lsh_app, name="lsh")
  • Step 4: Import smoke + help
.venv/bin/python -c "from tht.cli import app; print('app OK')"
for sub in cte datamart lsh; do .venv/bin/tht $sub --help >/dev/null 2>&1 && echo "$sub OK" || echo "$sub FAIL"; done
  • Step 5: Verifica finale CLI completa

Run: .venv/bin/tht --help Expected: tutti i sottocomandi visibili (phase, config, schema, session, vector, memory, search, evidence, db, decision, sql, cte, datamart, lsh).

  • Step 6: pytest verde + Commit
.venv/bin/pytest -q | tail -2
git add tht/cli/ tht/cli/__init__.py
git commit -m "feat(harness): port cte/datamart/lsh cmd (Onda 4) — CLI completa F1→F8"

Skill — Riscrittura tht-sessione

Task S.1: Crea struttura skill + frontmatter

  • Step 1: Crea la directory e il frontmatter
mkdir -p .pi/skills/tht-sessione

Crea .pi/skills/tht-sessione/SKILL.md con frontmatter:

---
name: tht-sessione
description: Orchestratore del workflow Thoth NL->SQL, fasi 1-8 (chiarimento domanda, memorie, riscrittura, schema linking, sintesi, piano CTE, SQL finale, datamart dbt). Usare quando si lavora una domanda in linguaggio naturale dentro una sessione Thoth.
---

Task S.2: Scrivi SKILL.md (discipline + F1-F8)

  • Step 1: Scrivi la sezione Discipline (con D13 free-text + D15 rollback)

Scrivi # Workflow sessione Thoth (fasi 1-8) + sezione ## Discipline (valgono in ogni fase). Adatta dalla skill ChironeWp3 (leggi /Users/mp/projects/ThothII/ChironeWp3/.pi/skills/nsp-sessione/SKILL.md righe 8-92), con questi cambi:

  • "PsdWp3" → "Thoth"
  • "dialog native" / "checklist a caselle" → "widget reviewer_select/reviewer_decide/reviewer_confirm"
  • Aggiungi disciplina D13 free-text: "Quando il reviewer usa 'Altro' con testo libero, valuta il testo in contesto, agisci, ri-chiedi se ambiguo (non defaultare). Il testo si registra nel rationale della decisione."
  • Aggiungi disciplina D15 rollback: "Dopo /torna N o 'Torna indietro', riprendi dalla Fase N rivedendo gli artefatti esistenti; teardown_to_phase cancella gli artefatti oltre il target. NON ri-eseguire comandi per artefatti ancora validi."

Trasferisci fedelmente i vincoli precisi (accetta-la-proposta sempre, nessuno step in limbo, artefatto = output di prima classe, una domanda alla volta).

  • Step 2: Scrivi F1 (Chiarimento)

Adatta F1 da ChironeWp3 (righe 93-119). Cambi: nomi Thoth, vocabolario widget. Sostanza invariata (nsp search per esplorare, reviewer_decide per chiarimenti con concept_clarified, rewrite_question per aggiornare question.md, reviewer_confirm kind:phase per chiudere). Sostituisci nsp → tht nei comandi.

  • Step 3: Scrivi F2 (Memorie) con save-one D11

Adatta F2. Cambi chiave: in profile=workstation, la promozione di UNA memoria usa tht memory save-one (D11). La skill guida: una memoria → save-one, molte → memory promote + memory index. tht memory search ritorna hit con metadata completo (subject/detail/rationale grazie al Task 3.1), quindi il modello ricostruisce la decisione direttamente dall'hit senza lookup registro.

  • Step 4: Scrivi F3 (Riscrittura)

Adatta F3 da ChironeWp3 (righe 143-172). Praticamente invariata: prerequisito Fase 3 (exit 5), reviewer_decide advance:true, sequenza ordinata (decisione → rewrite_question → tht session set-question). Solo vocabolario widget + nomi Thoth.

  • Step 5: Scrivi F4 (Schema linking) con D14 value-grounding + formula

Adatta F4. AGGIUNGI rispetto a ChironeWp3:

  • value_grounded (D14a): quando un valore citato (es. "ablazione") matcha più colonne (flag + testo), presenta reviewer_decide con opzioni value_grounded per ogni colonna candidata (usando aggregate_lsh_multi che non collassa). Il reviewer sceglie l'ancora.
  • concept_formula_approved/rejected (D14b): quando un concetto (es. "fascia pediatrica", "stesso anno") ha una formula SQL candidata (tht formula retrieve <concept>), presentala e il reviewer approva/rifiuta.

Mantieni i tipi esistenti (table_promoted/excluded/column_corrected/join_modified/evidence_*).

  • Step 6: Scrivi F5 (Sintesi) + F6 (CTE) + F7 (SQL) + F8 (Datamart)

Adatta F5-F8 da ChironeWp3 (righe 198-358). Praticamente invariate nella sostanza (schema_linking.json, session check, memory promote, reviewer_confirm kind:phase; CTE plan/test/result; SQL validate/explain/preview/save/export; datamart sì/no stub). Solo vocabolario widget + nomi Thoth + nsp→tht.

  • Step 7: Verifica coerenza cmd

Run: grep -oE "tht [a-z][a-z-]*" .pi/skills/tht-sessione/SKILL.md | sort -u Per ogni comando citato, verifica sia registrato: .venv/bin/tht <cmd> --help deve uscire 0.

Task S.3: Porta + adatta i 4 sottomoduli

  • Step 1: Porta i sottomoduli con adattamento vocabolario
for mod in cte memoria rewriting sql-generation; do
  sed -e 's/psdwp3/tht/g' -e 's/PsdWp3/Thoth/g' -e 's/nsp/tht/g' \
    /Users/mp/projects/ThothII/ChironeWp3/.pi/skills/nsp-sessione/$mod.md > .pi/skills/tht-sessione/$mod.md
done

Nota: sed 's/nsp/tht/g' qui è sicuro (sottomoduli prosa, non codice con transport/nspname). Verifica comunque: grep -wE "nsp" .pi/skills/tht-sessione/*.md → output vuoto.

  • Step 2: Verifica nessun riferimento chirone/psd nella skill

Run: grep -rniE "chirone|psdwp3|policlinico|sandonato|\bnsp\b" .pi/skills/tht-sessione/ Expected: output vuoto.

  • Step 3: Commit skill
git add .pi/skills/tht-sessione/
git commit -m "feat(harness): riscrittura skill tht-sessione (F1-F8, widget-descriptor, D11/D13/D14/D15)

Skill ex-novo che riflette Thoth: vocabolario widget-descriptor (reviewer_select/
decide/confirm), save-one D11 in F2, value_grounded + concept_formula D14 in F4,
free-text D13 e rollback D15 nelle discipline. Sottomoduli cte/memoria/rewriting/
sql-generation adattati. Nessun riferimento chirone/psd."

Onda 0b — Setup pre-sessione (evidence + LSH build)

Task 0b.1: Copia evidence dentro ThothII

  • Step 1: Verifica la cartella sorgente esiste

Run: ls -d /Users/mp/Chirone/chirone/etl/docs && du -sh /Users/mp/Chirone/chirone/etl/docs Expected: la cartella esiste, ~11M.

  • Step 2: Revisione PII (prima di committare)

Run (cerca dati che sembrano PII — codici fiscali, nomi+date, ecc.):

grep -rniE "codice fiscale|fiscal|[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]" /Users/mp/Chirone/chirone/etl/docs 2>/dev/null | head

Se trova PII → NON committare; metti harness/evidence/ in .gitignore e distribuisci fuori banda. Se pulito → procedi.

  • Step 3: Copia la cartella
cp -r /Users/mp/Chirone/chirone/etl/docs harness/evidence
  • Step 4: Aggiorna THT_DOCS_ROOT nel .env + tht-test.yaml
cd /Users/mp/projects/ThothII/harness
# .env: imposta THT_DOCS_ROOT al path dentro ThothII
sed -i '' -E 's|^THT_DOCS_ROOT=.*|THT_DOCS_ROOT=harness/evidence|' .env

In workspaces/tht-test.yaml, aggiungi/verifica il blocco evidence:

evidence:
  source_root: ${THT_DOCS_ROOT}
  evidence_dir: evidence
  • Step 5: Verifica tht search --kind evidence funziona

Run (dopo Onda 3 che porta search_cmd):

set -a; . ./.env; set +a
.venv/bin/tht search --kind evidence "ablazione" 2>&1 | head -5

Expected: risultati non vuoti.

  • Step 6: Commit (se PII-pulito)
git add harness/evidence/ .env  # .env è gitignored normalmente; verificare
git add -f .env 2>/dev/null || echo "(.env gitignored, salto)"
git commit -m "feat(harness): evidence dentro ThothII (Onda 0b) — self-contained"

Attenzione: .env è gitignored. NON committarlo con -f se contiene chiavi reali. Il default THT_DOCS_ROOT=harness/evidence va invece in .env.example.

Task 0b.2: Build indice LSH sul workspace tht-test

  • Step 1: Verifica prerequisiti (VPN + Ollama + lsh_cmd portato)
curl -s --max-time 2 http://localhost:11434/api/tags >/dev/null && echo "ollama OK" || echo "ollama DOWN"
.venv/bin/tht lsh --help >/dev/null 2>&1 && echo "lsh_cmd OK" || echo "lsh_cmd NON portato (verifica Onda 4)"
  • Step 2: Build indice (one-shot)
set -a; . ./.env; set +a
.venv/bin/tht lsh build --workspace workspaces/tht-test.yaml

Expected: indice costruito in indexes/, nessun errore. Richiede VPN (DWH raggiungibile).

  • Step 3: Verifica tht search ritorna match LSH multi-colonna

Run:

.venv/bin/tht search "ablazione" 2>&1 | head -20

Expected: match su più colonne (es. flag + testo patologia).

  • Step 4: Verifica test_value_grounding_real smette di skip-piare

Run: .venv/bin/pytest -m l2 tests/l2/test_value_grounding_real.py -o addopts="" -v Expected: PASS (non più SKIPPED per ModuleNotFoundError).


Sessione L2 — Validazione manuale (con l'operatore)

Task L2.1: Sessione end-to-end "cardioversione + ablazione same-year"

Prerequisiti: Onda -1, 0, 1, 2, 3, 4, Skill, 0b tutte complete. .env popolato + VPN + Pi con GLM 5.2.

  • Step 1: Avvia Pi in modalità RPC
cd /Users/mp/projects/ThothII/harness
pi --mode rpc
  • Step 2: Lancia la domanda di test

In Pi:

/nuova-domanda "Crea una lista con i pazienti che negli ultimi 20 anni hanno avuto una cardioversione elettrica ed una ablazione lo stesso anno. Per ogni paziente incluso nella lista esponi il sesso, l'età che aveva il paziente nell'anno in cui ha fatto l'ablazione, tutti i dati rilevanti dell'ablazione e tutti i dati rilevanti della cardioversione"
  • Step 3: Verifica criteri di successo (1-6 dallo spec)

Durante la sessione, verifica:

  1. Il modello chiama tht session new, legge la skill, inizia F1.
  2. Il modello chiama tool reviewer_* e il gate presenta widget (non prosa grezza).
  3. Ogni decisione del reviewer produce una riga in review_decisions.jsonl.
  4. Il workflow avanza F1→... usando i comandi tht corretti.
  5. Se il modello prova tht phase advance da shell, il gate lo blocca.
  6. Artefatti prodotti: schema_linking.json, cte_plan.json/ctes/*.sql, sql_final.sql.
  • Step 4: Documenta l'esito

Annota: fasi completate, dove si è fermato (se si ferma), eventuali bug del porting emersi. Un fallimento a metà NON è un fallimento del porting — è il segnale che L2 coglie.

  • Step 5: Commit note di sessione
# salva un report della sessione
git add docs/l2-session-report-<data>.md  # se creato
git commit -m "test(harness): sessione L2 cardioversione+ablazione — report esito"

Self-Review del piano

1. Spec coverage:

  • Decisione 1 (scope F1→F8): Tasks 0-4 portano tutti i cmd + backend. ✓
  • Decisione 2 (drift phase.py): Tasks 1.1 (schema_linking_phase), 1.2 (require_phase_or_exit + phase advance/reopen/show), 1.4/3.2 (fix costanti in cmd). ✓
  • Decisione 3 (skill riscritta): Tasks S.1-S.3. ✓
  • Decisione 4 (indipendenza ChironeWp3): verify in -1.8 (grep chirone). Evidence in 0b.1. ✓
  • Decisione 5 (registro memory locale): nessun task — proprietà di default (paths.artifacts locale). Documentata nello spec. ✓
  • Decisione 6 (metadata memory arricchito): Task 3.1 (TDD). ✓
  • Decisione 7 (evidence in ThothII): Task 0b.1. ✓
  • Decisione 8 (renaming tht): Onda -1 completa (Tasks -1.1 a -1.8). ✓
  • Decisione 9 (neutralizza chirone/psd): Task -1.6 Step 2-3. ✓
  • Decisione 10 (Onda -1 isolata): struttura del piano. ✓
  • Onda 0b (LSH + evidence setup): Tasks 0b.1, 0b.2. ✓

2. Placeholder scan: Tutti i task hanno codice/comandi concreti. Le parti "adatta dalla skill ChironeWp3 righe X-Y" referenziano file sorgente esatti leggibili. Nessun TBD/TODO. ✓

3. Type consistency: schema_linking_phase() usato in 1.1, referenziato in 1.4/3.2/4.2. require_phase_or_exit definito in 1.2, usato in cmd portati. Helper session_dir/_load_config_or_exit/physical_path portati in 1.4, usati nei cmd successivi. Coerente. ✓

Gap residui segnalati onestamente:

  • session_cmd ↔ sql_cmd circularità (Task 1.4 Step 6): ho segnalato di importare lazy, ma l'ordine esatto può richiedere aggiustamenti in implementazione.
  • RPC lato server mancanti (validate_select, current_user): rilevate 404. Non bloccano il loop base ma vanno (ri)installate sul Supabase. Operazione server-side, fuori dal piano codice.
  • L2 sessione è manuale/non-deterministica: Task L2.1 richiede l'operatore. Un fallimento a metà è informativo, non bloccante per il "done" del porting codice.