feat(replay): extract reviewer_schema_linking gates + de-slug question fallback

extract.mjs now emits a schema-linking ui_request descriptor (columns rendered
from the model's suggested_columns, since the live catalog enrichment needs the
DWH) and parses the reviewer's "N tabelle" outcome; when neither the manifest
nor question.md is reachable, the NL question is approximated by de-slugging
the session id. replay.json regenerated from the 2026-07-06-175012 session
(18 answered gates, includes the F4 schema-linking gate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 16:04:00 +02:00
co-authored by Claude Fable 5
parent 34aeda006e
commit 87e875bc81
2 changed files with 581 additions and 4017 deletions
+44 -1
View File
@@ -215,6 +215,33 @@ function buildDescriptor(name, args, idx) {
reserved: RESERVED,
};
}
// F4 schema-linking gate (column curation, v2). The model supplies tables with
// kind/rationale/suggested_columns; the live gate enriches columns from the
// catalog via `tht schema columns` (needs DWH), so here we render only what the
// model provided — columns come from suggested_columns as suggested=true.
if (name === "reviewer_schema_linking") {
const tables = parseOptions(args.tables).map((t) => ({
id: t.id,
name: t.name,
kind: t.kind ?? "promote",
recommended: !!t.recommended,
description: t.description ?? null,
rationale: t.rationale ?? null,
columns: (parseOptions(t.suggested_columns).map((c) =>
typeof c === "string" ? { name: c, description: null, suggested: true } : c,
)),
}));
return {
type: "ui_request",
id,
phase,
schema_version: SCHEMA_VERSION,
widget: "schema-linking",
title: args.title,
tables,
reserved: RESERVED,
};
}
return null;
}
@@ -263,6 +290,11 @@ function parseRealChoice(name, descriptor, resultText) {
if (/Rifiutato/i.test(t)) return { labels: ["Rifiuta"], note: t };
return { labels: [], note: t };
}
if (name === "reviewer_schema_linking") {
// "Schema linking registrato dal reviewer (6 tabelle + colonne curate)."
const m = t.match(/registrato dal reviewer \((\d+)\s+tabelle/);
return { labels: m ? [`${m[1]} tabelle`] : [], note: t };
}
return { labels: [], note: t };
}
@@ -303,6 +335,17 @@ function readSessionQuestion(sid) {
return null;
}
// Last-resort fallback when neither the manifest nor question.md can be found
// (e.g. the workspace repo is not checked out here). `tht session new` derives
// the session id slug from the question text, so de-slugging it recovers a
// readable approximation of the original NL question.
function questionFromSessionId(sid) {
if (typeof sid !== "string") return null;
const slug = sid.replace(/^\d{4}-\d{2}-\d{2}-\d{6}-/, "");
if (!slug) return null;
return slug.replace(/-/g, " ");
}
// Resolve a session id to its on-disk directory. We check every workspace yaml
// in harness/workspaces/ for a `paths.sessions` entry (relative OR absolute),
// then look for <sessions>/<session-id>/ in each. Workspaces are symlinks to
@@ -350,7 +393,7 @@ const replay = {
},
session: {
id: sessionId,
question: readSessionQuestion(sessionId),
question: readSessionQuestion(sessionId) ?? questionFromSessionId(sessionId),
created_at: createdAt,
},
stats: {
+537 -4016
View File
File diff suppressed because it is too large Load Diff