import assert from "node:assert/strict"; import test from "node:test"; import { applyThinkingSectionProgress, consultationComposeHeadingGroups, consultationContinuePrompt, consultationReportHeadings, consultationSectionPrompt, consultationSpokenHeadingRule, natalConsultationThinkingPlan, REPORT_HEADING, splitAnswerByHeadings, visibleThinkingSteps, windowConsultationThinkingPlan, } from "../src/lib/consultation-thinking-plan.ts"; test("multi-domain thinking plan uses Chinese product labels and hides tool ids", () => { const sections = natalConsultationThinkingPlan({ domains: ["career", "wealth"], requiredBlocks: [ "raw_structure", "raman_six_step", "yoga_table", "timing", "synthesis", "technique_audit_table", "modern_wrap", ], mustUseLayers: ["D1", "run-jyotish-consultation", "D10", "skill_read"], }); const encoded = JSON.stringify(sections); assert.deepEqual(sections.map((section) => section.heading), [ REPORT_HEADING.foundation, "事业", "财富", REPORT_HEADING.audit, ]); assert.ok(sections.some((section) => section.title === "接下来分析你的事业")); assert.ok(sections.some((section) => section.title === "接下来分析你的财富")); assert.equal(encoded.includes("run-jyotish"), false); assert.equal(encoded.includes("skill_read"), false); assert.ok(sections[0]?.steps.some((step) => step.label.includes("D1"))); }); test("window thinking plan stays on stable-layer copy", () => { const [section] = windowConsultationThinkingPlan(); assert.equal(section?.heading, REPORT_HEADING.foundation); assert.equal(JSON.stringify(section).includes("run-jyotish"), false); }); test("visible steps collapse extras into a remaining count", () => { const steps = Array.from({ length: 6 }, (_, index) => ({ id: `s${index}`, label: `步骤 ${index + 1}`, status: "pending" as const, })); const visible = visibleThinkingSteps(steps); assert.equal(visible.visible.length, 4); assert.equal(visible.hiddenCount, 2); }); test("answer split follows the required heading order", () => { const headings = consultationReportHeadings(["career", "wealth"]); const text = [ "## 统一参数与原始结构", "岁差 Lahiri。", "## 事业", "事业方向稳定。", "## 财富", "财运看 2/11 宫。", "## 技法审计表", "| 技法 | 状态 |", "## 现代生活", "先把合同条款写清楚。", ].join("\n"); const { slices } = splitAnswerByHeadings(text, headings); assert.match(slices["事业"] ?? "", /事业方向稳定/); assert.match(slices["财富"] ?? "", /财运看/); assert.match(slices[REPORT_HEADING.audit] ?? "", /技法/); assert.match(slices[REPORT_HEADING.wrap] ?? "", /合同条款/); }); test("settled answers mark leftover thinking sections done", () => { const sections = natalConsultationThinkingPlan({ domains: ["career"] }); const progressed = applyThinkingSectionProgress( sections, "今日行运已经写完,但没有领域二级标题。", { settled: true }, ); assert.equal(progressed.every((section) => section.steps.every((step) => step.status === "done")), true); }); test("progress marks the current heading active and completed ones done", () => { const sections = natalConsultationThinkingPlan({ domains: ["career"] }); const progressed = applyThinkingSectionProgress(sections, "## 统一参数与原始结构\n岁差。\n"); assert.equal(progressed[0]?.steps[0]?.status, "done"); assert.equal(progressed[1]?.steps[0]?.status, "active"); }); test("continue prompt asks to resume after the last complete heading", () => { const prompt = consultationContinuePrompt("## 事业\n方向稳定。\n"); assert.match(prompt, /最后一个完整标题是:## 事业/); assert.match(prompt, /不要重复已写出的段落/); assert.match(prompt, /不要写思考过程清单/); }); test("natal spoken answers must use markdown lists for parallel points", () => { assert.match(consultationSpokenHeadingRule("natal"), /Markdown bullet lists/); }); test("close heading group splits audit and modern-life into two compose slices", () => { const groups = consultationComposeHeadingGroups(natalConsultationThinkingPlan({ domains: ["career", "wealth"], })); assert.deepEqual(groups.map((group) => [...group.headings]), [ [REPORT_HEADING.foundation], ["事业"], ["财富"], [REPORT_HEADING.audit, REPORT_HEADING.wrap], ]); }); test("section prompt asks for one heading and forbids another calculation", () => { const prompt = consultationSectionPrompt("事业", "## 统一参数与原始结构\n岁差。\n"); assert.match(prompt, /只写这一个二级标题及其正文:## 事业/); assert.match(prompt, /不要再调用排盘工具/); assert.match(prompt, /统一参数与原始结构/); });