Improve audit pipeline and outreach review

This commit is contained in:
2026-06-08 22:16:32 +02:00
parent ff18fc202e
commit 1695110e0a
34 changed files with 2792 additions and 238 deletions

View File

@@ -5,6 +5,15 @@ import test from "node:test";
const actionPath = path.join(process.cwd(), "convex", "auditGenerationAction.ts");
const actionSource = existsSync(actionPath) ? readFileSync(actionPath, "utf8") : "";
const toneGuidelinesPath = path.join(
process.cwd(),
"lib",
"ai",
"customer-tone-guidelines.ts",
);
const toneGuidelinesSource = existsSync(toneGuidelinesPath)
? readFileSync(toneGuidelinesPath, "utf8")
: "";
const generationSourcePath = path.join(process.cwd(), "convex", "auditGeneration.ts");
const generationSource = existsSync(generationSourcePath)
? readFileSync(generationSourcePath, "utf8")
@@ -129,6 +138,12 @@ test("action starts, queries evidence, and runs stage pipeline", () => {
test("action includes all required audit stages", () => {
for (const stage of [
"classification",
"localSeoSpecialist",
"conversionUxSpecialist",
"visualTrustSpecialist",
"critiqueSpecialist",
"performanceAccessibilitySpecialist",
"evidenceVerifier",
"multimodalAudit",
"germanCopy",
"qualityReview",
@@ -142,6 +157,159 @@ test("action includes all required audit stages", () => {
}
});
test("specialist fan-out runs after evidence input and before German copy", () => {
const evidenceInputIndex = actionSource.indexOf("const evidenceInput = buildAuditEvidenceInput");
const fanOutIndex = actionSource.indexOf("Promise.all(\n specialistStageConfigs.map");
const verifierIndex = actionSource.indexOf('currentStep = "evidenceVerifier"');
const germanCopyIndex = actionSource.indexOf('currentStep = "germanCopy"');
assert.notEqual(evidenceInputIndex, -1, "Action should build evidence input.");
assert.notEqual(germanCopyIndex, -1, "Action should still run German copy.");
assert.notEqual(fanOutIndex, -1, "Action should fan out specialist stage configs.");
assert.notEqual(verifierIndex, -1, "Action should run the evidence verifier.");
assert.equal(
fanOutIndex > evidenceInputIndex && fanOutIndex < germanCopyIndex,
true,
"Specialist fan-out should run after evidence input and before German copy.",
);
assert.equal(
verifierIndex > fanOutIndex && verifierIndex < germanCopyIndex,
true,
"Evidence verifier should run after specialist fan-out and before German copy.",
);
});
test("specialist stages use specialist schemas and verified findings feed German copy", () => {
assert.equal(
hasStageCall("auditSpecialistResultSchema"),
true,
"Specialist stages should call generateObject with auditSpecialistResultSchema.",
);
assert.equal(
hasStageCall("auditEvidenceVerificationSchema"),
true,
"Verifier stage should call generateObject with auditEvidenceVerificationSchema.",
);
assert.match(
actionSource,
/(?:const|let)\s+verifiedFindings\s*[:=]/,
"Action should derive verifiedFindings before synthesis.",
);
assert.match(
actionSource,
/verifiedResult?\.?object|verifiedFindingIds/,
"Verifier output should use compact finding IDs instead of echoing full findings.",
);
assert.match(
actionSource,
/verifiedFindingIds\.has\(candidate\.findingId\)/,
"Action should map verifier-approved IDs back to original specialist findings.",
);
assert.match(
actionSource,
/buildGermanCopyPrompt\(\s*verifiedFindingsText/,
"German copy should be generated from verified findings text.",
);
assert.doesNotMatch(
actionSource,
/buildGermanCopyPrompt\(\s*classificationSummary\s*,/,
"German copy should no longer use raw classification summary as its primary finding input.",
);
});
test("critique specialist translates impeccable critique guidance into the audit fan-out", () => {
assert.match(
actionSource,
/stage:\s*["']critiqueSpecialist["']/,
"Action should include a dedicated critique specialist stage.",
);
assert.match(
actionSource,
/impeccable-critique/,
"Critique specialist should anchor findings to the impeccable critique skill id.",
);
assert.match(
actionSource,
/kognitive Last|Nielsen|AI-Slop|Informationsarchitektur/,
"Critique specialist should include critique guidance beyond generic visual trust.",
);
});
test("German copy prompt uses first-contact email tone guidelines without a new AI stage", () => {
const buildPromptSource = extractFunctionSource("buildGermanCopyPrompt");
assert.doesNotMatch(
buildPromptSource,
/Ich-Ich Kontext/,
"German copy prompt should not force formulaic Ich-Ich copy.",
);
assert.match(
actionSource,
/buildCustomerTonePromptSection/,
"German copy prompt should inject shared customer tone guidelines.",
);
assert.match(
buildPromptSource,
/evidence:\s*AuditEvidence/,
"German copy prompt should accept explicit evidence context.",
);
assert.match(
actionSource,
/buildGermanCopyPrompt\([\s\S]*verifiedFindingsText[\s\S]*multimodalSummary[\s\S]*evidenceInput[\s\S]*\)/,
"German copy prompt should receive the explicit evidence context at the callsite.",
);
assert.match(
toneGuidelinesSource,
/kollegial direkt/,
"Tone guidelines should lock the selected sender posture.",
);
assert.match(
toneGuidelinesSource,
/maximal zwei verifizierte Befunde|max\. zwei verifizierte Befunde/,
"Tone guidelines should keep outreach emails to at most two verified findings.",
);
assert.match(
toneGuidelinesSource,
/kein Mini-Audit/,
"Tone guidelines should explicitly forbid mini-audit emails.",
);
assert.doesNotMatch(
actionSource,
/tone(?:Review|Rewrite|Specialist)|emailToneSpecialist|copyToneSpecialist/,
"Tone work should not add another model-backed generation stage.",
);
});
test("quality review blocks when model review or German copy guard fails", () => {
const qualityPromptSource = extractFunctionSource("buildQualityReviewPrompt");
assert.match(
actionSource,
/qualityPassed\s*=\s*qualityResult\.object\.isValid\s*&&\s*guardResult\.passed/,
"qualityPassed should require both model review validity and German copy guard.",
);
assert.doesNotMatch(
actionSource,
/qualityPassed\s*=\s*guardResult\.passed\s*;/,
"qualityPassed must not ignore the model quality review.",
);
assert.match(
qualityPromptSource,
/echte Erstmail von Matthias/,
"Quality review should apply the selected first-contact email rubric.",
);
assert.match(
qualityPromptSource,
/KI-Verkaufstext/,
"Quality review should reject AI-like sales copy.",
);
assert.match(
qualityPromptSource,
/verified findings|verifizierte Befunde/i,
"Quality review should keep concrete claims tied to verified findings.",
);
});
test("action handles post-start failure paths in action-level catch", () => {
assert.equal(
hasPattern(
@@ -377,18 +545,18 @@ test("action runs german copy guard and blocks outreach-ready on validation fail
assert.equal(
hasPattern(
actionSource,
/qualityPassed\s*=\s*guardResult\.passed/,
/qualityPassed\s*=\s*qualityResult\.object\.isValid\s*&&\s*guardResult\.passed/,
),
true,
"Only deterministic German copy guard failures should hard-block the audit run.",
"Model QA and deterministic German copy guard failures should hard-block the audit run.",
);
assert.equal(
hasPattern(
actionSource,
/qualityPassed\s*=\s*qualityResult\.object\.isValid\s*&&\s*guardResult\.passed/,
/qualityPassed\s*=\s*guardResult\.passed\s*;/,
),
false,
"Subjective model QA warnings should not be combined with guardResult for terminal failure.",
"Action must not ignore the model QA validity flag.",
);
assert.equal(
hasPattern(actionSource, /internal\.leads\.reviewUpdateInternal/),