fix(harness): recover schema table name typos

This commit is contained in:
2026-07-20 17:53:24 +02:00
parent 3b52c8c08c
commit ec9b12dff4
2 changed files with 145 additions and 13 deletions
+61 -4
View File
@@ -367,6 +367,50 @@ function currentPhase(ctx, session) {
return m ? parseInt(m[1], 10) : 1;
}
// --- fuzzy table-name matching (prevents multi-turn debug loops on typos) ---
function levenshtein(a, b) {
if (a === b) return 0;
const la = a.length, lb = b.length;
if (!la) return lb;
if (!lb) return la;
let prev = Array.from({ length: lb + 1 }, (_, i) => i);
for (let i = 1; i <= la; i++) {
const cur = [i];
for (let j = 1; j <= lb; j++) {
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] !== b[j - 1] ? 1 : 0));
}
prev = cur;
}
return prev[lb];
}
const _catalogNamesCache = new Map();
function catalogTableNames(ctx) {
const key = ctx.cwd ?? "";
if (_catalogNamesCache.has(key)) return _catalogNamesCache.get(key);
let names;
try {
const raw = tht(ctx, ["schema", "render", "--format", "mschema-text"]);
names = [...raw.matchAll(/^CREATE TABLE (\S+)/gm)].map((m) => m[1]);
} catch {
names = [];
}
_catalogNamesCache.set(key, names);
return names;
}
function closestTableName(ctx, name, maxDist = 3) {
const names = catalogTableNames(ctx);
let best = null, bestDist = maxDist + 1;
for (const n of names) {
if (Math.abs(n.length - name.length) > maxDist) continue;
const d = levenshtein(n, name);
if (d < bestDist) { bestDist = d; best = n; }
}
return best;
}
// tht phase advance --auto: exit 6 = not ready / needs human (silent no-op), others propagated.
// Used for the fire-and-forget auto-advance of the auto phases (F2 memory, F6 cte) after a
// reviewer_decide: it only advances when the phase is auto-eligible (zero substantive
@@ -978,10 +1022,23 @@ export default function (pi) {
try {
cat = JSON.parse(tht(ctx, ["schema", "columns", t.name, "--json"]));
} catch (e) {
const msg = (e.stderr || e.message || String(e)).toString().trim();
return textResult(
`Tabella '${t.name}' non caricabile dal catalogo (${msg}). Proponi solo tabelle presenti nel catalogo (usa 'tht schema render' / 'tht search' per verificarne i nomi).`,
);
// Fuzzy recovery: the model often misspells Italian table names
// (e.g. "abellazione" vs "ablazione"). Auto-correct if a close
// catalog match exists, preventing a multi-turn debug spiral.
const fix = closestTableName(ctx, t.name);
if (fix) {
try {
cat = JSON.parse(tht(ctx, ["schema", "columns", fix, "--json"]));
t.id = fix;
t.name = fix;
} catch { /* fall through to error */ }
}
if (!cat) {
const msg = (e.stderr || e.message || String(e)).toString().trim();
return textResult(
`Tabella '${t.name}' non caricabile dal catalogo (${msg}). Proponi solo tabelle presenti nel catalogo (usa 'tht schema render' / 'tht search' per verificarne i nomi).`,
);
}
}
if (!Array.isArray(cat.columns)) {
return textResult(`Catalogo per '${t.name}' non contiene colonne valide. Verifica con 'tht schema columns ${t.name} --json'.`);