Fix MVP audit evidence pipeline
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
buildAuditEvidenceInput,
|
||||
type SkillRegistryEntryEvidence,
|
||||
} from "../lib/ai/audit-evidence";
|
||||
import { LOCAL_AUDIT_SKILL_REGISTRY_SOURCE } from "../lib/ai/local-audit-skill-registry";
|
||||
import { parseSkillsRegistry } from "../lib/skills-registry";
|
||||
|
||||
const SAMPLE_SKILL_REGISTRY: SkillRegistryEntryEvidence[] = [
|
||||
@@ -340,11 +339,7 @@ test("buildAuditEvidenceInput selects deterministic skills and supports design/u
|
||||
});
|
||||
|
||||
test("buildAuditEvidenceInput prioritizes local-audit v3 skills before cap", () => {
|
||||
const source = readFileSync(
|
||||
join(process.cwd(), "v2_elemente", "skills.md"),
|
||||
"utf8",
|
||||
);
|
||||
const skillRegistry = parseSkillsRegistry(source);
|
||||
const skillRegistry = parseSkillsRegistry(LOCAL_AUDIT_SKILL_REGISTRY_SOURCE);
|
||||
|
||||
assert.equal(
|
||||
skillRegistry.some((skill) => skill.id === "visual-design" && !skill.category),
|
||||
@@ -448,11 +443,7 @@ test("buildAuditEvidenceInput prioritizes local-audit v3 skills before cap", ()
|
||||
});
|
||||
|
||||
test("buildAuditEvidenceInput gates v3 skills when declared inputs are missing", () => {
|
||||
const source = readFileSync(
|
||||
join(process.cwd(), "v2_elemente", "skills.md"),
|
||||
"utf8",
|
||||
);
|
||||
const skillRegistry = parseSkillsRegistry(source);
|
||||
const skillRegistry = parseSkillsRegistry(LOCAL_AUDIT_SKILL_REGISTRY_SOURCE);
|
||||
|
||||
const actual = buildAuditEvidenceInput({
|
||||
lead: {
|
||||
|
||||
@@ -182,16 +182,21 @@ test("action calls generateObject with required schemas", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("action loads v3 skill registry from v2 source for evidence input", () => {
|
||||
test("action loads v3 skill registry from bundled MVP source for evidence input", () => {
|
||||
assert.equal(
|
||||
hasPattern(actionSource, /import\s*{[\s\S]*loadSkillsRegistry[\s\S]*}\s*from\s*["']\.\.\/lib\/skills-registry["']/),
|
||||
hasPattern(actionSource, /import\s*{[\s\S]*loadLocalAuditSkillRegistry[\s\S]*}\s*from\s*["']\.\.\/lib\/ai\/local-audit-skill-registry["']/),
|
||||
true,
|
||||
"Action should import loadSkillsRegistry from the shared registry parser.",
|
||||
"Action should import the bundled MVP skill registry loader.",
|
||||
);
|
||||
assert.equal(
|
||||
hasPattern(actionSource, /loadSkillsRegistry\(\s*(?:join\()?[\s\S]*v2_elemente[\s\S]*skills\.md[\s\S]*\)/),
|
||||
hasPattern(actionSource, /loadLocalAuditSkillRegistry\(\s*\)/),
|
||||
true,
|
||||
"Action should load the v3 registry from v2_elemente/skills.md.",
|
||||
"Action should load the v3 registry from a bundled MVP module.",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
actionSource,
|
||||
/v2_elemente|process\.cwd\(\)|loadSkillsRegistry\(|node:path/,
|
||||
"Action should not read v2 reference files or filesystem paths at runtime.",
|
||||
);
|
||||
assert.equal(
|
||||
hasPattern(actionSource, /skillRegistry:\s*\[\s*\]/),
|
||||
|
||||
@@ -224,6 +224,50 @@ test("persistAuditGenerationResult inserts into auditGenerations", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getAuditGenerationEvidence loads latest successful website enrichment evidence by lead", () => {
|
||||
const evidenceSource = extractExportSource("getAuditGenerationEvidence");
|
||||
|
||||
assert.equal(
|
||||
hasPattern(
|
||||
evidenceSource,
|
||||
/query\("agentRuns"\)[\s\S]*withIndex\("by_type_and_status_and_leadId"[\s\S]*eq\("type",\s*"website_enrichment"\)[\s\S]*eq\("status",\s*"succeeded"\)[\s\S]*eq\("leadId",\s*lead\._id\)[\s\S]*order\("desc"\)[\s\S]*take\(1\)/,
|
||||
),
|
||||
true,
|
||||
"Evidence query should locate the latest successful website_enrichment run for the same lead.",
|
||||
);
|
||||
assert.equal(
|
||||
hasPattern(
|
||||
evidenceSource,
|
||||
/const\s+enrichmentEvidenceRunId\s*=\s*latestSuccessfulEnrichmentRun\[0\]\?\._id\s*\?\?\s*args\.runId/,
|
||||
),
|
||||
true,
|
||||
"Evidence query should fall back to the audit run only when no enrichment run exists.",
|
||||
);
|
||||
for (const table of [
|
||||
"websiteCrawlPages",
|
||||
"websiteTechnicalChecks",
|
||||
]) {
|
||||
assert.equal(
|
||||
hasPattern(
|
||||
evidenceSource,
|
||||
new RegExp(
|
||||
`query\\("${table}"\\)[\\s\\S]*withIndex\\("by_runId"[\\s\\S]*eq\\("runId",\\s*enrichmentEvidenceRunId\\)`,
|
||||
),
|
||||
),
|
||||
true,
|
||||
`${table} should be loaded from the enrichment evidence run.`,
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
hasPattern(
|
||||
evidenceSource,
|
||||
/const\s+screenshots\s*=\s*\[\s*\.\.\.auditCaptureScreenshotsByRun,\s*\.\.\.enrichmentScreenshotsByRun\s*\]/,
|
||||
),
|
||||
true,
|
||||
"Evidence query should include audit-run ScreenshotOne captures and enrichment screenshots.",
|
||||
);
|
||||
});
|
||||
|
||||
test("truncateWithMarker is byte-capped and marker-safe in persistence", () => {
|
||||
assert.equal(
|
||||
hasPattern(auditGenerationSource, /const markerBytes = byteLength\(TRUNCATION_MARKER\);/),
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import { LOCAL_AUDIT_SKILL_REGISTRY_SOURCE } from "../lib/ai/local-audit-skill-registry";
|
||||
import { parseSkillsRegistry, toAuditUsedSkill } from "../lib/skills-registry";
|
||||
|
||||
test("parseSkillsRegistry parses v3 yaml metablocks from v2 source", async () => {
|
||||
const source = await readFile(join(process.cwd(), "v2_elemente", "skills.md"), "utf8");
|
||||
|
||||
const parsed = parseSkillsRegistry(source);
|
||||
test("parseSkillsRegistry parses v3 yaml metablocks from the MVP registry source", () => {
|
||||
const parsed = parseSkillsRegistry(LOCAL_AUDIT_SKILL_REGISTRY_SOURCE);
|
||||
|
||||
assert.equal(parsed.length, 9);
|
||||
const visualDesign = parsed.find((entry) => entry.id === "visual-design");
|
||||
@@ -28,9 +25,8 @@ test("parseSkillsRegistry parses v3 yaml metablocks from v2 source", async () =>
|
||||
assert.match(instructions, /Beurteile den ersten visuellen Eindruck/);
|
||||
});
|
||||
|
||||
test("toAuditUsedSkill exposes stable ids for v3 registry entries", async () => {
|
||||
const source = await readFile(join(process.cwd(), "v2_elemente", "skills.md"), "utf8");
|
||||
const parsed = parseSkillsRegistry(source);
|
||||
test("toAuditUsedSkill exposes stable ids for v3 registry entries", () => {
|
||||
const parsed = parseSkillsRegistry(LOCAL_AUDIT_SKILL_REGISTRY_SOURCE);
|
||||
const skill = parsed.find((entry) => entry.id === "contact-conversion");
|
||||
|
||||
assert.ok(skill);
|
||||
@@ -40,9 +36,8 @@ test("toAuditUsedSkill exposes stable ids for v3 registry entries", async () =>
|
||||
});
|
||||
});
|
||||
|
||||
test("parseSkillsRegistry does not infer categories for v3 entries without explicit metadata", async () => {
|
||||
const source = await readFile(join(process.cwd(), "v2_elemente", "skills.md"), "utf8");
|
||||
const parsed = parseSkillsRegistry(source);
|
||||
test("parseSkillsRegistry does not infer categories for v3 entries without explicit metadata", () => {
|
||||
const parsed = parseSkillsRegistry(LOCAL_AUDIT_SKILL_REGISTRY_SOURCE);
|
||||
const skill = parsed.find((entry) => entry.id === "performance-experience");
|
||||
|
||||
assert.ok(skill);
|
||||
|
||||
@@ -225,8 +225,8 @@ test("audits.getDetail returns audit + lead context with null-safe lead lookup",
|
||||
);
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
/return\s*{\s*audit,\s*lead\s*}/,
|
||||
"getDetail should return { audit, lead }.",
|
||||
/return\s*{[\s\S]*audit,[\s\S]*lead,[\s\S]*sourceSummaries:[\s\S]*}/,
|
||||
"getDetail should return audit, lead, and sourceSummaries.",
|
||||
);
|
||||
hasPattern(
|
||||
sourceFile.getFullText(),
|
||||
@@ -234,3 +234,48 @@ test("audits.getDetail returns audit + lead context with null-safe lead lookup",
|
||||
"audits.ts should export a getDetail query.",
|
||||
);
|
||||
});
|
||||
|
||||
test("audits.getDetail joins compact checked-page evidence from latest successful enrichment", () => {
|
||||
const getDetailSource = extractExportSource("getDetail");
|
||||
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
/query\("agentRuns"\)[\s\S]*withIndex\("by_type_and_status_and_leadId"[\s\S]*eq\("type",\s*"website_enrichment"\)[\s\S]*eq\("status",\s*"succeeded"\)[\s\S]*eq\("leadId",\s*audit\.leadId\)[\s\S]*order\("desc"\)[\s\S]*take\(1\)/,
|
||||
"getDetail should locate the latest successful website_enrichment run for the audit lead.",
|
||||
);
|
||||
|
||||
for (const table of [
|
||||
"websiteCrawlPages",
|
||||
"websiteTechnicalChecks",
|
||||
"websiteCrawlScreenshots",
|
||||
]) {
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
new RegExp(
|
||||
`query\\("${table}"\\)[\\s\\S]*withIndex\\("by_runId"[\\s\\S]*eq\\("runId",\\s*enrichmentRunId\\)[\\s\\S]*take\\(DETAIL_EVIDENCE_LIMIT\\)`,
|
||||
),
|
||||
`${table} should be loaded from the bounded enrichment run evidence window.`,
|
||||
);
|
||||
}
|
||||
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
/audit\.checkedPages\.map\(/,
|
||||
"getDetail should preserve audit.checkedPages as the canonical display order.",
|
||||
);
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
/fallbackCheckedPageEvidence/,
|
||||
"getDetail should return checked-page fallback rows when enrichment evidence is missing.",
|
||||
);
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
/ctx\.storage\.getUrl\(screenshot\.storageId\)/,
|
||||
"getDetail should resolve screenshot storage ids to display URLs.",
|
||||
);
|
||||
hasPattern(
|
||||
getDetailSource,
|
||||
/sourceSummaries:\s*{\s*checkedPages/,
|
||||
"getDetail should expose checked page summaries under sourceSummaries.checkedPages.",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -141,6 +141,53 @@ test("audit detail component uses getDetail query and renders skills overview se
|
||||
);
|
||||
});
|
||||
|
||||
test("audit detail component renders compact checked-page evidence", async () => {
|
||||
const detailSource = await source("components/audits/audit-detail.tsx");
|
||||
|
||||
assert.match(
|
||||
detailSource,
|
||||
/sourceSummaries/,
|
||||
"AuditDetail should read sourceSummaries from getDetail.",
|
||||
);
|
||||
assert.match(
|
||||
detailSource,
|
||||
/checkedPageEvidence/,
|
||||
"AuditDetail should derive checked page evidence from sourceSummaries.checkedPages.",
|
||||
);
|
||||
assert.match(
|
||||
detailSource,
|
||||
/Geprüfte Seiten/,
|
||||
"AuditDetail should render a checked-pages evidence card.",
|
||||
);
|
||||
assert.match(
|
||||
detailSource,
|
||||
/checkedPageEvidence\.map/,
|
||||
"AuditDetail should render one compact row per checked page.",
|
||||
);
|
||||
for (const label of [
|
||||
"Meta",
|
||||
"Kontaktformular",
|
||||
"CTA",
|
||||
"Interne Links",
|
||||
]) {
|
||||
assert.match(
|
||||
detailSource,
|
||||
new RegExp(label),
|
||||
`AuditDetail should expose ${label} evidence for each page.`,
|
||||
);
|
||||
}
|
||||
assert.match(
|
||||
detailSource,
|
||||
/page\.screenshots\.map/,
|
||||
"AuditDetail should render optional screenshot thumbnails when present.",
|
||||
);
|
||||
assert.match(
|
||||
detailSource,
|
||||
/<img[\s\S]*src=\{screenshot\.url\}/,
|
||||
"AuditDetail should render screenshot URLs from the detail query.",
|
||||
);
|
||||
});
|
||||
|
||||
test("audits detail route passes id to AuditDetail via Promise params", async () => {
|
||||
const pageSource = await source("app/dashboard/audits/[id]/page.tsx");
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ test("settings page surfaces integration status instead of a placeholder", () =>
|
||||
assert.doesNotMatch(helperSource, /requiredEnv: \["TASK8_BROWSER_ASSET_URL"\]/);
|
||||
assert.match(helperSource, /requiredEnv: \["SCREENSHOTONE_API_KEY"\]/);
|
||||
assert.match(helperSource, /requiredEnv: \[\]/);
|
||||
assert.match(componentSource, /Next\.js-Runtime/);
|
||||
assert.match(componentSource, /Convex-Action-Env/);
|
||||
assert.match(helperSource, /Convex-Run-Events/);
|
||||
});
|
||||
|
||||
test("verification notes cover critical MVP flows", () => {
|
||||
|
||||
Reference in New Issue
Block a user