# SQL Server qAI 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:** A containerised portal where a DBA pastes a SQL Server ShowPlan XML, gets 2–4 ranked remediation options plus SQL rewrites, with Jev (TypeSafe System One) scoring every option and choosing the first one to run, rendered as streamed OpenUI Lang elements. **Architecture:** One Next.js process, one SQLite file. The pipeline is deterministic-first: code parses the plan XML into a bounded digest, the user's OpenAI-compatible **analyst** model proposes candidates as JSON, **Jev** answers typed `score`/`choice`/`noul` questions about each candidate, code combines them with visible weights, then the analyst streams the final report in OpenUI Lang which `` paints as it arrives. Reopening a history item replays the stored report — zero model calls. **Tech Stack:** Next.js 16 (App Router) + React 19 + TypeScript, Tailwind v4 (layout utilities only), `node:sqlite`, `node:crypto`, `fast-xml-parser`, `zod/v4`, `@openuidev/react-lang`, `@typesafe-ai/sdk`, `lucide-react`. Tests with `node --test` on `.ts` files — Node strips types natively, so there is no test framework to install. **Spec:** `docs/superpowers/specs/2026-09-21-sql-server-qai.md` ## What the user sees Written for the implementer and for anyone reviewing whether this is the product that was asked for. No code in this section. - **Sign up** with email + password. Nothing else exists before that; there is no public analysis page. - **First-run guide, 3 steps.** Step 1 explains what the tool does. Step 2 is one form: the analyst model (base URL, key, model name, extra JSON params) and the Jev key. Step 3 confirms both models are reachable and sends them to the console. Every credential is typed by the logged-in user in this panel or in Settings — none is read from server config, so a second user brings their own keys and their own spend. - **Settings** holds the same form plus email/password change. Stored keys show as `••••1234`; leaving a key blank keeps the stored one. - **Console**: left rail is the history (rename inline, delete behind a confirm); the main pane is one job — paste ShowPlan XML or drop a `.sqlplan` file, optionally add a note, press Analyse. A "Where do I get this XML?` disclosure gives the SSMS route (Ctrl+M → run → save the `.sqlplan`) and the `SET STATISTICS XML ON` route for people without SSMS. - **During a run** four stages are named one at a time: reading the plan, asking for options, Jev is scoring, writing the report. The report then paints while it is still streaming. - **The report** is OpenUI elements, never a markdown blob: the finding, Jev's first move with its confidence, plan metrics, the findings with plan evidence, a ranked options table with the weights printed, each option's action steps and any rewritten SQL, per-dimension Jev score bars, and a danger callout whenever an option could change results. - **Copy** puts the SQL (or the whole report source when there is no SQL) on the clipboard, per block and for the whole report. **Export to PDF** is the browser print path with a paper stylesheet — the dark console flips to black-on-white and the rail and buttons disappear. - **Nothing leaves the container** except the plan digest — top operators, missing indexes, warnings, waits, the statement text — sent to the user's own analyst endpoint and to TypeSafe. Raw XML stays in the local SQLite file. - **Failures are named where they happen**: not-a-plan, analyst 401/prose/too-few-options, Jev rate-limited (composite order, labelled as such), empty report. A partially stored run still opens from history and shows what it managed to produce. ## Global Constraints - Node ≥ 24 (Docker base `node:24-slim`). Verified on this machine: `node --test x.test.ts` runs TypeScript directly; `node:sqlite` `DatabaseSync` works without a flag. - Allowed runtime deps, nothing else without a one-line reason in the commit message: `next react react-dom zod @openuidev/react-lang @typesafe-ai/sdk fast-xml-parser lucide-react`. - No ORM. No auth library. No PDF library. No queue/worker. No OpenAI SDK — raw `fetch` to `/chat/completions`. - Model + Jev credentials are per-user, AES-256-GCM encrypted at rest with `APP_SECRET`, never sent to the browser (masked `••••1234`). - Jev is `@typesafe-ai/sdk` → `POST /v1/systemone`, model `jev-latest`. Jev never generates prose; it returns probabilities. Combining happens in code. - Analyst is any OpenAI-compatible base URL + key + model + extra params, from Settings. - All colour and type flows through named tokens (`var(--qai-*)`) — Hallmark gate 48: no inline hex/oklch inside components. - Dark-only console (OLED slate + phosphor green). Print/PDF flips the same tokens to paper white. - Copy = clipboard. PDF = `window.print()` + print stylesheet. No generated-file endpoints. - Mobile: 320 / 375 / 414 / 768 px, no horizontal scroll, `overflow-x: clip` on `html` and `body`. - Delete confirms first. Every mutation shows a visible result. ## Design System (locked — Hallmark + ui-ux-pro-max) Generated with `ui-ux-pro-max --design-system --variance 4 --motion 3 --density 8`; persisted at `design-system/sql-server-qai/MASTER.md`. Read that file before any UI task. Page overrides go in `design-system/sql-server-qai/pages/.md`. - Genre **atmospheric** · theme **Terminal** (mono display, phosphor accent) · macrostructure **Workbench** (app-console variant; auth/setup use **Long Document**) · nav **N3 side-rail** — the history rail *is* the nav · footer **Ft4 dense colophon** on auth/setup/settings only; the console has no footer. - Type: JetBrains Mono for display/data/labels, IBM Plex Sans for prose. No italic headings, no gradient text, no glassmorphism, no emoji icons (lucide-react SVG only). - Density 8/10: 4 px scale, 15 px body, 12 px floor. - Motion 3/10: fade + 120–220 ms `--qai-ease-out` only. `prefers-reduced-motion` → instant. - Every UI task: keep values in `tokens.css`, reference tokens by name, keep the Hallmark stamp at the top of `app/ui.css`, append the run to `.hallmark/log.json`. ## File Structure ``` tokens.css all colour/type/space/easing tokens (single source) app/globals.css tailwind import + tokens.css + ui.css app/ui.css every component class (Hallmark stamped) app/layout.tsx fonts + app/page.tsx redirect → /app or /login app/(auth)/layout.tsx centred auth shell + Ft4 colophon app/(auth)/login/page.tsx login app/(auth)/signup/page.tsx signup app/setup/page.tsx first-run guide, 3 steps app/settings/page.tsx model / Jev / account / history app/app/layout.tsx console shell: side rail + main app/app/page.tsx run surface app/app/[id]/page.tsx replay a stored report (no model calls) app/api/analyze/route.ts SSE pipeline: digest → candidates → Jev → report app/api/healthz/route.ts "ok" lib/db.ts node:sqlite + schema + all queries lib/auth.ts scrypt, sessions, requireUser lib/vault.ts AES-256-GCM seal/open/mask lib/settings.ts settings row <-> typed config, encrypt on write lib/digest.ts ShowPlan XML -> Digest (pure, no I/O) lib/analyst.ts candidates + report stream (OpenAI-compatible) lib/jev.ts Jev questions, composite weights, verdict lib/report-ui.tsx OpenUI component library + prompt lib/stream.ts SSE encode/decode + OpenAI delta extractor (pure) lib/actions.ts server actions: signup/login/logout/rename/delete/settings components/*.tsx auth-form, settings-form, runner, rail, report-view scripts/jev-gate.mjs asks Jev yes/no: does this plan match the goal? scripts/smoke.sh container smoke tests/*.test.ts node:test, no framework Dockerfile docker-compose.yml .env.example README.md ``` Pure logic lives in `lib/*.ts` so it is testable without Next. Pages and components get build + manual checks. ## Review Focus None of these is exercised by the spec's happy path. Each is pinned to a test in the named task. 1. **Not a plan.** Pasted a `.sql` script, truncated XML, or a namespaced `.sqlplan` → inline error naming what was found; never an endless spinner. *(Task 6 + Task 10)* 2. **Analyst misconfig.** Wrong base URL, 401, prose instead of JSON, or 1 candidate instead of 2–4 → error names the stage and the HTTP status; no empty report renders. *(Task 7)* 3. **Semantically unsafe rewrite.** A `SELECT TOP 100 …` that drops a predicate must never be the safe recommendation — Jev's `semantic_safety` under 0.6 forces a visible "verify results first" flag and keeps it out of the headline slot. *(Task 8)* 4. **Huge plans.** 5 MB+ XML with thousands of operators → hard 12 MB cap, digest bounded to the top operators, report says it analysed a digest. No hang, no OOM, no full-XML prompt. *(Task 6 + Task 10)* 5. **Partial failure mid-run.** Jev 429/529 or timeout after candidates exist → composite-only ranking with a `jev_unavailable` flag; the row is saved with what it has; reopening it never re-calls a model. *(Task 8 + Task 10)* --- ### Task 1: Scaffold, tokens, console shell **Files:** create via `create-next-app` in `.` (keep existing `docs/`, `design-system/`); create `tokens.css`, `app/globals.css`, `app/ui.css`, `app/layout.tsx`, `app/page.tsx`, `app/app/layout.tsx`, `app/app/page.tsx`, `app/api/healthz/route.ts`, `.env.example`, `.hallmark/log.json`; modify `package.json`, `.gitignore`. **Interfaces — produces:** tokens `--qai-{paper,paper-2,paper-3,ink,ink-muted,accent,on-accent,warn,danger,info,rule,focus}`, `--qai-font-{display,body}`, `--qai-space-{xs,s,md,l,xl,2xl,3xl}`, `--qai-text-{xs,sm,base,lg,xl,2xl,display}`, `--qai-ease-out`, `--qai-dur-{1,2}`, `--qai-radius`. Classes `.shell .rail .main .card .btn .btn-primary .field .label .pill .colophon .report .sql .bar .grid-metrics .runner .toolbar`. - [ ] **Step 1: Scaffold and install** ```bash npx create-next-app@latest . --ts --tailwind --eslint --app --no-src-dir --import-alias "@/*" --use-npm npm i zod @openuidev/react-lang @typesafe-ai/sdk fast-xml-parser lucide-react npm pkg set scripts.test="node --no-warnings --test tests/*.test.ts" git init -q && git add -A && git commit -qm "scaffold: next 16 + tailwind" ``` - [ ] **Step 2: `tokens.css`** ```css /* Hallmark · genre: atmospheric · macrostructure: Workbench · theme: Terminal * enrichment: none (typography only) · nav: N3 side-rail · footer: Ft4 dense colophon * inferred: audience = DBAs/consultants · use = analyse one plan · tone = technical-utilitarian * axes: paper band dark (L<30%) · display mono · accent phosphor green */ :root { --qai-paper: oklch(17% 0.014 255); --qai-paper-2: oklch(21% 0.015 258); --qai-paper-3: oklch(26% 0.016 260); --qai-ink: oklch(96% 0.004 250); --qai-ink-muted: oklch(72% 0.02 250); --qai-accent: oklch(80% 0.19 152); --qai-on-accent: oklch(20% 0.04 152); --qai-warn: oklch(82% 0.15 86); --qai-danger: oklch(66% 0.21 25); --qai-info: oklch(77% 0.11 232); --qai-rule: oklch(31% 0.016 258); --qai-focus: var(--qai-accent); --qai-font-display: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace; --qai-font-body: "IBM Plex Sans", system-ui, sans-serif; --qai-space-xs: 4px; --qai-space-s: 8px; --qai-space-md: 12px; --qai-space-l: 16px; --qai-space-xl: 24px; --qai-space-2xl: 32px; --qai-space-3xl: 48px; --qai-text-xs: 12px; --qai-text-sm: 13px; --qai-text-base: 15px; --qai-text-lg: 18px; --qai-text-xl: 22px; --qai-text-2xl: 28px; --qai-text-display: 34px; --qai-ease-out: cubic-bezier(0.2, 0.7, 0.2, 1); --qai-dur-1: 120ms; --qai-dur-2: 220ms; --qai-radius: 6px; } @media print { :root { --qai-paper:#fff; --qai-paper-2:#f4f5f7; --qai-paper-3:#e9ebef; --qai-ink:#14161a; --qai-ink-muted:#4a5160; --qai-rule:#c9ced8; --qai-accent:#0a7d3c; --qai-on-accent:#fff; --qai-warn:#8a5a00; --qai-danger:#a11212; --qai-info:#1a4fb8; } } ``` - [ ] **Step 3: `app/globals.css` + `app/ui.css`** `app/globals.css` — keep create-next-app's Tailwind directives at the top, then add: ```css @import "./tokens.css"; @import "./ui.css"; html, body { overflow-x: clip; } body { background: var(--qai-paper); color: var(--qai-ink); font-family: var(--qai-font-body); font-size: var(--qai-text-base); line-height: 1.5; margin: 0; } * { box-sizing: border-box; } :focus-visible { outline: 2px solid var(--qai-focus); outline-offset: 2px; } @media (prefers-reduced-motion: reduce) { * { animation-duration: .01ms !important; transition-duration: .01ms !important; } } ``` `app/ui.css` (starts with the stamp above — copy the stamp from `tokens.css` into it too): ```css .shell { display: grid; grid-template-columns: 260px minmax(0,1fr); min-height: 100dvh; } .rail { background: var(--qai-paper-2); border-inline-end: 1px solid var(--qai-rule); padding: var(--qai-space-l); display: flex; flex-direction: column; gap: var(--qai-space-md); font-family: var(--qai-font-display); min-width: 0; } .main { min-width: 0; padding: var(--qai-space-2xl); display: flex; flex-direction: column; gap: var(--qai-space-xl); } .card { background: var(--qai-paper-2); border-radius: var(--qai-radius); padding: var(--qai-space-l); min-width: 0; } .btn { font: 500 var(--qai-text-sm)/1 var(--qai-font-display); padding: 10px 14px; border-radius: var(--qai-radius); border: 1px solid var(--qai-rule); background: var(--qai-paper-3); color: var(--qai-ink); cursor: pointer; transition: background var(--qai-dur-1) var(--qai-ease-out), transform var(--qai-dur-1) var(--qai-ease-out); } .btn:hover { background: var(--qai-rule); } .btn:active { transform: translateY(1px); } .btn:disabled { opacity: .5; cursor: not-allowed; } .btn-primary { background: var(--qai-accent); color: var(--qai-on-accent); border-color: transparent; } .field { width: 100%; background: var(--qai-paper); color: var(--qai-ink); border: 1px solid var(--qai-rule); border-radius: var(--qai-radius); padding: 10px 12px; font-family: var(--qai-font-display); font-size: var(--qai-text-sm); } .label { font: 500 var(--qai-text-xs)/1 var(--qai-font-display); color: var(--qai-ink-muted); text-transform: uppercase; letter-spacing: .04em; } .pill { font: 500 var(--qai-text-xs)/1 var(--qai-font-display); padding: 4px 8px; border-radius: 999px; border: 1px solid var(--qai-rule); color: var(--qai-ink-muted); } .colophon { border-top: 1px solid var(--qai-rule); padding: var(--qai-space-l) 0; font: 400 var(--qai-text-xs)/1.6 var(--qai-font-display); color: var(--qai-ink-muted); } .report { display: flex; flex-direction: column; gap: var(--qai-space-l); min-width: 0; } .grid-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--qai-space-md); } .sql { overflow-x: auto; font-family: var(--qai-font-display); font-size: var(--qai-text-sm); white-space: pre; } .bar { height: 6px; border-radius: 999px; background: var(--qai-accent); max-width: 100%; } .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); } [data-level="danger"] { border-inline-start: 3px solid var(--qai-danger); } [data-level="warn"] { border-inline-start: 3px solid var(--qai-warn); } [data-verdict] { border-inline-start: 3px solid var(--qai-accent); } @media (max-width: 860px) { .shell { grid-template-columns: minmax(0,1fr); } .rail { border-inline-end: 0; } } @media print { .rail, .runner, .toolbar, .colophon, .no-print { display: none !important; } .shell { grid-template-columns: minmax(0,1fr); } .main { padding: 0; } } ``` - [ ] **Step 4: Layout, shell, healthz** `app/layout.tsx`: ```tsx import type { Metadata, Viewport } from "next"; import { IBM_Plex_Sans, JetBrains_Mono } from "next/font/google"; import "./globals.css"; const body = IBM_Plex_Sans({ subsets: ["latin"], weight: ["400", "500", "600"], variable: "--font-body" }); const mono = JetBrains_Mono({ subsets: ["latin"], weight: ["400", "500", "700"], variable: "--font-mono" }); export const metadata: Metadata = { title: "SQL Server qAI", description: "Query plan triage, ranked by Jev." }; export const viewport: Viewport = { width: "device-width", initialScale: 1 }; export default function Root({ children }: { children: React.ReactNode }) { return {children}; } ``` `app/api/healthz/route.ts`: ```ts export const dynamic = "force-dynamic"; export const GET = () => Response.json({ ok: true }); ``` `app/page.tsx`: ```tsx import { redirect } from "next/navigation"; import { sessionUser } from "@/lib/auth"; export default async function Home() { redirect((await sessionUser()) ? "/app" : "/login"); } ``` `app/app/layout.tsx` (rail content arrives in Task 11; the shell must render now): ```tsx export default function Console({ children }: { children: React.ReactNode }) { return
{children}
; } ``` `app/app/page.tsx`: an `

New analysis

` — replaced in Task 11. - [ ] **Step 5: Verify + commit** `npm run build && (npm run start & sleep 6; curl -s localhost:3000/api/healthz; kill %1)` → `{"ok":true}`. Create `.hallmark/log.json`: ```json [{ "date": "2026-09-21", "macrostructure": "Workbench", "theme": "Terminal", "enrichment": "none", "brief": "SQL Server qAI · plan triage console" }] ``` `.env.example`: `APP_SECRET=` and `DATA_DIR=/data`. Add `data/` and `.hallmark/` to `.gitignore` only if the repo already ignores build junk. Commit `feat: tokens, shell, healthz`. --- ### Task 2: Database layer **Files:** create `lib/db.ts`, `tests/db.test.ts`. **Interfaces — produces:** `db` (`DatabaseSync`) · `createUser(email, passHash): string` · `userByEmail(email): { id, pass } | null` · `userEmail(id): { email } | null` · `putSession(tokenHash, userId, expiresAt): void` · `sessionUserId(tokenHash): string | null` · `dropSession(tokenHash): void` · `getSettings(userId): SettingsRow | null` · `saveSettings(userId, SettingsInput): void` · `newAnalysis(userId, title, xml): string` · `patchAnalysis(id, Record): void` · `listAnalyses(userId): { id, title, status, created_at }[]` · `getAnalysis(id, userId): Analysis | null` · `renameAnalysis(id, userId, title): void` · `deleteAnalysis(id, userId): void` · types `SettingsInput`, `Analysis`. - [ ] **Step 1: Write the failing test** — `DATA_DIR` must be set before `lib/db.ts` is imported; the module opens the file at import time. ```ts import test from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; process.env.DATA_DIR = mkdtempSync(`${tmpdir()}/qai-`); const q = await import("../lib/db.ts"); test("user, settings upsert, analysis lifecycle", () => { const id = q.createUser("a@b.c", "salt:hash"); assert.equal(q.userByEmail("a@b.c")?.id, id); assert.equal(q.userByEmail("nope@x.y"), null); assert.equal(q.userEmail(id)?.email, "a@b.c"); const s = { analyst_base_url: "http://x/v1", analyst_key: "k1", analyst_model: "m", analyst_extra: "{}", jev_key: "j", jev_model: "jev-latest", onboarded: 1 }; q.saveSettings(id, s); q.saveSettings(id, { ...s, analyst_key: "k2" }); assert.equal(q.getSettings(id)?.analyst_key, "k2", "upsert not insert"); const aid = q.newAnalysis(id, "Orders scan", ""); q.patchAnalysis(aid, { status: "done", oul: "root = Report([])", digest: "{}" }); assert.equal(q.listAnalyses(id)[0].title, "Orders scan"); assert.equal(q.getAnalysis(aid, id)?.status, "done"); q.renameAnalysis(aid, id, "Renamed"); assert.equal(q.getAnalysis(aid, id)?.title, "Renamed"); q.deleteAnalysis(aid, id); assert.equal(q.getAnalysis(aid, id), null); }); test("session resolves only while unexpired", () => { const id = q.createUser("s@b.c", "h"); q.putSession("tok", id, Date.now() + 1000); assert.equal(q.sessionUserId("tok"), id); q.putSession("old", id, Date.now() - 1); assert.equal(q.sessionUserId("old"), null); q.dropSession("tok"); assert.equal(q.sessionUserId("tok"), null); }); ``` - [ ] **Step 2: Run it** — `npm run test` → cannot find module `../lib/db.ts`. - [ ] **Step 3: Implement `lib/db.ts`** ```ts import { DatabaseSync } from "node:sqlite"; import { mkdirSync } from "node:fs"; import { randomUUID } from "node:crypto"; const dir = process.env.DATA_DIR ?? "data"; mkdirSync(dir, { recursive: true }); const g = globalThis as { __qaiDb?: DatabaseSync }; export const db = (g.__qaiDb ??= (() => { const d = new DatabaseSync(`${dir}/qai.db`); d.exec(` PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, pass TEXT NOT NULL, created_at INTEGER NOT NULL); CREATE TABLE IF NOT EXISTS sessions ( token_hash TEXT PRIMARY KEY, user_id TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL); CREATE TABLE IF NOT EXISTS settings ( user_id TEXT PRIMARY KEY, analyst_base_url TEXT NOT NULL DEFAULT '', analyst_key TEXT NOT NULL DEFAULT '', analyst_model TEXT NOT NULL DEFAULT '', analyst_extra TEXT NOT NULL DEFAULT '{}', jev_key TEXT NOT NULL DEFAULT '', jev_model TEXT NOT NULL DEFAULT 'jev-latest', onboarded INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL); CREATE TABLE IF NOT EXISTS analyses ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, title TEXT NOT NULL, xml TEXT NOT NULL, digest TEXT, candidates TEXT, verdict TEXT, oul TEXT, status TEXT NOT NULL DEFAULT 'running', error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL); CREATE INDEX IF NOT EXISTS ix_analyses_user ON analyses(user_id, created_at DESC);`); return d; })()); const now = () => Date.now(); const one = (sql: string, ...p: unknown[]): T | null => (db.prepare(sql).get(...p) as T) ?? null; // node:sqlite binds null/number/string/bigint/Buffer only: pass 0/1, never booleans. export type SettingsInput = { analyst_base_url: string; analyst_key: string; analyst_model: string; analyst_extra: string; jev_key: string; jev_model: string; onboarded: number }; export type Analysis = { id: string; title: string; xml: string; digest: string | null; candidates: string | null; verdict: string | null; oul: string | null; status: string; error: string | null; created_at: number }; export function createUser(email: string, passHash: string): string { const id = randomUUID(); db.prepare("INSERT INTO users (id, email, pass, created_at) VALUES (?,?,?,?)").run(id, email, passHash, now()); return id; } export const userByEmail = (email: string) => one<{ id: string; pass: string }>("SELECT id, pass FROM users WHERE email = ?", email); export const userEmail = (id: string) => one<{ email: string }>("SELECT email FROM users WHERE id = ?", id); export function putSession(tokenHash: string, userId: string, expiresAt: number) { db.prepare("INSERT INTO sessions (token_hash, user_id, created_at, expires_at) VALUES (?,?,?,?)") .run(tokenHash, userId, now(), expiresAt); } export function sessionUserId(tokenHash: string): string | null { return one<{ user_id: string }>("SELECT user_id FROM sessions WHERE token_hash = ? AND expires_at > ?", tokenHash, now())?.user_id ?? null; } export const dropSession = (tokenHash: string) => db.prepare("DELETE FROM sessions WHERE token_hash = ?").run(tokenHash); export const getSettings = (userId: string) => one("SELECT * FROM settings WHERE user_id = ?", userId); export function saveSettings(userId: string, s: SettingsInput) { db.prepare(`INSERT INTO settings (user_id, analyst_base_url, analyst_key, analyst_model, analyst_extra, jev_key, jev_model, onboarded, updated_at) VALUES (?,?,?,?,?,?,?,?,?) ON CONFLICT(user_id) DO UPDATE SET analyst_base_url=excluded.analyst_base_url, analyst_key=excluded.analyst_key, analyst_model=excluded.analyst_model, analyst_extra=excluded.analyst_extra, jev_key=excluded.jev_key, jev_model=excluded.jev_model, onboarded=excluded.onboarded, updated_at=excluded.updated_at`) .run(userId, s.analyst_base_url, s.analyst_key, s.analyst_model, s.analyst_extra, s.jev_key, s.jev_model, s.onboarded, now()); } export function newAnalysis(userId: string, title: string, xml: string): string { const id = randomUUID(); db.prepare("INSERT INTO analyses (id, user_id, title, xml, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)") .run(id, userId, title, xml, "running", now(), now()); return id; } export function patchAnalysis(id: string, p: Record) { const keys = Object.keys(p); if (!keys.length) return; db.prepare(`UPDATE analyses SET ${keys.map((k) => `${k} = ?`).join(", ")}, updated_at = ? WHERE id = ?`) .run(...keys.map((k) => p[k]), now(), id); } export const listAnalyses = (userId: string) => db.prepare("SELECT id, title, status, created_at FROM analyses WHERE user_id = ? ORDER BY created_at DESC") .all(userId) as { id: string; title: string; status: string; created_at: number }[]; export const getAnalysis = (id: string, userId: string) => one("SELECT * FROM analyses WHERE id = ? AND user_id = ?", id, userId); export const renameAnalysis = (id: string, userId: string, title: string) => db.prepare("UPDATE analyses SET title = ?, updated_at = ? WHERE id = ? AND user_id = ?").run(title, now(), id, userId); export const deleteAnalysis = (id: string, userId: string) => db.prepare("DELETE FROM analyses WHERE id = ? AND user_id = ?").run(id, userId); ``` - [ ] **Step 4: Run tests, commit** — `npm run test` → 2 pass. Commit `feat: sqlite layer via node:sqlite`. --- ### Task 3: Auth — signup, login, db-backed sessions **Files:** create `lib/auth.ts`, `tests/auth.test.ts`, `lib/actions.ts`, `app/(auth)/layout.tsx`, `app/(auth)/login/page.tsx`, `app/(auth)/signup/page.tsx`, `components/auth-form.tsx`. **Interfaces — consumes** Task 2 queries. **Produces:** `hashPassword(pw): string` · `verifyPassword(pw, stored): boolean` · `hashToken(t): string` · `emailOk(e)` · `passwordOk(p)` · `SESSION_COOKIE = "qai_s"` · `SESSION_DAYS = 30` · `startSession(userId)` · `endSession()` · `sessionUser(): { id, email } | null` · `requireUser(): { id, email }` (redirects to `/login`) · `signUpNewUser(email, password): { ok: true; id } | { ok: false; error }` · server actions `signUp`, `signIn`, `signOut` in `lib/actions.ts`. - [ ] **Step 1: Write the failing test** ```ts import test from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; process.env.DATA_DIR = mkdtempSync(`${tmpdir()}/qai-auth-`); const { hashPassword, verifyPassword, hashToken } = await import("../lib/auth.ts"); test("password hash round-trips and rejects", () => { const h = hashPassword("correct horse battery staple"); assert.ok(h.includes(":"), "salt:hash shape"); assert.ok(verifyPassword("correct horse battery staple", h)); assert.ok(!verifyPassword("wrong", h)); }); test("malformed stored hash never verifies", () => { for (const bad of ["", "nosep", "a:", ":b", "zz:zz"]) assert.ok(!verifyPassword("x", bad)); }); test("token hash is stable, 64 hex", () => { assert.equal(hashToken("abc"), hashToken("abc")); assert.match(hashToken("abc"), /^[0-9a-f]{64}$/); }); ``` - [ ] **Step 2: Run it** — module not found. - [ ] **Step 3: Implement `lib/auth.ts`** ```ts import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { randomBytes, scryptSync, createHash, timingSafeEqual } from "node:crypto"; import { createUser, userByEmail, userEmail, putSession, sessionUserId, dropSession, getSettings } from "@/lib/db"; export const SESSION_COOKIE = "qai_s"; export const SESSION_DAYS = 30; export const emailOk = (e: string) => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(e); export const passwordOk = (p: string) => p.length >= 10; // trust boundary: signup + password change only export function hashPassword(pw: string): string { const salt = randomBytes(16); return `${salt.toString("hex")}:${scryptSync(pw, salt, 64).toString("hex")}`; } export function verifyPassword(pw: string, stored: string): boolean { const [salt, hash] = stored.split(":"); if (!salt || !hash) return false; try { const a = scryptSync(pw, Buffer.from(salt, "hex"), 64); const b = Buffer.from(hash, "hex"); return a.length === b.length && timingSafeEqual(a, b); } catch { return false; } } export const hashToken = (t: string) => createHash("sha256").update(t).digest("hex"); export function signUpNewUser(email: string, password: string): { ok: true; id: string } | { ok: false; error: string } { const e = email.trim().toLowerCase(); if (!emailOk(e)) return { ok: false, error: "Enter a valid email address." }; if (!passwordOk(password)) return { ok: false, error: "Use at least 10 characters." }; if (userByEmail(e)) return { ok: false, error: "That email already has an account." }; // ponytail: no rate limiting — single-container tool. Add a token bucket before exposing this to a network. return { ok: true, id: createUser(e, hashPassword(password)) }; } export async function startSession(userId: string) { const token = randomBytes(32).toString("hex"); putSession(hashToken(token), userId, Date.now() + SESSION_DAYS * 864e5); (await cookies()).set(SESSION_COOKIE, token, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", maxAge: SESSION_DAYS * 86400 }); } export async function endSession() { const store = await cookies(); const t = store.get(SESSION_COOKIE)?.value; if (t) dropSession(hashToken(t)); store.delete(SESSION_COOKIE); } export async function sessionUser(): Promise<{ id: string; email: string } | null> { const t = (await cookies()).get(SESSION_COOKIE)?.value; if (!t) return null; const id = sessionUserId(hashToken(t)); if (!id) return null; const row = userEmail(id); return row ? { id, email: row.email } : null; } export async function requireUser() { const u = await sessionUser(); if (!u) redirect("/login"); return u; } export const needsOnboarding = (userId: string) => getSettings(userId)?.onboarded !== 1; ``` The cookie stores a random token; the DB stores only its SHA-256, so a DB dump does not hand out live sessions. - [ ] **Step 4: Server actions + pages** `lib/actions.ts`: ```ts "use server"; import { redirect } from "next/navigation"; import { revalidatePath } from "next/cache"; import { userByEmail, getSettings, renameAnalysis, deleteAnalysis } from "@/lib/db"; import { signUpNewUser, verifyPassword, startSession, endSession, requireUser, emailOk } from "@/lib/auth"; export async function signUp(_: void, fd: FormData) { const r = signUpNewUser(String(fd.get("email") ?? ""), String(fd.get("password") ?? "")); if (!r.ok) return r; await startSession(r.id); redirect("/setup"); } export async function signIn(_: void, fd: FormData) { const e = String(fd.get("email") ?? "").trim().toLowerCase(); const u = emailOk(e) ? userByEmail(e) : null; if (!u || !verifyPassword(String(fd.get("password") ?? ""), u.pass)) return { ok: false, error: "Email or password is wrong." }; await startSession(u.id); redirect(getSettings(u.id)?.onboarded === 1 ? "/app" : "/setup"); } export async function signOut() { await endSession(); redirect("/login"); } export async function rename(_: void, fd: FormData) { const u = await requireUser(); const id = String(fd.get("id") ?? ""); const title = String(fd.get("title") ?? "").trim().slice(0, 120); if (id && title) renameAnalysis(id, u.id, title); revalidatePath("/app"); } export async function remove(_: void, fd: FormData) { const u = await requireUser(); deleteAnalysis(String(fd.get("id") ?? ""), u.id); revalidatePath("/app"); } ``` `components/auth-form.tsx` — visible labels, inline error, pending state: ```tsx "use client"; import { useActionState } from "react"; export function AuthForm({ action, submit, fields }: { action: (_: void, fd: FormData) => Promise<{ ok?: boolean; error?: string } | never>; submit: string; fields: { name: string; label: string; type: string; autoComplete?: string; help?: string }[]; }) { const [state, formAction, pending] = useActionState(action, null); return
{fields.map((f) =>
{f.help && {f.help}}
)} {state && !state.ok &&

{state.error}

}
; } ``` `app/(auth)/layout.tsx`: ```tsx export default function Auth({ children }: { children: React.ReactNode }) { return

SQL Server qAI

{children}

ShowPlan XML in · Jev-ranked remediation out · self-hosted

; } ``` `app/(auth)/login/page.tsx`: ```tsx import { signIn } from "@/lib/actions"; import { AuthForm } from "@/components/auth-form"; export default function Login() { return <>

Sign in

Create an account

; } ``` `app/(auth)/signup/page.tsx` — same shape, `action={signUp}`, `submit="Create account"`, password field `autoComplete="new-password"` and `help: "10 characters minimum."`, link to `/login`. - [ ] **Step 5: Verify + commit** `npm run test` passes; `npm run build` clean. Manual: `/signup` → lands on `/setup`; wrong password → inline alert, no redirect; sign out → cookie gone; `/app` while signed out → `/login`. Commit `feat: email+password auth with db-backed sessions`. --- ### Task 4: Credential vault + settings store **Files:** create `lib/vault.ts`, `lib/settings.ts`, `tests/vault.test.ts`. **Interfaces — produces:** `seal(plain): string` · `open(sealed): string` (`""` on any malformed input) · `mask(secret): string` · `AnalystConfig = { baseUrl, apiKey, model, extra }` · `saveSettingsForm(userId, fd, onboarded = 1)` (blank secret field keeps the stored key) · `analystConfig(userId): AnalystConfig | null` · `jevKey(userId): string` · `jevModel(userId): string` · `settingsView(userId)` returning masked/ready flags only — never a raw key. - [ ] **Step 1: Write the failing test** ```ts import test from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; process.env.APP_SECRET = "unit-test-secret-value"; process.env.DATA_DIR = mkdtempSync(`${tmpdir()}/qai-vault-`); const { seal, open, mask } = await import("../lib/vault.ts"); const db = await import("../lib/db.ts"); const { saveSettingsForm, analystConfig, settingsView, jevKey } = await import("../lib/settings.ts"); test("seal/open round-trips; junk and tamper yield empty", () => { assert.equal(open(seal("sk-abc123")), "sk-abc123"); assert.equal(open("deadbeef.00.zzz"), ""); assert.equal(open(""), ""); assert.equal(mask("sk-abcdefgh1234"), "••••1234"); assert.equal(mask(""), ""); }); test("blank secret keeps the stored key", () => { const u = db.createUser("v@x.y", "h"); const fd = (o: Record) => { const f = new FormData(); Object.entries(o).forEach(([k, v]) => f.set(k, v)); return f; }; saveSettingsForm(u, fd({ analyst_base_url: "https://api/x/v1/", analyst_key: "sk-1111", analyst_model: "gpt", analyst_extra: '{"temperature":0.2}', jev_key: "ts-1", jev_model: "jev-latest" })); assert.equal(analystConfig(u)?.apiKey, "sk-1111"); saveSettingsForm(u, fd({ analyst_base_url: "https://api/y/v1", analyst_key: "", analyst_model: "gpt", analyst_extra: "not json", jev_key: "", jev_model: "" })); const c = analystConfig(u)!; assert.equal(c.baseUrl, "https://api/y/v1", "trailing slash trimmed"); assert.equal(c.apiKey, "sk-1111", "blank must not wipe the key"); assert.deepEqual(c.extra, {}, "bad JSON falls back to empty params"); assert.equal(jevKey(u), "ts-1"); assert.equal(settingsView(u).has_analyst, true); assert.equal(settingsView(u).analyst_key_masked, "••••1111"); assert.equal((settingsView(u) as Record).analyst_key, undefined, "no raw key to the client"); }); ``` - [ ] **Step 2: Run it** — module not found. - [ ] **Step 3: Implement** `lib/vault.ts`: ```ts import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto"; function key() { const s = process.env.APP_SECRET; if (!s || s.length < 16) throw new Error("APP_SECRET is missing or shorter than 16 chars — set it in .env"); return scryptSync(s, "qai-vault-v1", 32); } export function seal(plain: string): string { if (!plain) return ""; const iv = randomBytes(12); const c = createCipheriv("aes-256-gcm", key(), iv); const ct = Buffer.concat([c.update(plain, "utf8"), c.final()]); return `${iv.toString("hex")}.${c.getAuthTag().toString("hex")}.${ct.toString("hex")}`; } export function open(sealed: string): string { if (!sealed) return ""; try { const [iv, tag, ct] = sealed.split("."); const d = createDecipheriv("aes-256-gcm", key(), Buffer.from(iv, "hex"), { authTagLength: 16 }); d.setAuthTag(Buffer.from(tag, "hex")); return Buffer.concat([d.update(Buffer.from(ct, "hex")), d.final()]).toString("utf8"); } catch { return ""; } } export const mask = (v: string) => (v ? `••••${v.slice(-4)}` : ""); ``` `lib/settings.ts`: ```ts import { getSettings, saveSettings, type SettingsInput } from "@/lib/db"; import { seal, open, mask } from "@/lib/vault"; export type AnalystConfig = { baseUrl: string; apiKey: string; model: string; extra: Record }; const str = (fd: FormData, k: string) => String(fd.get(k) ?? "").trim(); const kept = (fd: FormData, k: string, existing: string) => { const v = str(fd, k); return v ? seal(v) : existing; }; export function saveSettingsForm(userId: string, fd: FormData, onboarded = 1) { const cur = getSettings(userId); let extra = str(fd, "analyst_extra") || "{}"; try { JSON.parse(extra); } catch { extra = "{}"; } saveSettings(userId, { analyst_base_url: str(fd, "analyst_base_url").replace(/\/+$/, ""), analyst_key: kept(fd, "analyst_key", cur?.analyst_key ?? ""), analyst_model: str(fd, "analyst_model"), analyst_extra: extra, jev_key: kept(fd, "jev_key", cur?.jev_key ?? ""), jev_model: str(fd, "jev_model") || "jev-latest", onboarded: cur?.onboarded === 1 ? 1 : onboarded, } satisfies SettingsInput); } export function analystConfig(userId: string): AnalystConfig | null { const s = getSettings(userId); const baseUrl = s?.analyst_base_url ?? ""; const apiKey = open(s?.analyst_key ?? ""); const model = s?.analyst_model ?? ""; if (!baseUrl || !apiKey || !model) return null; let extra: Record = {}; try { extra = JSON.parse(s!.analyst_extra) as Record; } catch { /* bad JSON means no extra params */ } return { baseUrl, apiKey, model, extra }; } export const jevKey = (userId: string) => open(getSettings(userId)?.jev_key ?? ""); export const jevModel = (userId: string) => getSettings(userId)?.jev_model || "jev-latest"; export function settingsView(userId: string) { const s = getSettings(userId); const analystKey = open(s?.analyst_key ?? ""); const jev = open(s?.jev_key ?? ""); return { analyst_base_url: s?.analyst_base_url ?? "", analyst_model: s?.analyst_model ?? "", analyst_extra: s?.analyst_extra ?? "{}", jev_model: s?.jev_model ?? "jev-latest", analyst_key_masked: mask(analystKey), jev_key_masked: mask(jev), has_analyst: !!(s?.analyst_base_url && s?.analyst_model && analystKey), has_jev: !!jev, }; } export type SettingsView = ReturnType; ``` - [ ] **Step 4: Run tests, commit** — commit `feat: encrypted credential vault`. --- ### Task 5: First-run guide + settings page **Files:** create `components/settings-form.tsx`, `app/setup/page.tsx`, `app/settings/page.tsx`; modify `lib/actions.ts`. **Interfaces — consumes** `saveSettingsForm`, `settingsView`, `SettingsView`, `requireUser`, `getSettings`, `listAnalyses`. **Produces** `saveSettingsAction(_: void, fd): { ok: boolean; error?: string }`. - [ ] **Step 1: Shared form** — `components/settings-form.tsx`. One form, two hosts (wizard + settings). Visible labels, helper text under each field, error beside the field, never a placeholder-as-label: ```tsx "use client"; import { useActionState } from "react"; import type { SettingsView } from "@/lib/settings"; export function SettingsForm({ action, view, step, account }: { action: (_: void, fd: FormData) => Promise<{ ok?: boolean; error?: string }>; view: SettingsView; step?: string; account?: boolean; }) { const [state, formAction, pending] = useActionState(action, null); const F = (name: string, label: string, value: string, help: string, type = "text") => (
{help}
); const Group = ({ title, children }: { title: string; children: React.ReactNode }) => (
{title}{children}
); return
{step &&

{step}

} {F("analyst_base_url", "Base URL", view.analyst_base_url, "e.g. https://api.openai.com/v1")} {F("analyst_key", "API key", "", view.has_analyst ? `Saved ${view.analyst_key_masked} — leave blank to keep it` : "Encrypted with APP_SECRET before it hits disk")} {F("analyst_model", "Model name", view.analyst_model, "e.g. gpt-4o-mini, llama-3.3-70b")} {F("analyst_extra", "Extra params (JSON)", view.analyst_extra, 'Merged into the request body, e.g. {"temperature":0.2}')} {F("jev_key", "TypeSafe API key", "", view.has_jev ? `Saved ${view.jev_key_masked} — leave blank to keep it` : "console.typesafe.ai/keys")} {F("jev_model", "Jev model", view.jev_model, "jev-latest unless you know better")} {account && {F("email", "Email", "", "Used to sign in", "email")} {F("password", "New password", "", "Leave blank to keep the current one", "password")} } {state && !state.ok &&

{state.error}

} {state?.ok &&

Saved.

}
; } ``` - [ ] **Step 2: Action + pages** `lib/actions.ts` addition: ```ts import { saveSettingsForm } from "@/lib/settings"; import { db } from "@/lib/db"; import { hashPassword } from "@/lib/auth"; export async function saveSettingsAction(_: void, fd: FormData) { const u = await requireUser(); const email = String(fd.get("email") ?? "").trim().toLowerCase(); const password = String(fd.get("password") ?? ""); if (email && !emailOk(email)) return { ok: false, error: "Enter a valid email address." }; if (password && password.length < 10) return { ok: false, error: "Use at least 10 characters." }; saveSettingsForm(u.id, fd); if (password) db.prepare("UPDATE users SET pass = ? WHERE id = ?").run(hashPassword(password), u.id); if (email) db.prepare("UPDATE users SET email = ? WHERE id = ?").run(email, u.id); revalidatePath("/settings"); return { ok: true }; } ``` `app/setup/page.tsx` — 3 steps, `?step=2` deep links, visible "Step 2 of 3" (ui-ux-pro-max: multi-step needs progress): ```tsx import { redirect } from "next/navigation"; import { requireUser } from "@/lib/auth"; import { settingsView } from "@/lib/settings"; import { getSettings } from "@/lib/db"; import { saveSettingsAction } from "@/lib/actions"; import { SettingsForm } from "@/components/settings-form"; const TITLES = { 1: "Paste a plan. Get a ranked fix.", 2: "Say which models to ask.", 3: "Ready." } as const; export default async function Setup({ searchParams }: { searchParams: Promise<{ step?: string }> }) { const u = await requireUser(); const n = (["1", "2", "3"].includes((await searchParams).step ?? "") ? (await searchParams).step! : "1") as "1" | "2" | "3"; const view = settingsView(u.id); if (getSettings(u.id)?.onboarded === 1 && view.has_analyst && view.has_jev) redirect("/app"); return

Step {n} of 3

{TITLES[n]}

{n === "1" &&

qAI reads a SQL Server ShowPlan XML, works out the bottleneck in code, asks your model for several ways to fix it, then has Jev score each one. You configure nothing on this screen.

} {n === "2" && } {n === "3" && <>

One run = one digest, a few model calls, four Jev questions per option. If the analyst or Jev is unreachable, the report says so instead of inventing a verdict.

Analyst {view.has_analyst ? "ready" : "missing"} · Jev {view.has_jev ? "ready" : "missing"}

Go }

Keys stay in this container, encrypted with APP_SECRET · settings

{n !== "3" && Next}
; } ``` `app/settings/page.tsx`: ```tsx ```tsx import { requireUser } from "@/lib/auth"; import { settingsView } from "@/lib/settings"; import { listAnalyses } from "@/lib/db"; import { saveSettingsAction, signOut } from "@/lib/actions"; import { SettingsForm } from "@/components/settings-form"; export default async function Settings() { const u = await requireUser(); const rows = listAnalyses(u.id); return

Settings

History · {rows.length}

Rename or delete individual runs from the left rail. Nothing here deletes them in bulk — that is on purpose.

; } ``` - [ ] **Step 3: Verify + commit** `npm run build`. Manual: fresh signup lands on `/setup` step 1 → step 2 saves both models → step 3 `Go` → `/app`. Reload `/settings`: keys show as `••••`, blank fields keep the stored values. Commit `feat: setup wizard + settings`. --- ### Task 6: ShowPlan digest parser **Files:** create `lib/digest.ts`, `tests/digest.test.ts`. **Interfaces — consumes** nothing. **Produces:** `class PlanError` · `MAX_XML_BYTES = 12_000_000` · `type Operator = { id: string; op: string; cost: number; estRows: number; actualRows: number | null; execs: number | null; io: number | null; cpu: number | null; spillLevels: number | null; parallel: boolean }` · `type RowGuess = { id: string; op: string; est: number; actual: number; ratio: number }` · `type MissingIndex = { table: string; equality: string[]; inequality: string[]; included: string[]; impact: number }` · `type Wait = { type: string; ms: number }` · `type Digest = { sql: string; bytes: number; subtreeCost: number | null; parallel: boolean; nonParallelReason: string | null; earlyAbort: string | null; tables: string[]; topOperators: Operator[]; rowGuessErrors: RowGuess[]; missingIndexes: MissingIndex[]; warnings: string[]; waits: Wait[] }` · `digestPlan(xml: string): Digest` · `digestForModel(d: Digest): object` — the only shape any model or Jev ever sees. - [ ] **Step 1: Write the failing test** ```ts import test from "node:test"; import assert from "node:assert/strict"; const { digestPlan, PlanError, digestForModel } = await import("../lib/digest.ts"); const PLAN = ` GoodEnoughPlanFound Could Not Generate Valid Parallel Plan `; test("digests what matters in a showplan", () => { const d = digestPlan(PLAN); assert.match(d.sql, /o\.Total > 500/, "XML entities decoded"); assert.equal(d.subtreeCost, 4.871); assert.equal(d.earlyAbort, "GoodEnoughPlanFound"); assert.equal(d.nonParallelReason, "Could Not Generate Valid Parallel Plan"); assert.deepEqual(d.tables, ["wide.Sales.Orders", "wide.Sales.Customer"]); assert.equal(d.topOperators[0].op, "Clustered Index Scan"); assert.equal(d.topOperators[0].spillLevels, 2); assert.equal(d.topOperators[0].actualRows, 1243500); assert.equal(d.topOperators[0].parallel, false); assert.deepEqual(d.missingIndexes[0], { table: "wide.Sales.Orders", equality: ["CustomerId"], inequality: ["Total"], included: ["Id"], impact: 94.17 }); assert.ok(d.warnings.includes("NoJoinPredicate")); assert.ok(d.warnings.some((w) => w.startsWith("SpillToTempDb"))); assert.deepEqual(d.waits, [{ type: "PAGEIOLATCH_SH", ms: 812 }]); assert.ok(d.rowGuessErrors[0].ratio > 100); assert.equal(d.bytes, PLAN.length); }); test("a BOM-prefixed plan still digests — SSMS writes .sqlplan as UTF-16 with a BOM", () => { assert.equal(digestPlan("\uFEFF" + PLAN).subtreeCost, 4.871); }); test("rejects input that is not a plan", () => { for (const bad of ["", "SELECT 1", "", " digestPlan(bad), PlanError); }); test("digestForModel stays bounded on a fat plan", () => { const big = `` + Array.from({ length: 400 }, (_, i) => ``).join("") + ``; const v = digestForModel(digestPlan(big)) as { topOperators: unknown[]; tables: unknown[] }; assert.ok(v.topOperators.length <= 12); }); ``` - [ ] **Step 2: Run it** — cannot find module `../lib/digest.ts`. - [ ] **Step 3: Implement `lib/digest.ts`** ```ts import { XMLParser } from "fast-xml-parser"; export class PlanError extends Error {} export const MAX_XML_BYTES = 12_000_000; const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "", removeNSPrefix: true }); export type Operator = { id: string; op: string; cost: number; estRows: number; actualRows: number | null; execs: number | null; io: number | null; cpu: number | null; spillLevels: number | null; parallel: boolean }; export type RowGuess = { id: string; op: string; est: number; actual: number; ratio: number }; export type MissingIndex = { table: string; equality: string[]; inequality: string[]; included: string[]; impact: number }; export type Wait = { type: string; ms: number }; export type Digest = { sql: string; bytes: number; subtreeCost: number | null; parallel: boolean; nonParallelReason: string | null; earlyAbort: string | null; tables: string[]; topOperators: Operator[]; rowGuessErrors: RowGuess[]; missingIndexes: MissingIndex[]; warnings: string[]; waits: Wait[] }; function collect(node: any, name: string, out: any[] = []): any[] { if (Array.isArray(node)) { for (const n of node) collect(n, name, out); return out; } if (node && typeof node === "object") for (const [k, v] of Object.entries(node)) { if (k === "#text") continue; if (k === name) (Array.isArray(v) ? v : [v]).forEach((n) => out.push(n)); collect(v, name, out); } return out; } const num = (v: any): number | null => (v === undefined || v === null || v === "" || Number.isNaN(Number(v)) ? null : Number(v)); const text = (v: any): string | null => (typeof v === "object" && v !== null ? (String(v["#text"] ?? "") || null) : (v == null ? null : String(v))); const unbrack = (v: any) => String(v ?? "").replace(/[[\]]/g, ""); const colNames = (node: any) => collect(node, "Column").map((c) => unbrack(c.Name)).filter(Boolean); export function digestPlan(raw: string): Digest { const xml = raw.replace(/^\uFEFF/, ""); // SSMS writes a BOM; the XML parser does not want it if (!xml || xml.trim().length < 40) throw new PlanError("Empty input. Paste the ShowPlan XML, or drop a .sqlplan file."); if (xml.length > MAX_XML_BYTES) throw new PlanError(`Plan is ${Math.round(xml.length / 1e6)} MB, over the ${MAX_XML_BYTES / 1e6} MB cap.`); let doc: any; try { doc = parser.parse(xml); } catch { throw new PlanError("That XML will not parse — re-export the actual execution plan."); } if (!doc?.ShowPlanXML) throw new PlanError("Not a SQL Server ShowPlan XML document. In SSMS: Results → Save As → .sqlplan, or run the query with ATTNPL."); const stmts = collect(doc, "StmtSimple"); const stmt = stmts.find((s) => s?.QueryBlock || s?.RelOp) ?? stmts[0] ?? {}; const operators: Operator[] = collect(doc, "RelOp").map((o) => { const rt = collect(o, "RunTimeCountersPerThread")[0]; return { id: String(o.NodeId ?? o["Node_id"] ?? "?"), op: String(o.LogicalOp ?? o.PhysicalOp ?? "Unknown"), // Real ShowPlan spells these EstimateIO / EstimateCPU; builds differ, so fall through every spelling. cost: num(o.EstimatedOperatorCost) ?? num(o.EstimatedTotalSubtreeCost) ?? num(o.EstimateIO) ?? 0, estRows: num(o.EstimateRows) ?? 0, actualRows: rt ? num(rt.ActualRows) : null, execs: rt ? num(rt.ActualExecs) : null, io: num(o.EstimateIO) ?? num(o.EstimatedIO), cpu: num(o.EstimateCPU) ?? num(o.EstimatedCPU), spillLevels: num(collect(o, "SpillToTempDb")[0]?.SpillLevel), parallel: o.Parallel === "true", }; }); const tables = [...new Set([...collect(doc, "IndexScan"), ...collect(doc, "TableScan")] .map((s) => unbrack(s.Table)).filter(Boolean))]; const missingIndexes: MissingIndex[] = collect(doc, "MissingIndexGroup").map((g) => { const mi = collect(g, "MissingIndex")[0] ?? {}; const byUsage = (u: string) => collect(mi, "ColumnGroup").filter((c) => c.Usage === u).flatMap(colNames); return { table: [mi.Database, mi.Schema, mi.Table].map(unbrack).filter(Boolean).join("."), equality: byUsage("EQUALITY"), inequality: byUsage("INEQUALITY"), included: collect(mi, "IncludedColumns").flatMap(colNames), impact: num(g.Impact) ?? 0, }; }).sort((a, b) => b.impact - a.impact); // Generic on purpose: SQL Server adds warning elements over time, unknown ones still surface. const warnings = [...new Set(collect(doc, "Warnings").flatMap((w) => Object.entries(w).filter(([k]) => k !== "#text").map(([k, v]: [string, any]) => { const attrs = v && typeof v === "object" ? Object.entries(v).filter(([, x]) => typeof x !== "object").map(([ak, x]) => `${ak}=${x}`).join(" ") : ""; return attrs ? `${k} ${attrs}` : k; })))]; const waits: Wait[] = collect(doc, "WaitTime") .map((w) => ({ type: String(w.WaitType ?? "unknown"), ms: num(w.WaitTime) ?? 0 })) .filter((w) => w.ms > 0).sort((a, b) => b.ms - a.ms).slice(0, 5); const rowGuessErrors: RowGuess[] = operators .filter((o) => o.actualRows !== null && o.estRows > 0) .map((o) => ({ id: o.id, op: o.op, est: o.estRows, actual: o.actualRows as number, ratio: Math.max(o.estRows, o.actualRows as number) / Math.min(o.estRows, o.actualRows as number) })) .filter((r) => r.ratio >= 10).sort((a, b) => b.ratio - a.ratio).slice(0, 5); return { sql: text(stmt.StatementText) ?? "", bytes: xml.length, subtreeCost: num(stmt.StatementSubtreeCost), parallel: operators.some((o) => o.parallel), nonParallelReason: text(collect(doc, "NonParallelPlanReason")[0]), earlyAbort: text(collect(doc, "StatementOptmEarlyAbortReason")[0]), tables, topOperators: operators.sort((a, b) => b.cost - a.cost).slice(0, 12), rowGuessErrors, missingIndexes, warnings, waits, }; } // Tokens are the constraint: models and Jev read this, never the raw XML. export const digestForModel = (d: Digest) => ({ sql: d.sql.slice(0, 4000), subtreeCost: d.subtreeCost, parallel: d.parallel, nonParallelReason: d.nonParallelReason, earlyAbort: d.earlyAbort, tables: d.tables.slice(0, 20), topOperators: d.topOperators, rowGuessErrors: d.rowGuessErrors, missingIndexes: d.missingIndexes.slice(0, 5), warnings: d.warnings.slice(0, 20), waits: d.waits, }); ``` - [ ] **Step 4: Prove it on a real plan, not just the fixture** `mkdir -p fixtures`, drop one genuine `.sqlplan` from SSMS in as `fixtures/real.sqlplan`, and add this test. It asserts nothing about specific numbers — only that a real plan yields a non-empty digest. If no real plan is available, leave the test skipped and say so in the commit message: ```ts test("a real .sqlplan digests", () => { let xml: string; try { xml = readFileSync("fixtures/real.sqlplan", "utf8"); } catch { return console.log("skip: no fixtures/real.sqlplan"); } const d = digestPlan(xml); assert.ok(d.topOperators.length > 0, "no operators found — check attribute spellings"); assert.ok(d.sql.length > 0); }); ``` Add `import { readFileSync } from "node:fs";` to the test file. - [ ] **Step 5: Run tests, commit** If the entity assertion fails on your `fast-xml-parser` version, decode in `digestPlan`: wrap `text(stmt.StatementText)` with `.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&")`, then re-run. Commit `feat: showplan digest`. --- ### Task 7: Analyst — multiple options, multiple rewrites **Files:** create `lib/analyst.ts`, `tests/analyst.test.ts`. **Interfaces — consumes** `AnalystConfig` (Task 4), `Digest` + `digestForModel` (Task 6). **Produces:** `class AnalystError` · `type Action = { title: string; detail: string; effort: "low" | "medium" | "high"; requires_change_control: boolean }` · `type Candidate = { key: string; title: string; diagnosis: string; actions: Action[]; rewrite_sql: string | null; expected: string }` · `extractJson(text): unknown` · `proposeCandidates(cfg, digest, note, fetchImpl?): Promise` (2–4 items, schema-validated) · `CANDIDATE_SYSTEM_PROMPT`. - [ ] **Step 1: Write the failing test** ```ts import test from "node:test"; import assert from "node:assert/strict"; const { proposeCandidates, extractJson, AnalystError } = await import("../lib/analyst.ts"); const cfg = { baseUrl: "https://api.test/v1", apiKey: "k", model: "m", extra: {} }; const digest = { sql: "SELECT 1", bytes: 8, subtreeCost: 1, parallel: false, nonParallelReason: null, earlyAbort: null, tables: ["t"], topOperators: [], rowGuessErrors: [], missingIndexes: [], warnings: [], waits: [] } as any; const ok = (content: string) => ({ ok: true, status: 200, json: async () => ({ choices: [{ message: { content } }] }) }); const CANDS = { candidates: [ { key: "idx_customer", title: "Covering index", diagnosis: "Scan of 1.2M rows on Sales.Orders, no key on CustomerId.", actions: [{ title: "Create index", detail: "CREATE INDEX ...", effort: "medium", requires_change_control: true }], rewrite_sql: null, expected: "seek instead of scan" }, { key: "rewrite_split", title: "Split the statement", diagnosis: "One statement scans everything then filters.", actions: [{ title: "Deploy rewrite", detail: "Two statements", effort: "high", requires_change_control: true }], rewrite_sql: "SELECT ...", expected: "far fewer logical reads" }] }; test("returns schema-valid candidates", async () => { const got = await proposeCandidates(cfg, digest, "", async () => ok(JSON.stringify(CANDS)) as any); assert.equal(got.length, 2); assert.equal(got[0].key, "idx_customer"); }); test("survives code fences and sends extra params", async () => { let sent: any; const fetchImpl = async (_u: any, init: any) => { sent = JSON.parse(init.body); return ok("```json\n" + JSON.stringify(CANDS) + "\n```") as any; }; const got = await proposeCandidates({ ...cfg, extra: { temperature: 0.1 } }, digest, "index exists already", fetchImpl as any); assert.equal(got.length, 2); assert.equal(sent.temperature, 0.1); assert.match(sent.messages[0].content, /candidates/); assert.match(sent.messages[1].content, /index exists already/); }); test("names the stage on auth, prose, and shape failures", async () => { await assert.rejects(() => proposeCandidates(cfg, digest, "", async () => ({ ok: false, status: 401, text: async () => "bad key" }) as any), (e: any) => e instanceof AnalystError && /401/.test(e.message)); await assert.rejects(() => proposeCandidates(cfg, digest, "", async () => ok("no json here") as any), AnalystError); await assert.rejects(() => proposeCandidates(cfg, digest, "", async () => ok(JSON.stringify({ candidates: [CANDS.candidates[0]] })) as any), (e: any) => /at least 2/.test(e.message)); }); test("extractJson copes with fences and chatter", () => { assert.deepEqual(extractJson('Sure!\n```json\n{"a":1}\n```'), { a: 1 }); assert.deepEqual(extractJson('noise {"a":1} noise'), { a: 1 }); }); ``` - [ ] **Step 2: Run it** — module not found. - [ ] **Step 3: Implement `lib/analyst.ts`** ```ts import { z } from "zod/v4"; import type { Digest } from "@/lib/digest"; import { digestForModel } from "@/lib/digest"; import type { AnalystConfig } from "@/lib/settings"; export class AnalystError extends Error {} const Action = z.object({ title: z.string().min(3), detail: z.string().min(3), effort: z.enum(["low", "medium", "high"]), requires_change_control: z.boolean() }); const RawCandidate = z.object({ key: z.string().min(2), title: z.string().min(3), diagnosis: z.string().min(15), actions: z.array(Action).min(1), rewrite_sql: z.string().nullable(), expected: z.string().min(5) }); const Proposal = z.object({ candidates: z.array(RawCandidate).min(2).max(4) }); export type Action = z.infer; export type Candidate = z.infer; export const CANDIDATE_SYSTEM_PROMPT = `You are a SQL Server performance engineer. You receive a JSON digest of one execution plan. Judge only what the digest contains — never invent operators, waits, or row counts you were not given. Return 2 to 4 genuinely different remediation options, not one option restated. Real differences: an index change, a rewritten query, a statistics or parallelism fix, a batching or schema change. At least one option must be the lowest-risk one. Each option needs a snake_case key, a diagnosis naming WHY the plan is slow, 1-4 ordered actions, an optional rewritten query, and an expected effect stated as a concrete claim about the plan (logical reads, seek vs scan, tempdb spill, degree of parallelism). A rewrite must return the same rows as the original. If you cannot rewrite safely, set rewrite_sql to null. Output ONE JSON object and nothing else: {"candidates":[{"key":"idx_covering_orders","title":"...","diagnosis":"...","actions":[{"title":"...","detail":"...", "effort":"low|medium|high","requires_change_control":false}],"rewrite_sql":null,"expected":"..."}]}`; export function extractJson(text: string): unknown { const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text); const s = (fenced ? fenced[1] : text).trim(); const start = s.indexOf("{"); const end = s.lastIndexOf("}"); if (start === -1 || end <= start) throw new AnalystError("The analyst model returned prose instead of JSON."); try { return JSON.parse(s.slice(start, end + 1)); } catch { throw new AnalystError("The analyst model returned malformed JSON."); } } export async function proposeCandidates(cfg: AnalystConfig, digest: Digest, note: string, fetchImpl: typeof fetch = fetch): Promise { const res = await fetchImpl(`${cfg.baseUrl}/chat/completions`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${cfg.apiKey}` }, body: JSON.stringify({ model: cfg.model, temperature: 0.6, ...cfg.extra, stream: false, messages: [{ role: "system", content: CANDIDATE_SYSTEM_PROMPT }, { role: "user", content: JSON.stringify({ digest: digestForModel(digest), user_note: note || null }) }] }), }).catch((e) => { throw new AnalystError(`Could not reach ${cfg.baseUrl}: ${(e as Error).message}`); }); if (!res.ok) throw new AnalystError(`Analyst model returned HTTP ${res.status}. ${(await res.text()).slice(0, 300)}`); const content: string = ((await res.json() as any)?.choices?.[0]?.message?.content) ?? ""; const parsed = Proposal.safeParse(extractJson(content)); if (!parsed.success) { const n = (extractJson(content) as any)?.candidates?.length ?? 0; throw new AnalystError(n < 2 ? `The analyst model proposed ${n} option(s); at least 2 are needed before Jev can rank anything.` : `Analyst output did not match the schema at ${parsed.error.issues[0].path.join(".")}: ${parsed.error.issues[0].message}`); } return parsed.data.candidates; } ``` No `response_format` is sent: plenty of OpenAI-compatible endpoints 400 on it, and `extractJson` already handles the fenced/prose cases it would have prevented. - [ ] **Step 4: Run tests, commit** — commit `feat: analyst proposes multiple options`. --- ### Task 8: Jev — score, rank, decide **Files:** create `lib/jev.ts`, `tests/jev.test.ts`. **Interfaces — consumes** `Digest` + `digestForModel` (Task 6), `Candidate` (Task 7), `@typesafe-ai/sdk`. **Produces:** `makeJevClient(apiKey: string, model?: string): TypeSafeClient` · `type Dim = "bottleneck_fit" | "semantic_safety" | "ease" | "root_cause"` · `const WEIGHTS: Record` (sums to 1) · `type Ranked = { key: string; dims: Record; composite: number; flags: string[] }` · `type Verdict = { order: Ranked[]; headline: string; source: "jev" | "composite"; jev_pick: string | null; jev_confidence: number; agrees: boolean; anything_worth_running: number; weights: Record; flags: string[] }` · `judgeCandidates(digest, candidates, client): Promise` · `jevFallback(candidates, why): Verdict`. Question design follows TypeSafe's composite-scoring pattern: one narrow judgment per question, four questions in a single per-candidate call (the API answers them in parallel), then a second call that compares all candidates side by side. Every rubric is worded so **higher is better** — nothing gets inverted in code. Weights are a starting opinion, not a discovery: 0.40 bottleneck_fit / 0.30 semantic_safety / 0.15 ease / 0.15 root_cause. They are shipped inside the `Verdict` so the UI can print them, and they are the only tuning knob in the product. A DBA who disagrees with the ranking changes four numbers in `lib/jev.ts` — that is the intended edit point, so state it in the README rather than building a weights UI. - [ ] **Step 1: Write the failing test** ```ts import test from "node:test"; import assert from "node:assert/strict"; const { judgeCandidates, WEIGHTS, jevFallback } = await import("../lib/jev.ts"); const { TypeSafeClient } = await import("@typesafe-ai/sdk"); const digest = { sql: "SELECT", bytes: 1, subtreeCost: 1, parallel: false, nonParallelReason: null, earlyAbort: null, tables: [], rowGuessErrors: [], missingIndexes: [], warnings: [], waits: [], topOperators: [{ id: "0", op: "Clustered Index Scan", cost: 4.1, estRows: 1000, actualRows: 1200000, execs: 1, io: 40, cpu: 39, spillLevels: 2, parallel: false }] } as any; const candidates = [ { key: "a", title: "Covering index", diagnosis: "Scan of a million rows; no key exists on CustomerId.", actions: [{ title: "Create index", detail: "CREATE INDEX", effort: "medium", requires_change_control: true }], rewrite_sql: null, expected: "seek instead of scan" }, { key: "b", title: "Add TOP 100", diagnosis: "Too many rows leave the scan, so cut them off.", actions: [{ title: "Add TOP 100", detail: "TOP 100", effort: "low", requires_change_control: false }], rewrite_sql: "SELECT TOP 100", expected: "less I/O" }, ] as any; const scored = (fit: number, safe: number, ease: number, cause: number) => ({ bottleneck_fit: { type: "score", score: fit, confidence: 0.9, legend: {}, probabilities: {} }, semantic_safety: { type: "score", score: safe, confidence: 0.8, legend: {}, probabilities: {} }, ease: { type: "score", score: ease, confidence: 0.7, legend: {}, probabilities: {} }, root_cause: { type: "noul", noul: cause } }); // The SDK's client takes an injectable fetch — no network in tests. function fakeJev(map: any) { return new TypeSafeClient({ apiKey: "test", fetch: (async (_u: any, init: any) => { const body = JSON.parse(init.body); const answers = body.questions.first_to_run ? map.cross : map.byKey[body.state.candidate.key]; return new Response(JSON.stringify({ model: "jev-1.13.0", usage: { input_tokens: 1, output_tokens: 1 }, answers }), { status: 200, headers: { "content-type": "application/json" } }); }) as any, retry: { maxRetries: 0 } }); } const GOOD = { byKey: { a: scored(4, 4, 2, 1), b: scored(0.4, 0.5, 4, 0.1) }, cross: { first_to_run: { type: "choice", choice: "a", confidence: 0.82, probabilities: { a: 0.82, b: 0.18 } }, anything_worth_running: { type: "noul", noul: 0.9 } } }; test("weights sum to 1", () => assert.equal(Object.values(WEIGHTS).reduce((a, b) => a + b, 0), 1)); test("jev picks the winner; the unsafe option is flagged and cannot lead", async () => { const v = await judgeCandidates(digest, candidates, fakeJev(GOOD)); assert.equal(v.source, "jev"); assert.equal(v.headline, "a"); assert.equal(v.order[0].key, "a"); assert.equal(v.agrees, true); assert.equal(v.jev_confidence, 0.82); assert.ok(v.order.find((r) => r.key === "b")!.flags.includes("verify_semantics")); assert.ok(v.order.find((r) => r.key === "a")!.flags.includes("change_control")); assert.ok(v.order.find((r) => r.key === "a")!.dims.bottleneck_fit.value === 1); }); test("a low-confidence jev pick defers to composite order", async () => { const low = { byKey: GOOD.byKey, cross: { first_to_run: { type: "choice", choice: "b", confidence: 0.2, probabilities: { a: 0.4, b: 0.6 } }, anything_worth_running: { type: "noul", noul: 0.4 } } }; const v = await judgeCandidates(digest, candidates, fakeJev(low)); assert.equal(v.source, "composite"); assert.equal(v.headline, "a"); assert.equal(v.agrees, false); assert.ok(v.flags.includes("low_confidence")); assert.ok(v.flags.includes("nothing_clearly_worthwhile")); }); test("an unknown jev choice label falls back to composite order", async () => { const bogus = { byKey: GOOD.byKey, cross: { first_to_run: { type: "choice", choice: "zzz", confidence: 0.99, probabilities: { zzz: 0.99 } }, anything_worth_running: { type: "noul", noul: 0.9 } } }; const v = await judgeCandidates(digest, candidates, fakeJev(bogus)); assert.equal(v.source, "composite"); assert.equal(v.headline, "a"); assert.equal(v.jev_pick, null); }); test("fallback ranks without Jev and says so", () => { const v = jevFallback(candidates, "429 rate limited"); assert.equal(v.source, "composite"); assert.ok(v.flags.includes("jev_unavailable")); assert.equal(v.order.length, 2); }); ``` - [ ] **Step 2: Run it** — module not found. - [ ] **Step 3: Implement `lib/jev.ts`** ```ts import { TypeSafeClient, score, noul, choice } from "@typesafe-ai/sdk"; import type { Digest } from "@/lib/digest"; import { digestForModel } from "@/lib/digest"; import type { Candidate } from "@/lib/analyst"; export type Dim = "bottleneck_fit" | "semantic_safety" | "ease" | "root_cause"; export const WEIGHTS: Record = { bottleneck_fit: 0.4, semantic_safety: 0.3, ease: 0.15, root_cause: 0.15 }; const SAFETY_FLOOR = 0.6; const CONFIDENCE_FLOOR = 0.35; const BOTTLENECK = [ "Unrelated to the costly operator or the worst estimate error in the plan.", "Touches a minor operator; the dominant cost is untouched.", "Partly addresses the dominant cost; a scan or spill probably remains.", "Directly removes the dominant cost for this query shape.", "Removes the dominant cost and the pattern behind it, so the shape itself changes.", ]; const SAFETY = [ "Changes results: a predicate, join, grouping, or NULL handling is altered.", "Likely changes results in edge cases: duplicates, ordering, tie-breaking.", "Equivalent on most data but leans on unstated assumptions.", "Clearly equivalent, with a stated reason.", "Equivalent and checkable: ships a comparison or rollback plan.", ]; const EASE = [ "Needs a schema plus application redesign and a release.", "Needs an application deploy with change control on the app tier.", "DBA work with approval, e.g. an index create during a window.", "One rewritten query or session setting, reviewable in minutes.", "Copy-paste change with no server-side change at all.", ]; export type Ranked = { key: string; dims: Record; composite: number; flags: string[] }; export type Verdict = { order: Ranked[]; headline: string; source: "jev" | "composite"; jev_pick: string | null; jev_confidence: number; agrees: boolean; anything_worth_running: number; weights: Record; flags: string[] }; export const makeJevClient = (apiKey: string, model = "jev-latest") => new TypeSafeClient({ apiKey, defaultModel: model, timeout: 20_000 }); const norm = (s: number, levels: number) => Math.max(0, Math.min(1, s / (levels - 1))); function rank(c: Candidate, a: any): Ranked { const dims = { bottleneck_fit: { value: norm(a.bottleneck_fit.score, BOTTLENECK.length), confidence: a.bottleneck_fit.confidence }, semantic_safety: { value: norm(a.semantic_safety.score, SAFETY.length), confidence: a.semantic_safety.confidence }, ease: { value: norm(a.ease.score, EASE.length), confidence: a.ease.confidence }, root_cause: { value: a.root_cause.noul, confidence: null }, // noul reports no confidence } as Record; const composite = (Object.keys(WEIGHTS) as Dim[]).reduce((s, k) => s + WEIGHTS[k] * dims[k].value, 0); const confs = Object.values(dims).map((d) => d.confidence).filter((x): x is number => x !== null); const flags: string[] = []; if (dims.semantic_safety.value < SAFETY_FLOOR) flags.push("verify_semantics"); if (confs.length && Math.min(...confs) < CONFIDENCE_FLOOR) flags.push("low_confidence"); if (c.actions.some((x) => x.requires_change_control)) flags.push("change_control"); return { key: c.key, dims, composite: Number(composite.toFixed(4)), flags }; } export async function judgeCandidates(digest: Digest, candidates: Candidate[], client: TypeSafeClient): Promise { const plan = digestForModel(digest); const per = await Promise.all(candidates.map((c) => client.systemOne({ state: { plan, candidate: c }, questions: { bottleneck_fit: score("How directly do the actions in `candidate` attack the dominant bottleneck in `plan` — the most expensive operator and the worst estimate error?", BOTTLENECK), semantic_safety: score("If `candidate` is applied, how certain is it that the query still returns exactly the same rows as the original?", SAFETY), ease: score("How easy is `candidate` to apply in a real shop, counting people, approvals, and downtime?", EASE), root_cause: noul("The diagnosis in `candidate` names the actual cause of the cost in `plan`, not a symptom of it."), }, }))); const cross = await client.systemOne({ state: { plan, options: candidates.map((c) => ({ key: c.key, title: c.title, diagnosis: c.diagnosis, expected: c.expected })) }, questions: { first_to_run: choice("Which single entry in `options` should the DBA run first for the biggest drop in the cost shown in `plan`?", Object.fromEntries(candidates.map((c) => [c.key, `${c.title} — ${c.expected}`]))), anything_worth_running: noul("At least one entry in `options` would meaningfully reduce the cost shown in `plan`."), }, }); const order = candidates.map((c, i) => rank(c, (per[i] as any).answers)).sort((a, b) => b.composite - a.composite); const pick = (cross as any).answers.first_to_run; const worth = (cross as any).answers.anything_worth_running.noul; const valid = candidates.some((c) => c.key === pick.choice); const useJev = valid && pick.confidence >= 0.5; // confidence-gated routing: act only when Jev is sure enough const flags: string[] = []; if (!useJev) flags.push(pick.confidence < CONFIDENCE_FLOOR ? "low_confidence" : "composite_order"); if (worth < 0.5) flags.push("nothing_clearly_worthwhile"); return { order, source: useJev ? "jev" : "composite", headline: useJev ? pick.choice : order[0].key, jev_pick: valid ? pick.choice : null, jev_confidence: pick.confidence, agrees: valid && pick.choice === order[0].key, anything_worth_running: worth, weights: WEIGHTS, flags }; } // Neutral answers: Jev never spoke, so nothing is claimed about safety or fit. The UI must say so. export function jevFallback(candidates: Candidate[], why: string): Verdict { const order = candidates.map((c) => rank(c, { bottleneck_fit: { score: 0, confidence: null }, semantic_safety: { score: 0, confidence: null }, ease: { score: 0, confidence: null }, root_cause: { noul: 0 } })).sort((a, b) => b.composite - a.composite); return { order, source: "composite", headline: order[0]?.key ?? "", jev_pick: null, jev_confidence: 0, agrees: false, anything_worth_running: 0, weights: WEIGHTS, flags: ["jev_unavailable", why.slice(0, 200)] }; } ``` - [ ] **Step 4: Run tests, commit** — `npm run test` → all suites green. Commit `feat: jev composite scoring and verdict`. --- ### Task 9: OpenUI report library (rendered output, not markdown) **Files:** create `lib/report-ui.tsx`, `tests/report-ui.test.ts`. **Interfaces — consumes** `@openuidev/react-lang`, `zod/v4`. **Produces:** `library` (`createLibrary`, `root: "Report"`), `reportPromptOptions` (`preamble`, `additionalRules`, `examples`), and the components `Headline Metric MetricGrid Finding Findings Step StepList SqlBlock ScoreBar OptionRow OptionTable VerdictBlock Callout Report`. The same module is imported by the route (for `library.prompt()`) and by the client (for ``), so the prompt and the renderer can never drift apart. Composition uses `.ref` (`z.array(Child.ref)`) and `renderNode(children)` — children are declared before their container, root last. - [ ] **Step 1: Write the failing test** ```ts import test from "node:test"; import assert from "node:assert/strict"; const { library, reportPromptOptions } = await import("../lib/report-ui.tsx"); test("the prompt advertises every component and the root", () => { const p = library.prompt(reportPromptOptions); for (const n of ["Report", "Headline", "VerdictBlock", "MetricGrid", "Findings", "OptionTable", "StepList", "SqlBlock", "ScoreBar", "Callout"]) assert.ok(p.includes(n), `missing ${n}`); assert.match(p, /root/i); assert.match(p, /never invent/i); }); ``` - [ ] **Step 2: Run it** — module not found. - [ ] **Step 3: Implement `lib/report-ui.tsx`** ```tsx import { defineComponent, createLibrary } from "@openuidev/react-lang"; import { z } from "zod/v4"; const Headline = defineComponent({ name: "Headline", description: "Report title: the one-line finding, with severity.", props: z.object({ title: z.string(), subtitle: z.string().optional(), severity: z.enum(["critical", "warning", "info"]).optional() }), component: ({ props }) =>

{props.severity === "critical" ? "Critical" : props.severity === "warning" ? "Warning" : "Note"}

{props.title}

{props.subtitle &&

{props.subtitle}

}
}); const Metric = defineComponent({ name: "Metric", description: "One plan metric.", props: z.object({ label: z.string(), value: z.string(), note: z.string().optional() }), component: ({ props }) =>

{props.label}

{props.value}

{props.note &&

{props.note}

}
}); const MetricGrid = defineComponent({ name: "MetricGrid", description: "Row of plan metrics.", props: z.object({ items: z.array(Metric.ref) }), component: ({ props, renderNode }) =>
{renderNode(props.items)}
}); const Finding = defineComponent({ name: "Finding", description: "One problem, the plan evidence for it, its impact.", props: z.object({ where: z.string(), issue: z.string(), evidence: z.string(), impact: z.string() }), component: ({ props }) =>

{props.where}

{props.issue}

{props.evidence}

{props.impact}

}); const Findings = defineComponent({ name: "Findings", description: "Everything wrong with the plan.", props: z.object({ items: z.array(Finding.ref) }), component: ({ props, renderNode }) =>
{renderNode(props.items)}
}); const Step = defineComponent({ name: "Step", description: "One action step.", props: z.object({ text: z.string(), effort: z.enum(["low", "medium", "high"]).optional(), owner: z.enum(["dba", "developer", "both"]).optional(), changeControl: z.boolean().optional() }), component: ({ props }) =>
  • {props.text} {props.effort && effort {props.effort}} {props.owner && {props.owner}} {props.changeControl && change control}
  • }); const StepList = defineComponent({ name: "StepList", description: "Ordered action plan for one option.", props: z.object({ title: z.string(), steps: z.array(Step.ref) }), component: ({ props, renderNode }) =>

    {props.title}

      {renderNode(props.steps)}
    }); const SqlBlock = defineComponent({ name: "SqlBlock", description: "A SQL snippet the user can copy.", props: z.object({ title: z.string(), sql: z.string(), caption: z.string().optional() }), component: ({ props }) =>
    {props.title}
    {props.sql}
    {props.caption &&

    {props.caption}

    }
    }); const ScoreBar = defineComponent({ name: "ScoreBar", description: "One Jev dimension: normalised value plus confidence.", props: z.object({ label: z.string(), value: z.number(), confidence: z.number().optional(), note: z.string().optional() }), component: ({ props }) =>

    {props.label}{props.confidence !== undefined ? ` · confidence ${props.confidence.toFixed(2)}` : " · no confidence reported"}

    {props.note &&

    {props.note}

    }
    }); const OptionRow = defineComponent({ name: "OptionRow", description: "One ranked option.", props: z.object({ rank: z.number(), title: z.string(), composite: z.number(), headline: z.boolean(), flags: z.string().optional() }), component: ({ props }) => {props.rank}{props.title} {props.composite.toFixed(2)} {props.flags || "—"} }); const OptionTable = defineComponent({ name: "OptionTable", description: "All options ranked by composite score.", props: z.object({ title: z.string(), weights: z.string(), rows: z.array(OptionRow.ref) }), component: ({ props, renderNode }) =>

    {props.title}

    {props.weights}

    {renderNode(props.rows)}
    }); const VerdictBlock = defineComponent({ name: "VerdictBlock", description: "What Jev decided, and how sure it was.", props: z.object({ headline: z.string(), why: z.string(), source: z.enum(["jev", "composite"]), confidence: z.number() }), component: ({ props }) =>

    First move · {props.source === "jev" ? `Jev, confidence ${props.confidence.toFixed(2)}` : "composite order — Jev did not decide"}

    {props.headline}

    {props.why}

    }); const Callout = defineComponent({ name: "Callout", description: "A warning or note the user must not miss.", props: z.object({ level: z.enum(["info", "warn", "danger", "success"]), title: z.string(), text: z.string() }), component: ({ props }) =>

    {props.title}

    {props.text}

    }); const Report = defineComponent({ name: "Report", description: "Root. A query-plan report in reading order.", props: z.object({ blocks: z.array(z.union([Headline.ref, VerdictBlock.ref, MetricGrid.ref, Findings.ref, OptionTable.ref, StepList.ref, SqlBlock.ref, ScoreBar.ref, Callout.ref])) }), component: ({ props, renderNode }) =>
    {renderNode(props.blocks)}
    }); export const library = createLibrary({ root: "Report", components: [Headline, Metric, MetricGrid, Finding, Findings, Step, StepList, SqlBlock, ScoreBar, OptionRow, OptionTable, VerdictBlock, Callout, Report] }); export const reportPromptOptions = { preamble: "You output only OpenUI Lang. No prose, no markdown fences, no commentary before or after.", additionalRules: [ "root = Report([...]) and it must list every block you define.", "Block order: Headline, VerdictBlock, MetricGrid, Findings, OptionTable, then per option its StepList and SqlBlock, then ScoreBars, then Callout.", "Use ONLY numbers present in the input. Never invent a cost, row count, impact, or confidence.", "One SqlBlock per rewrite, titled with that option's key. T-SQL only, no markdown.", "Every option flagged verify_semantics gets a Callout level danger: compare result rows before deploying.", "If verdict.source is composite, say plainly that Jev did not decide.", ], examples: [`root = Report([head, verdict]) head = Headline("Clustered Index Scan on Sales.Orders is 84% of the plan", "No usable key on CustomerId", "critical") verdict = VerdictBlock("Create the covering index", "bottleneck_fit 1.00, semantic_safety 1.00, ease 0.50.", "jev", 0.82)`], }; ``` - [ ] **Step 4: Verify + commit** — `npm run test` green; `npm run build` compiles the `.tsx` on both server and client boundaries. Commit `feat: openui report library`. --- ### Task 10: The pipeline — `POST /api/analyze` as a server-sent event stream **Files:** create `lib/stream.ts`, `app/api/analyze/route.ts`, `tests/stream.test.ts`; modify `lib/analyst.ts` (add `streamReport` + `REPORT_SYSTEM_PROMPT`). **Interfaces — consumes** `requireUser`, `analystConfig`, `jevKey`, `jevModel`, `digestPlan`, `PlanError`, `digestForModel`, `proposeCandidates`, `judgeCandidates`, `makeJevClient`, `jevFallback`, `library`, `reportPromptOptions`, `newAnalysis`, `patchAnalysis`. **Produces:** `sse(event, data): string` · `readSse(body): AsyncGenerator<{ event: string; data: any }>` · `openAiTextDeltas(res): AsyncGenerator` · `streamReport(cfg, payload, fetchImpl?): AsyncGenerator` · `REPORT_SYSTEM_PROMPT(openuiPrompt: string): string`. **Wire format (server → client):** `stage {stage}` (`digesting` | `proposing` | `judging` | `writing`) · `candidate {key,title}` · `verdict {Verdict}` · `oul {chunk}` · `done {id}` · `error {stage, message}`. - [ ] **Step 1: Write the failing test for the pure stream helpers** ```ts import test from "node:test"; import assert from "node:assert/strict"; const { sse, readSse, openAiTextDeltas } = await import("../lib/stream.ts"); const streamOf = (parts: string[]) => new ReadableStream({ start(c) { parts.forEach((p) => c.enqueue(new TextEncoder().encode(p))); c.close(); } }); test("sse frames one event", () => assert.equal(sse("stage", { stage: "digesting" }), 'event: stage\ndata: {"stage":"digesting"}\n\n')); test("readSse rejoins frames split anywhere", async () => { const raw = sse("stage", { stage: "digesting" }) + sse("oul", { chunk: "root = Report([])" }); const events: any[] = []; for await (const e of readSse(streamOf([raw.slice(0, 12), raw.slice(12, 40), raw.slice(40)]))) events.push(e); assert.deepEqual(events, [{ event: "stage", data: { stage: "digesting" } }, { event: "oul", data: { chunk: "root = Report([])" } }]); }); test("readSse ignores a keep-alive frame it cannot parse", async () => { const events: any[] = []; for await (const e of readSse(streamOf([": ping\n\n", sse("done", { id: "1" })]))) events.push(e); assert.deepEqual(events, [{ event: "message", data: undefined } satisfies any].slice(1)); }); test("openAiTextDeltas yields content and stops at [DONE]", async () => { const res = new Response(streamOf(['data: {"choices":[{"delta":{"content":"ab"}}]}\n\n', 'data: {"choices":[{"delta":{"content":"cd"}}]}\n\n', "data: [DONE]\n\n"]), { status: 200 }); const out: string[] = []; for await (const d of openAiTextDeltas(res)) out.push(d); assert.equal(out.join(""), "abcd"); }); ``` - [ ] **Step 2: Run it** — module not found. - [ ] **Step 3: Implement `lib/stream.ts`** ```ts export const sse = (event: string, data: unknown) => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; export async function* readSse(body: ReadableStream): AsyncGenerator<{ event: string; data: any }> { const reader = body.getReader(); const dec = new TextDecoder(); let buf = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buf += dec.decode(value, { stream: true }); let at: number; while ((at = buf.indexOf("\n\n")) !== -1) { const frame = buf.slice(0, at); buf = buf.slice(at + 2); const event = /^event: (.+)$/m.exec(frame)?.[1] ?? "message"; const raw = /^data: (.*)$/m.exec(frame)?.[1] ?? ""; try { yield { event, data: JSON.parse(raw) }; } catch { /* keep-alive or unparsable frame */ } } } } export async function* openAiTextDeltas(res: Response): AsyncGenerator { const reader = res.body!.getReader(); const dec = new TextDecoder(); let buf = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buf += dec.decode(value, { stream: true }); let at: number; while ((at = buf.indexOf("\n\n")) !== -1) { const line = (buf.slice(0, at).split("\n").find((l) => l.startsWith("data:")) ?? "").slice(5).trim(); buf = buf.slice(at + 2); if (!line) continue; if (line === "[DONE]") return; let delta: unknown; try { delta = JSON.parse(line)?.choices?.[0]?.delta?.content; } catch { continue; } if (typeof delta === "string" && delta) yield delta; } } } ``` Delete the third test's odd `satisfies` line and use this instead — a keep-alive frame yields nothing: ```ts test("readSse ignores frames it cannot parse", async () => { const events: any[] = []; for await (const e of readSse(streamOf([": ping\n\n", sse("done", { id: "1" })]))) events.push(e); assert.deepEqual(events, [{ event: "done", data: { id: "1" } }]); }); ``` Append to `lib/analyst.ts`: ```ts export const REPORT_SYSTEM_PROMPT = (openuiPrompt: string) => `${openuiPrompt} You are writing the final answer for a DBA holding one SQL Server execution plan. The ranking you are given was computed by Jev from typed judgments. It is not your opinion: do not re-rank it, soften it, or hide it. Quote each option's scores as given. A rewrite must return the same rows as the original — when in doubt keep the original text and say why. Never invent a number that is not in the input.`; export async function* streamReport(cfg: AnalystConfig, payload: unknown, openuiPrompt: string, fetchImpl: typeof fetch = fetch): AsyncGenerator { const res = await fetchImpl(`${cfg.baseUrl}/chat/completions`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${cfg.apiKey}` }, body: JSON.stringify({ model: cfg.model, temperature: 0.3, ...cfg.extra, stream: true, messages: [{ role: "system", content: REPORT_SYSTEM_PROMPT(openuiPrompt) }, { role: "user", content: JSON.stringify(payload) }] }), }).catch((e) => { throw new AnalystError(`Could not reach ${cfg.baseUrl}: ${(e as Error).message}`); }); if (!res.ok || !res.body) throw new AnalystError(`Analyst model returned HTTP ${res.status} while writing the report.`); yield* openAiTextDeltas(res); } ``` Add the import at the top of `lib/analyst.ts`: `import { openAiTextDeltas } from "@/lib/stream";`. - [ ] **Step 4: Implement `app/api/analyze/route.ts`** The whole run is one stream. Every stage is announced, every failure is named with its stage, and the row is saved with whatever it managed to produce. ```ts import { requireUser } from "@/lib/auth"; import { analystConfig, jevKey, jevModel } from "@/lib/settings"; import { digestPlan, digestForModel, PlanError } from "@/lib/digest"; import { proposeCandidates, streamReport, AnalystError } from "@/lib/analyst"; import { judgeCandidates, jevFallback, makeJevClient } from "@/lib/jev"; import { sse } from "@/lib/stream"; import { newAnalysis, patchAnalysis } from "@/lib/db"; import { library, reportPromptOptions } from "@/lib/report-ui"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; const MAX_BODY_BYTES = 16_000_000; export async function POST(req: Request) { const user = await requireUser().catch(() => null); if (!user) return Response.json({ error: "Sign in first." }, { status: 401 }); const analyst = analystConfig(user.id); const jev = jevKey(user.id); if (!analyst) return Response.json({ error: "Set the analyst model in Settings first." }, { status: 412 }); if (!jev) return Response.json({ error: "Set your Jev (TypeSafe) API key in Settings first." }, { status: 412 }); const raw = await req.text(); if (raw.length > MAX_BODY_BYTES) return Response.json({ error: "Request too large." }, { status: 413 }); let xml = ""; let note = ""; try { const body = JSON.parse(raw); xml = String(body.xml ?? ""); note = String(body.note ?? "").slice(0, 500); } catch { return Response.json({ error: "Expected JSON with an xml field." }, { status: 400 }); } const id = newAnalysis(user.id, note.trim().slice(0, 120) || "Plan analysis", xml); const enc = new TextEncoder(); const prompt = library.prompt(reportPromptOptions); const stream = new ReadableStream({ async start(ctrl) { const send = (event: string, data: unknown) => ctrl.enqueue(enc.encode(sse(event, data))); let oul = ""; const close = (status: string, error?: string) => { patchAnalysis(id, { status, ...(error ? { error } : {}), ...(oul ? { oul } : {}) }); send("done", { id }); ctrl.close(); }; try { send("stage", { stage: "digesting" }); let digest; try { digest = digestPlan(xml); } catch (e) { const m = e instanceof PlanError ? e.message : "Could not read that plan."; send("error", { stage: "digesting", message: m }); patchAnalysis(id, { status: "failed", error: m }); send("done", { id }); return ctrl.close(); } patchAnalysis(id, { digest: JSON.stringify(digest) }); send("stage", { stage: "proposing" }); let candidates; try { candidates = await proposeCandidates(analyst, digest, note); } catch (e) { const m = e instanceof AnalystError ? e.message : "The analyst model call failed."; send("error", { stage: "proposing", message: m }); patchAnalysis(id, { status: "failed", error: m }); send("done", { id }); return ctrl.close(); } candidates.forEach((c) => send("candidate", { key: c.key, title: c.title })); patchAnalysis(id, { candidates: JSON.stringify(candidates) }); send("stage", { stage: "judging" }); let verdict; try { verdict = await judgeCandidates(digest, candidates, makeJevClient(jev, jevModel(user.id))); } catch (e) { verdict = jevFallback(candidates, `Jev call failed: ${(e as Error).message}`); } send("verdict", verdict); patchAnalysis(id, { verdict: JSON.stringify(verdict) }); send("stage", { stage: "writing" }); for await (const chunk of streamReport(analyst, { plan: digestForModel(digest), options: candidates, verdict }, prompt)) { oul += chunk; send("oul", { chunk }); } close(oul ? "done" : "partial", oul ? undefined : "The analyst model returned an empty report."); } catch (e) { const m = (e as Error)?.message ?? "Analysis failed."; send("error", { stage: "stream", message: m }); patchAnalysis(id, { status: "failed", error: m, ...(oul ? { oul } : {}) }); send("done", { id }); ctrl.close(); } } }); return new Response(stream, { headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-store", connection: "keep-alive", "x-accel-buffering": "no" } }); } ``` No `cancel()` handler: the default is right. When the DBA closes the tab, the producer's next `send` throws, the `catch` stores the partial row, and that is the behaviour we want. - [ ] **Step 5: Verify + commit** `npm run test` green. Manual, with `npm run dev`, a signed-in browser session, and real keys: ```bash curl -sN -X POST localhost:3000/api/analyze -H 'content-type: application/json' \ --cookie "qai_s=" \ -d '{"xml":"","note":"slow after the migration"}' | head -20 ``` Expected: `event: stage` frames, then `candidate`, `verdict`, a run of `oul` chunks, then `done`. Then prove Review-Focus 1 and 5: post `{"xml":"SELECT 1"}` and confirm exactly one `error` frame with `stage: digesting` and a `done` frame, never a hanging stream; and post a valid plan with the Jev key invalidated to confirm a `verdict` with `source: "composite"` and the `jev_unavailable` flag. Commit `feat: analysis pipeline with staged SSE`. --- ### Task 11: Console UI — rail, run, live render, rename, delete, copy, PDF **Files:** create `app/app/page.tsx` (replace), `app/app/[id]/page.tsx`, `components/rail.tsx`, `components/history-item.tsx`, `components/runner.tsx`, `components/report-view.tsx`, `components/export-buttons.tsx`. **Interfaces — consumes** `library`, `readSse`, `listAnalyses`, `getAnalysis`, `rename`, `remove`, `digestForModel`. **Produces:** the `/app` run surface and `/app/[id]` replay. - [ ] **Step 1: Rail + history rows (server components)** `components/history-item.tsx` — inline rename, delete behind a confirm: ```tsx import { rename, remove } from "@/lib/actions"; export function HistoryItem({ row }: { row: { id: string; title: string; status: string; created_at: number } }) { const when = new Date(row.created_at).toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }); return
  • {row.title}{row.status !== "done" && {row.status}}
    {when}
    { if (!confirm("Delete this analysis?")) e.preventDefault(); }}>
  • ; } ``` `onSubmit` with `confirm` needs a client boundary — put `HistoryItem` in a file starting with `"use client";` and import `confirm` from `window` (it is a browser global; no import needed). Delete is the only destructive action in the product: it must never fire on a stray click. `components/rail.tsx`: ```tsx import { listAnalyses } from "@/lib/db"; import { HistoryItem } from "./history-item"; export async function Rail({ userId }: { userId: string }) { const rows = listAnalyses(userId); return <> qAI

    History · {rows.length}

      {rows.map((r) => )}

    Settings

    ; } ``` `app/app/layout.tsx` — replace the placeholder rail: ```tsx import { requireUser } from "@/lib/auth"; import { Rail } from "@/components/rail"; export default async function Console({ children }: { children: React.ReactNode }) { const u = await requireUser(); return
    {children}
    ; } ``` - [ ] **Step 2: Runner + live report** `components/report-view.tsx`: ```tsx "use client"; import { Renderer } from "@openuidev/react-lang"; import { library } from "@/lib/report-ui"; export function ReportView({ oul, streaming }: { oul: string; streaming: boolean }) { if (!oul) return null; return ; } ``` `components/export-buttons.tsx` — Copy takes the OpenUI Lang source plus the SQL blocks it contains; PDF is the browser's own print path with a stylesheet that flips tokens to paper: ```tsx "use client"; export function ExportButtons({ oul, title }: { oul: string; title: string }) { const copy = async () => { const sql = [...document.querySelectorAll("#report pre.sql")].map((p) => p.textContent).join("\n\n"); await navigator.clipboard.writeText(`${title}\n\n${sql || oul}`); }; return
    ; } ``` `components/runner.tsx` — stage indicator then token-by-token render (ui-ux-pro-max: no spinner for 30 seconds, always show progress): ```tsx "use client"; import { useState } from "react"; import { readSse } from "@/lib/stream"; import { ReportView } from "./report-view"; import { ExportButtons } from "./export-buttons"; const STAGES = ["digesting", "proposing", "judging", "writing"] as const; const LABEL: Record = { digesting: "Reading the plan", proposing: "Asking for options", judging: "Jev is scoring", writing: "Writing the report" }; export function Runner() { const [xml, setXml] = useState(""); const [note, setNote] = useState(""); const [stage, setStage] = useState(null); const [oul, setOul] = useState(""); const [error, setError] = useState(null); const at = STAGES.indexOf(stage ?? ""); async function run() { setError(null); setOul(""); setStage("digesting"); const res = await fetch("/api/analyze", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ xml, note }) }); if (!res.ok || !res.body) { setError((await res.json().catch(() => null))?.error ?? `Request failed (${res.status}).`); return setStage(null); } for await (const e of readSse(res.body)) { if (e.event === "stage") setStage(e.data.stage); else if (e.event === "oul") setOul((s) => s + e.data.chunk); else if (e.event === "error") setError(`${e.data.stage}: ${e.data.message}`); else if (e.event === "done") { setStage(null); history.replaceState(null, "", `/app/${e.data.id}`); } } } return

    Paste a plan

    Where do I get this XML?
    1. SSMS: press Ctrl+M (Include Actual Execution Plan), then run the query.
    2. On the Execution Plan tab, right-click → Save As .sqlplan. Or right-click the statement → Showplan XML and copy the cell.
    3. No SSMS: prefix the batch with SET STATISTICS XML ON; and copy the XML result cell.
    4. Drop the file or paste its contents below. Only the digest leaves this container, and only to your own models.