Files
ThothII/docs/superpowers/plans/2026-06-27-tht-porting-cli-skill.md
T
marcopan d857004f47 docs(plan): allinea piano alle 3 correzioni spec (drop registry, repo workspace, LSH)
- Task 3.2: nuovo Step 1b — drop funzioni registry da memory_cmd + tht/memory.py
  (load/save/update/delete/promote/reusable_promotions); promotion (F5) riscritta
  come upsert batch al vectordb; memory list/delete su vectordb (no registry).
- Skill F2/F5: drop riferimenti a 'memory promote + memory index' con registry.
- Onda 0b riscritta: repo workspace per-cliente separato (no copia in harness/),
  LSH scarica-tutti-i-valori-distinti (no 'campiona'), indice nel repo workspace cliente.
2026-06-27 12:52:58 +02:00

1222 lines
49 KiB
Markdown

# 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**
```bash
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)**
```bash
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)**
```bash
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:
```toml
name = "tht"
```
```toml
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**
```bash
git mv .pi/extensions/nsp-gate.js .pi/extensions/tht-gate.js
```
- [ ] **Step 2: Rewrite `nsp` → `tht` nei .js (word-boundary, lascia camelCase)**
```bash
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:
```bash
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**
```bash
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_`:
```bash
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:
```bash
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**
```bash
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**
```bash
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)**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
.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**
```bash
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**
```bash
.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**
```bash
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**
```bash
.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**
```bash
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**
```bash
.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**
```bash
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**
```bash
.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:
```bash
.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:
```python
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`:
```python
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**
```bash
.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):
```python
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 è:
```python
# 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**
```bash
.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**
```bash
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):
```python
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**
```bash
.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**
```bash
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`:
```python
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**
```bash
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**
```python
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**
```bash
.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**
```bash
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**
```python
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**
```bash
.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**
```python
# 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:
```python
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**
```bash
.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)**
```bash
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 1b: Drop funzioni registry da memory_cmd + tht/memory.py (decisione spec 5)**
Le memory vivono SOLO nel vectordb; il `registry.jsonl` è vestigiale e droppato. In `tht/memory.py` NON portare le 6 funzioni che lo gestiscono: `load_registry`, `save_registry`, `update_record`, `delete_record`, `promote(registry_path)`, `reusable_promotions`. In `memory_cmd.py` i comandi che le usavano (`memory promote`, `memory list`, `memory delete` con il vecchio modello) vanno riscritti o droppati:
- `memory promote` → riscritto come upsert batch al vectordb (usa `memory_vector_records` + `writer.upsert_records`, analogo a `save_one_memory` ma su una lista). La skill F5 chiama questo.
- `memory list` → scan del vectordb (reader).
- `memory delete` → upsert con `metadata.status="superseded"` (il writer è upsert-only, niente DELETE fisico).
- `memory search` → invariato (legge dal vectordb, l'hit ha già metadata completo grazie al Task 3.1).
**Conservare in `tht/memory.py`:** `MemoryRecord`, `memory_vector_records`, `memory_vector_record_for_decision`, `save_one_memory`. Le decisioni restano nel ledger di sessione (`review_decisions.jsonl`, per-sessione, gestito da `tht/decisions.py` — separato dalle memory).
- [ ] **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:
```python
# 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**
```python
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**
```bash
.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**
```bash
.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**
```bash
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**
```python
from tht.cli.sql_cmd import sql_app # noqa: E402
app.add_typer(sql_app, name="sql")
```
- [ ] **Step 4: Import smoke + help**
```bash
.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**
```bash
.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**
```bash
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**
```python
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**
```bash
.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**
```bash
.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**
```bash
mkdir -p .pi/skills/tht-sessione
```
Crea `.pi/skills/tht-sessione/SKILL.md` con frontmatter:
```yaml
---
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. **Cambio chiave (drop registry, decisione spec 5):** le memory vivono SOLO nel vectordb. La skill F2 usa `tht memory search` per trovare memorie; l'hit ha metadata completo (subject/detail/rationale grazie al Task 3.1), quindi il modello ricostruisce la decisione direttamente dall'hit. **Niente `memory promote` + `memory index` con registry** (vestigiale, droppato): la promotion (F5) è upsert diretto a vectordb (`save-one` per una memoria, batch per molte).
- [ ] **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, **promotion memory = upsert batch diretto a vectordb** [drop registry, vedi decisione spec 5], 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**
```bash
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**
```bash
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 (repo workspace per-cliente + LSH build)
**Principio (decisioni spec 7+8):** ThothII è generico. Il contenuto evidence e gli indici LSH sono **per-cliente**, in un repo workspace separato. Niente copia in `harness/`.
### Task 0b.1: Prepara il repo workspace per-cliente
Per il test L2, il cliente è PSD (aritmologia @ policlinico). Il contenuto evidence esiste già sul disco del dev (`/Users/mp/Chirone/chirone/etl/docs`).
- [ ] **Step 1: Crea la struttura del repo workspace cliente**
```bash
# Il repo workspace è SEPARATO da ThothII. Per il test locale lo si crea accanto.
mkdir -p /Users/mp/projects/tht-workspace-psd/evidence
# Collega/copialo l'evidence esistente (curata a mano, 229 markdown, ~11M)
cp -r /Users/mp/Chirone/chirone/etl/docs/* /Users/mp/projects/tht-workspace-psd/evidence/
# Workspace YAML cliente (copia del template, parametrizzato per PSD)
cp /Users/mp/projects/ThothII/harness/workspaces/tht.example.yaml \
/Users/mp/projects/tht-workspace-psd/psd.yaml
```
> Il repo `tht-workspace-psd/` va git-init separatamente (è per-cliente, non parte di ThothII). Per il test locale si usa così com'è.
- [ ] **Step 2: Configura il .env per puntare al repo workspace cliente**
```bash
cd /Users/mp/projects/ThothII/harness
# THT_DOCS_ROOT punta all'evidence del repo cliente (NON a harness/evidence)
sed -i '' -E 's|^THT_DOCS_ROOT=.*|THT_DOCS_ROOT=/Users/mp/projects/tht-workspace-psd/evidence|' .env
```
- [ ] **Step 3: Verifica tht search --kind evidence funziona**
Run (dopo Onda 3 che porta search_cmd):
```bash
set -a; . ./.env; set +a
.venv/bin/tht search --kind evidence "ablazione" 2>&1 | head -5
```
Expected: risultati non vuoti.
- [ ] **Step 4: Documenta il deploy nel README**
Il README ThothII deve spiegare la struttura deploy: `checkout ThothII + checkout tht-workspace-<cliente> + .env punta al repo cliente`. Aggiorna la sezione "Configure" del README.
### Task 0b.2: Build indice LSH (scarica TUTTI i valori distinti, per-cliente)
**Principio (decisione spec 8):** `tht lsh build` scarica tutti i valori distinti scaricabili delle colonne di testo dal DWH (NON "campiona"), costruisce MinHash+LSH, serializza nel path `indexes/` del **repo workspace cliente**.
- [ ] **Step 1: Verifica prerequisiti (VPN + Ollama + lsh_cmd portato)**
```bash
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, per il workspace cliente PSD)**
```bash
set -a; . ./.env; set +a
.venv/bin/tht lsh build --workspace /Users/mp/projects/tht-workspace-psd/psd.yaml
```
Expected: indice costruito in `<repo-workspace>/indexes/`, nessun errore. Richiede VPN (DWH raggiungibile).
- [ ] **Step 3: Verifica tht search ritorna match LSH multi-colonna**
Run:
```bash
.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**
```bash
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**
```bash
# 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.