Hide the button once both personality cards are answered, persist the second card in the same opt-in round, and freeze Skill 10.0.26. Co-authored-by: Cursor <cursoragent@cursor.com>
194 lines
8.0 KiB
TypeScript
194 lines
8.0 KiB
TypeScript
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();
|
|
assert.equal(buildMethodFollowupPlan(open).next_followup?.choice_kind, undefined);
|
|
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");
|
|
assert.match(chat, /payload\?\.code === "tie_break_unavailable"/);
|
|
assert.match(chat, /await loadCaseSnapshot\(\);\s*\n\s*return;/);
|
|
const unavailableBranch = chat.slice(
|
|
chat.indexOf('payload?.code === "tie_break_unavailable"'),
|
|
chat.indexOf("暂时无法开始参考题"),
|
|
);
|
|
assert.doesNotMatch(unavailableBranch, /setError/);
|
|
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");
|
|
assert.match(skill, /^version: 10\.0\.26$/m);
|
|
assert.match(skill, /有 \/ 没有 \/ 记不清 \/ 这条先跳过/);
|
|
assert.match(strategy, /有 \/ 没有 \/ 记不清 \/ 这条先跳过/);
|
|
});
|