# ThothII reviewer replay Riproduce su `http://localhost:5333` la **vera UI** dei widget reviewer di una sessione registrata, **senza VPN, Pi, Python, né DWH**. Si usano i descriptor dei gate così come furono proposti al revisore (catturati nel transcript Pi), nello stesso ordine, con le scelte realmente fatte mostrate come badge di confronto. ## Schema del flusso ``` browser (SPA reale, :5333) │ REST + SSE, same-origin ▼ server.mjs (node:http, zero dipendenze) ├─ static: serve il bundle SPA da tools/replay/web/ └─ replay SSE: GET /sessions/:id/events → event: ui_request (gate N) POST /sessions/:id/response → badge "scelta reale" + gate N+1 │ legge ▼ replay.json (fixture estratta dal transcript) │ prodotta da ▼ extract.mjs (legge ~/.pi/agent/sessions/…/*.jsonl + question.md) ``` Niente backend Fastify, niente `pi --mode rpc`, niente `tht`, niente DB: il server gira direttamente sui descriptor registrati. ## Comandi ```bash # Estrai la fixture per una sessione (id, file, o directory di transcript): scripts/replay.sh extract 2026-07-03-132933-fammi-la-lista-dei-pazienti-che-negli-ul scripts/replay.sh extract ~/.pi/agent/sessions/--Users-mp-projects-ThothII-harness--/ # (una tantum, o dopo modifiche al frontend) costruisci il bundle SPA: scripts/replay.sh build # Avvia il server su :5333: scripts/replay.sh run # Scorciatoia: extract (default) + build (se manca) + run: scripts/replay.sh ``` **Cosa può estrarre `extract`:** | Input | Comportamento | |---|---| | *(nessuno)* | La sessione di default (3 lug 17:07, 20 gate). | | `` | Scansiona **tutti** i transcript nella directory Pi per quel session-id. Gestisce automaticamente sessioni spezzate su più file (resume): raccoglie i gate da tutti i transcript, li ordina cronologicamente, e scarta i gate senza risposta (quelli riproposti al resume). | | `` | Un singolo transcript. | | `` | Tutti i `*.jsonl` nella directory; se contengono sessioni diverse, tiene quella dominante (con più gate). | La domanda della sessione è letta da `session_manifest.yaml` (campo `question`), cercando la directory sessions in ogni workspace di `harness/workspaces/` (via `paths.sessions`, assoluto o relativo). Niente più path hardcoded. ## Cosa vedi nel browser 1. Apri `http://localhost:5333`. La lista sessioni mostra **una** sessione (quella riprodotta) con la domanda reale. 2. Cliccala: appare il **gate 1** — un `reviewer_select` (widget select) con il titolo, l'intro e le opzioni esatte che il modello propose il 3 luglio, incluso il badge "consigliato" sull'opzione raccomandata. 3. Clicca un'opzione. Sotto la tua scelta compare un `info` con la **scelta reale del 3 luglio** (`📌 SCELTA REALE (3 lug 2026): …`), e — se coincide — un `✓ Hai scelto come il 3 luglio.` 4. Avanzi attraverso tutti i gate nell'ordine loggato: `select` (F1 chiarimenti, F6 conferma piano), `multiselect` (F3 riscrittura, F4 schema linking, F6 piano CTE), `artifact-gate` (F2/F3/F4/F5 phase advance, F6 approvazione CTE con SQL visibile nell'`ArtifactView`). 5. Dopo l'ultimo gate: `✓ Replay completato — 20/20 gate`. I 20 gate della sessione 17:07: 4 `select` + 11 `confirm` + 5 `decide`. ## Limiti e fedeltà al log - **Sessioni multi-file (resume)**: gestite. L'estrattore raccoglie i gate da tutti i transcript della sessione, li ordina per timestamp del messaggio, e scarta i gate senza `toolResult` (quelli dove il revisore chiuse Pi senza rispondere e che furono riproposti al resume). Risultato: la sequenza esatta dei gate che il revisore ha effettivamente visto e deciso. - **Workspace discovery**: la domanda è letta da `session_manifest.yaml`, cercando `paths.sessions` nei workspace di `harness/workspaces/`. Funziona per qualunque cliente (non solo `psd`), purché il workspace yaml sia presente e `paths.sessions` sia un path assoluto o relativo (non espanso con `${VAR}` — in quel caso il path non è risolvibile offline e la domanda resta vuota). - **Phase compute disattivata**: la fase corrente (colorazione `F1`…`F8`) non è ricalcolata dal ledger (richiederebbe `tht phase`); i gate usano un phase-tag neutro `replay`. L'ordine e i descriptor sono quelli reali. - **Gate falliti/saltati**: alcuni gate non hanno una "scelta reale" perché nella sessione live furono saltati (memorie vuote → avanzamento auto) o falliti (es. `cte_plan` con tipo invalido). Il badge `⚠ Gate fallito nella sessione reale` lo segnala fedelmente — riflette il log, non un bug del replay. - **`text_delta`/ragionamenti del modello**: omessi (scelta "solo gate"). Per vederli, estendi `extract.mjs` per raccogliere anche i `text_delta`. - **Reserved controls** (`back`/`exit`/`other`): nel replay sono ammessi come qualunque altra risposta, ma non hanno semantica (non c'è stato di navigazione da ripristinare); trattali come "procedi". - **Single-user, single-session**: il server tiene un solo cursore globale; non è pensato per più client concorrenti (un refresh del browser riprende dal gate corrente). ## File - `extract.mjs` — estrattore fixture dal transcript Pi JSONL. - `augment-schema-linking.mjs` — riscrive il gate F4 tabelle/colonne come descrittore `schema-linking` (vedi `schema-linking-fixture.json`). - `augment-review-gates.mjs` — riscrive i gate F6 piano CTE, CTE 1 approvato e Fase 5 completata come descrittori v2 (`cte_plan`/`cte_result`/`phase`, vedi `cte-plan-fixture.json`, `cte-result-fixture.json`, `phase-summary-fixture.json`). **Nota:** questi tre gate sono ancora fixture sintetiche coerenti coi contratti v2 — dopo la prima sessione live che passa per un gate `cte_plan`/`cte_result`/`phase` reale, rilancia `extract.mjs` per catturare i descrittori v2 reali e sostituire le fixture. - `server.mjs` — server HTTP+SSE standalone (node:http, zero deps). - `replay.json` — fixture generata (committata per comodità; rigenerata con `augment-schema-linking.mjs` poi `augment-review-gates.mjs`, in quest'ordine). - `web/` — bundle SPA costruito da `frontend/` con `VITE_BACKEND_URL=http://localhost:5333` (gitignorato; prodotto da `build`).