feat: analyst proposes multiple options; README with gitea remote
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4fefd0f6da
commit
816f22c227
@@ -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 });
|
||||
});
|
||||
Reference in New Issue
Block a user