import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { applyProbeOutcome } from "../src/lib/rectification-agentic/core/apply-probe-outcome.ts"; import type { ConflictProbe } from "../src/lib/rectification-agentic/core/types.ts"; import { isTieBreakRoundSchema } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import { rangeDeliveryForSnapshot } from "../src/lib/rectification-agentic/v9/divergence-panel.ts"; import { buildMethodFollowupPlan, tieBreakPersonalityAvailable, tieBreakPersonalityFollowup, } from "../src/lib/rectification-agentic/v9/method-followup.ts"; import { expectedAnswerSchemaFor, stableFollowupQuestionId } from "../src/lib/rectification-agentic/v9/server-focus.ts"; const TIMES = ["04:48", "04:53", "05:06", "05:07"] as const; const LEDGER = [ { status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2016-09-01", occurredTo: "2016-09-30", eventKind: "education_start", summary: "上大学", }, { status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: null, eventKind: "career_entry", summary: "入职实习", }, { status: "confirmed", domain: "relationship", datePrecision: "month", occurredFrom: "2024-05-01", occurredTo: null, eventKind: "relationship_start", summary: "确定关系", }, ] as const; function contrastProbe(layer: "d9" | "d10") { const signs = layer === "d9" ? ["巨蟹座", "狮子座"] as const : ["狮子座", "处女座"]; return { probeId: `contrast:varga.${layer}.${signs[0]}/${signs[1]}`, candidateSetVersion: "04:48-05:07", question: layer === "d9" ? "亲密关系里更接近下面哪一种相处方式?" : "平时做事,你更接近下面哪一种?", expectedOutcomes: [ { outcomeId: "yes", supportsCandidateIds: ["04:53"], conflictsCandidateIds: ["05:06"] }, { outcomeId: "weak_yes", supportsCandidateIds: ["05:06"], conflictsCandidateIds: ["04:53"] }, ], candidateSplitHash: `varga.${layer}.${signs[0]}/${signs[1]}`, informationGain: layer === "d9" ? 1.4 : 0.9, sourceFeatures: [{ technique: layer.toUpperCase(), calculationResultId: null }], domain: layer === "d9" ? "relationship" : "career", year: null, semanticKey: `varga.${layer}.${signs[0]}/${signs[1]}`, choiceKind: "varga_style" as const, styleOptions: [ { label: "相处里更在意照顾对方的感受", answerClass: "yes" as const, sign: signs[0] }, { label: "习惯带头,也不排斥站到台前", answerClass: "weak_yes" as const, sign: signs[1] }, ], }; } const D9 = contrastProbe("d9"); const D10 = contrastProbe("d10"); function planInput(askedProbeKeys: readonly string[] = []) { return { evidence: LEDGER, declinedTopics: [], sessionOutcome: "discriminate_candidates" as const, candidatesSeparated: false, remainingLayers: ["d9", "d10"], remainingSplitTimes: ["04:48", "05:07"] as const, remainingCandidateCount: 4, topCandidateTimes: [...TIMES], askedProbeKeys, contrastPacket: { candidateSetVersion: "04:48-05:07", vargaDifferences: [], probes: [D9, D10], }, }; } test("GET availability matches unasked D9/D10 varga_style, not window_scan", () => { const open = planInput(); // 原值: 主 followup 无 choice_kind // 新值: 未覆盖领域会先出定向存在性题 // 原因: D5 七条线都会问到;性格题仍是卡下可选入口 const followup = buildMethodFollowupPlan(open).next_followup; if (followup?.intent === "collect_method_evidence") { assert.equal(followup.choice_kind, "existence"); } assert.equal(tieBreakPersonalityAvailable(open), true); assert.match(tieBreakPersonalityFollowup(open)?.semantic_key ?? "", /varga\.d9/); assert.equal(rangeDeliveryForSnapshot({ window_scan: { d9_candidates_differ: true, d10_candidates_differ: true }, tieBreakAvailable: tieBreakPersonalityAvailable(open), }).tie_break_available, true); const afterD9 = planInput([D9.semanticKey]); const second = tieBreakPersonalityFollowup(afterD9); assert.match(second?.semantic_key ?? "", /varga\.d10/); assert.equal(second?.tie_break_round, true); assert.equal(tieBreakPersonalityAvailable(afterD9), true); const bothAsked = planInput([D9.semanticKey, D10.semanticKey]); assert.equal(tieBreakPersonalityFollowup(bothAsked), null); assert.equal(tieBreakPersonalityAvailable(bothAsked), false); assert.equal(rangeDeliveryForSnapshot({ window_scan: { d9_candidates_differ: true, d10_candidates_differ: true }, tieBreakAvailable: tieBreakPersonalityAvailable(bothAsked), }).tie_break_available, false); assert.equal(tieBreakPersonalityAvailable({ ...open, accepted: true }), false); }); test("one click stamps a round schema; scoring stays tie_break without elimination", () => { const followup = tieBreakPersonalityFollowup(planInput()); assert.ok(followup?.choice_frame); const schema = expectedAnswerSchemaFor( followup.choice_frame, stableFollowupQuestionId(followup), null, followup, ); assert.equal(schema?.tie_break_round, true); assert.equal(isTieBreakRoundSchema(schema), true); assert.equal(isTieBreakRoundSchema({ choice_kind: "varga_style" }), false); const d10: ConflictProbe = { id: "probe:varga.d10.狮子座/处女座", semantic_key: "varga.d10.狮子座/处女座", candidate_split_hash: "04:48-05:07:varga.d10", domain: "career", year: 0, question: "平时做事,你更接近下面哪一种?", candidate_ids: [...TIMES], expected_outcomes: [ { answer_class: "yes", supports: ["04:53"], conflicts: ["05:06"] }, { answer_class: "weak_yes", supports: ["05:06"], conflicts: ["04:53"] }, { answer_class: "no", supports: [], conflicts: [] }, { answer_class: "unsure", supports: [], conflicts: [] }, ], information_gain: 0.9, source: "varga_contrast", choice_kind: "varga_style", style_options: [ { label: "相处里更在意照顾对方的感受", answer_class: "yes", sign: "巨蟹座" }, { label: "习惯带头,也不排斥站到台前", answer_class: "weak_yes", sign: "狮子座" }, ], }; const scored = applyProbeOutcome( { "04:48": 10, "04:53": 12, "05:06": 11, "05:07": 10 }, d10, "yes", ); assert.equal(scored.kind, "tie_break"); assert.equal(scored.deltas["04:53"], 1); assert.equal(scored.eliminated_ids.length, 0); }); test("click path does not surface 409 copy; round continue is opt-in only", () => { const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8"); const answer = readFileSync(new URL("../src/lib/rectification-agentic/v9/answer-choice.ts", import.meta.url), "utf8"); const panel = readFileSync(new URL("../src/lib/rectification-agentic/v9/divergence-panel.ts", import.meta.url), "utf8"); const pool = readFileSync(new URL("../src/lib/rectification-agentic/v9/collection-question-pool.ts", import.meta.url), "utf8"); const route = readFileSync( new URL("../src/app/api/rectification/cases/[caseId]/tie-break/route.ts", import.meta.url), "utf8", ); // 原值: 客户端 requestTieBreak 把 tie_break_unavailable 当静默刷新,不 setError // 新值: 卡上入口已删,客户端不再有这条 click path;409 口径留在 API // 原因: BUG-706 assert.doesNotMatch(chat, /requestTieBreak/); assert.doesNotMatch(chat, /tie_break_unavailable/); assert.match(route, /tie_break_unavailable/); assert.match(answer, /continueTieBreakRound\?: boolean/); assert.match(answer, /continueTieBreakRound === true/); assert.match(answer, /isTieBreakRoundSchema/); assert.doesNotMatch(panel, /d9_candidates_differ[\s\S]{0,80}tieBreakAvailable/); assert.doesNotMatch(pool, /export function isTargetedCollectDeclined/); assert.doesNotMatch(pool, /export function isTargetedCollectYearFollowup/); }); test("Skill 10.0.26 lists the fourth targeted-collect skip option", () => { const skill = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url), "utf8"); const strategy = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/references/conversation-strategy.md", import.meta.url), "utf8"); // 原值: /^version: 10\.0\.27$/ / 新值: /^version: 10\.0\.28$/ / 原因: D6 改写出卡句后 Skill bump assert.match(skill, /^version: 10\.0\.28$/m); assert.match(skill, /已拒绝(没有发生过 \/ 这类事都没有过)的目标不得换词重问;跳过的按服务器计划最多重问一次/); assert.match(strategy, /跳过的线按服务器计划最多换一种问法再问一次/); });