cf6405ed88
The 09:31 staging card had four ready sections but showed only structural validation failure. Derive the list copy from section status so a written pipeline stop is not described as an empty schema miss. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
4.6 KiB
TypeScript
104 lines
4.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import { classifySectionErrorCode } from "../src/lib/personal-report-generation.ts";
|
|
import { summarizePersonalReportFailure } from "../src/lib/personal-report-failure-summary.ts";
|
|
import {
|
|
PersonalReportAgentOutputError,
|
|
classifyWriterRepairCategory,
|
|
writerRepairPromptSuffix,
|
|
} from "../src/mastra/personal-report.ts";
|
|
|
|
test("refs mismatch is not classified as evidence insufficient", () => {
|
|
assert.equal(
|
|
classifySectionErrorCode(new Error("report_writer_evidence_refs_mismatch")),
|
|
"section_refs_mismatch",
|
|
);
|
|
assert.equal(
|
|
classifySectionErrorCode(new Error("report_writer_section_identity_mismatch")),
|
|
"section_identity_mismatch",
|
|
);
|
|
assert.equal(
|
|
classifySectionErrorCode(new Error("Report evidence insufficient: planets_missing")),
|
|
"section_evidence_insufficient",
|
|
);
|
|
assert.equal(
|
|
classifySectionErrorCode(new PersonalReportAgentOutputError("length")),
|
|
"section_output_truncated",
|
|
);
|
|
assert.equal(classifySectionErrorCode(new Error("section_output_invalid")), "section_output_invalid");
|
|
});
|
|
|
|
test("repair prompt names the failure category without carrying content", () => {
|
|
const refs = writerRepairPromptSuffix({ error: new Error("report_writer_evidence_refs_mismatch") });
|
|
assert.match(refs, /失败类别:refs mismatch/);
|
|
assert.match(refs, /evidenceRefs 必须逐字复制 plan\.evidenceRefs 数组,不得增删/);
|
|
assert.doesNotMatch(refs, /ev-|Sun|Moon|宫/);
|
|
|
|
const truncated = writerRepairPromptSuffix({ finishReason: "length", targetCharactersMin: 260 });
|
|
assert.equal(classifyWriterRepairCategory({ finishReason: "length" }), "output truncated");
|
|
assert.match(truncated, /失败类别:output truncated/);
|
|
assert.match(truncated, /压缩到约 260 字/);
|
|
|
|
const schema = writerRepairPromptSuffix({});
|
|
assert.match(schema, /失败类别:schema invalid/);
|
|
});
|
|
|
|
test("failure summary is readable and stays at error-code granularity", () => {
|
|
const truncated = summarizePersonalReportFailure({
|
|
themeCount: 4,
|
|
failureCode: "report_schema_invalid",
|
|
sections: [
|
|
{ status: "blocked", lastErrorCode: "section_output_truncated" },
|
|
{ status: "blocked", lastErrorCode: "section_output_truncated" },
|
|
{ status: "blocked", lastErrorCode: "section_output_truncated" },
|
|
{ status: "blocked", lastErrorCode: "section_output_truncated" },
|
|
],
|
|
});
|
|
assert.equal(truncated.summary, "4 个主题中 4 个写作失败:输出被截断");
|
|
assert.equal(truncated.innerReason, "all_sections_blocked");
|
|
|
|
const incomplete = summarizePersonalReportFailure({
|
|
themeCount: 4,
|
|
failureCode: "report_schema_invalid",
|
|
sections: [
|
|
{ status: "blocked", lastErrorCode: "section_refs_mismatch" },
|
|
{ status: "pending", lastErrorCode: null },
|
|
{ status: "pending", lastErrorCode: null },
|
|
{ status: "pending", lastErrorCode: null },
|
|
],
|
|
});
|
|
assert.equal(incomplete.summary, "4 个主题中 1 个写作失败:证据引用未对齐,3 个未开始");
|
|
assert.equal(incomplete.innerReason, "section_generation_incomplete");
|
|
assert.deepEqual(incomplete.lastErrorCodes, ["section_refs_mismatch"]);
|
|
|
|
const assembledChapters = summarizePersonalReportFailure({
|
|
themeCount: 4,
|
|
failureCode: "report_schema_invalid",
|
|
sections: [
|
|
{ status: "ready", lastErrorCode: null },
|
|
{ status: "ready", lastErrorCode: null },
|
|
{ status: "ready", lastErrorCode: null },
|
|
{ status: "ready", lastErrorCode: null },
|
|
],
|
|
});
|
|
assert.equal(assembledChapters.summary, "4 个主题已写成,整份报告未完成装配");
|
|
assert.equal(assembledChapters.innerReason, "section_generation_incomplete");
|
|
});
|
|
|
|
test("writer identity and evidenceRefs assertions keep set-equality semantics", () => {
|
|
const generationSource = readFileSync(
|
|
new URL("../src/lib/personal-report-generation.ts", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const agentSource = readFileSync(
|
|
new URL("../src/mastra/personal-report.ts", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(generationSource, /if \(candidate\.id !== entry\.id \|\| candidate\.theme !== entry\.theme\) throw new Error\("report_writer_section_identity_mismatch"\)/);
|
|
assert.match(generationSource, /equalStringSets\(candidate\.evidenceRefs, entry\.evidenceRefs\)/);
|
|
assert.match(generationSource, /throw new Error\("report_writer_evidence_refs_mismatch"\)/);
|
|
assert.match(agentSource, /evidenceRefs 必须逐字复制 plan\.evidenceRefs 数组,不得增删/);
|
|
assert.doesNotMatch(generationSource, /targetCharacters\.max\s*\/\s*2/);
|
|
});
|