feat: analyst proposes multiple options; README with gitea remote

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ahmed mohammed
2026-09-22 07:44:01 +03:00
co-authored by Claude Opus 4.7
parent 4fefd0f6da
commit 816f22c227
3 changed files with 189 additions and 21 deletions
+45
View File
@@ -0,0 +1,45 @@
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 });
});