feat(consult): sections presuppose nothing, examples carry no reading rule or job shape, corrections are acknowledged
- natalAnswerShapeBody: NATAL_SECTION_RULE replaces 「这个人是什么样、你们怎么相处、 事情会怎么走」; the shared voice quotes the same constant (BUG-1164) - product-voice / VOICE.md: no 逆行 in examples, parents example leads with the affliction and asks to confirm, married-presupposing Good example removed (BUG-1165) - follow-up shape: a user correction is acknowledged, never defended (BUG-1166) - career examples carry no job shape (升职 / 专业能力 / 做深现有工作) (BUG-1168) - biography backtest golden regenerated with the real engine so the card carries brief 1's aspects, combustion, yoga domains and saturn_from_moon_house; backtest script gains --correction follow-up turns Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
585c7370f8
commit
955bab691a
@@ -0,0 +1,83 @@
|
||||
// TASK-consult-no-presupposition-and-backtest-20261001 T1–T3 (BUG-1164~1166, BUG-1168).
|
||||
// The section rule no longer presupposes the person is there; examples carry no
|
||||
// reading rule and no job shape; a correction turn acknowledges, never defends.
|
||||
import assert from "node:assert/strict";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
FOLLOW_UP_CORRECTION_RULE,
|
||||
NATAL_SECTION_RULE,
|
||||
followUpAnswerShapeInstruction,
|
||||
natalAnswerShapeBody,
|
||||
natalAnswerShapeInstruction,
|
||||
} from "../src/lib/consultation-thinking-plan.ts";
|
||||
import { productConversationVoice } from "../src/mastra/product-voice.ts";
|
||||
|
||||
const OLD_SECTION_RULE = /这个人是什么样、你们怎么相处、事情会怎么走/;
|
||||
|
||||
const SECTION_RULE_TEXT = "每段先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落。受冲信号两个及以上时,这一段第一句就写这一面,用「可能」,段末用一句话请用户确认(例:「如果实际情况不是这样,告诉我,我按真实情况重看。」),全篇这类确认最多一处。不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。";
|
||||
|
||||
function source(relative: string) {
|
||||
return readFileSync(new URL(`../${relative}`, import.meta.url), "utf8");
|
||||
}
|
||||
|
||||
/** Every prompt-bearing module: src/mastra/*.ts and src/lib/consultation-*.ts. */
|
||||
function promptSources() {
|
||||
const files = [
|
||||
...readdirSync(new URL("../src/mastra/", import.meta.url)).filter((name) => name.endsWith(".ts")).map((name) => `src/mastra/${name}`),
|
||||
...readdirSync(new URL("../src/lib/", import.meta.url)).filter((name) => /^consultation-.*\.ts$/.test(name)).map((name) => `src/lib/${name}`),
|
||||
];
|
||||
return files.map((file) => ({ file, text: source(file) }));
|
||||
}
|
||||
|
||||
test("T1: each section leads with the strongest signal, asks to confirm an affliction, presupposes nothing (BUG-1164)", () => {
|
||||
assert.equal(NATAL_SECTION_RULE, SECTION_RULE_TEXT);
|
||||
for (const point of ["在不在你身边", "请用户确认", "不预设", "是否在上班、做哪类工作", "性别未知时不用「他」「她」称伴侣和孩子", "全篇这类确认最多一处"]) {
|
||||
assert.ok(NATAL_SECTION_RULE.includes(point), point);
|
||||
}
|
||||
const body = natalAnswerShapeBody();
|
||||
assert.ok(body.includes(NATAL_SECTION_RULE));
|
||||
assert.match(body, /每段的判断后在括号里给撑住它的盘上依据/);
|
||||
assert.doesNotMatch(body, OLD_SECTION_RULE);
|
||||
// The shared voice quotes the same constant; the rule text lives in one file.
|
||||
assert.ok(productConversationVoice.includes(NATAL_SECTION_RULE));
|
||||
assert.doesNotMatch(productConversationVoice, OLD_SECTION_RULE);
|
||||
assert.match(source("src/mastra/product-voice.ts"), /3\. \$\{NATAL_SECTION_RULE\}/);
|
||||
const holders = promptSources().filter(({ text }) => text.includes("包括这个人在不在你身边")).map(({ file }) => file);
|
||||
assert.deepEqual(holders, ["src/lib/consultation-thinking-plan.ts"]);
|
||||
for (const { file, text } of promptSources()) assert.doesNotMatch(text, OLD_SECTION_RULE, file);
|
||||
});
|
||||
|
||||
test("T2: no 逆行 in prompts or examples except the prohibition line and technique names (BUG-1165)", () => {
|
||||
const hits = promptSources().flatMap(({ file, text }) => text.split("\n").flatMap((line, index) => (
|
||||
line.includes("逆行") ? [`${file}:${index + 1}:${line.trim()}`] : []
|
||||
)));
|
||||
const allowed = hits.filter((hit) => (
|
||||
// Brief 2's shared reading forbids the 逆行 = 反复 inference; technique labels are names, not readings.
|
||||
/consultation-condensed-checklist\.ts:.*逆行不等于「反复、打回来」/.test(hit)
|
||||
|| /consultation-technique-audit\.ts:.*"逆行(次限|太阳弧)"/.test(hit)
|
||||
));
|
||||
assert.deepEqual(hits, allowed);
|
||||
assert.equal(allowed.length, 3);
|
||||
assert.match(productConversationVoice, /不是。(4 宫主落 10 宫)她把心力放在你的前途上/);
|
||||
assert.doesNotMatch(productConversationVoice, /已经在一起的关系|这段婚姻/);
|
||||
});
|
||||
|
||||
test("T2b: career examples carry no job shape (BUG-1168)", () => {
|
||||
assert.doesNotMatch(productConversationVoice, /升职|专业能力|做深|现有工作|这份工作|换公司|投简历|涨薪/);
|
||||
const voiceDoc = source("docs/VOICE.md");
|
||||
for (const phrase of ["之后再谈升职更有把握", "把已经积累的专业能力做成长期事业", "这份工作还值得留"]) {
|
||||
assert.ok(!voiceDoc.includes(phrase), phrase);
|
||||
}
|
||||
assert.match(productConversationVoice, /不预设读者在上班、做哪类工作/);
|
||||
});
|
||||
|
||||
test("T3: a correction turn acknowledges first and does not defend the reading (BUG-1166)", () => {
|
||||
assert.equal(
|
||||
FOLLOW_UP_CORRECTION_RULE,
|
||||
"用户纠正了前面的判断(比如说父亲其实不常在身边):先认下这件事,再说盘上哪几条和它对得上、原来哪句读偏了;不为原判断辩护。",
|
||||
);
|
||||
assert.ok(followUpAnswerShapeInstruction().includes(FOLLOW_UP_CORRECTION_RULE));
|
||||
assert.ok(!natalAnswerShapeInstruction().includes(FOLLOW_UP_CORRECTION_RULE));
|
||||
});
|
||||
@@ -217,15 +217,23 @@ test("persona is the sharp-but-reliable colleague, and hope never becomes a plat
|
||||
// 新值: 锁父母题按对象分段的 Good、单对象不拆段的 Good、格局名/大运拟人的 Bad;顾问报告 Bad 保留、理由改成「没有盘上依据」
|
||||
// 原因: TASK-consult-plain-answer-20261001 D1/D2/T1(BUG-1132):09-17 定稿样例随形状一起下线
|
||||
test("the pinned Good examples split by person and stay plain; the coined-pattern opener is Bad", () => {
|
||||
const parentsStart = voice.indexOf("Good (父母题首轮,按对象分段;盘上数据是示意)");
|
||||
const careerStart = voice.indexOf("Good (单对象事业题首轮,不拆段;盘上数据是示意)");
|
||||
// 原值: indexOf("Good (父母题首轮,按对象分段;盘上数据是示意)") / indexOf("Good (单对象事业题首轮,不拆段;盘上数据是示意)")
|
||||
// 新值: 两个标签改为「括号里是依据的占位,只演示口气与结构,不是读法」,事业例再加「不预设读者在上班、做哪类工作」
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D3/D7(BUG-1165/1168):示例不得携带读法规则与职业形态
|
||||
const parentsStart = voice.indexOf("Good (父母题首轮,按对象分段;括号里是依据的占位,只演示口气与结构,不是读法)");
|
||||
const careerStart = voice.indexOf("Good (单对象事业题首轮,不拆段;括号里是依据的占位,只演示口气与结构,不是读法;不预设读者在上班、做哪类工作)");
|
||||
const badStart = voice.indexOf("Bad (多对象题,改写示意)");
|
||||
assert.ok(parentsStart > -1 && careerStart > parentsStart && badStart > careerStart);
|
||||
const parents = voice.slice(parentsStart, careerStart);
|
||||
for (const heading of ["## 你和妈妈", "## 你和爸爸", "## 他们怎么对你", "## 时间怎么看", "## 这周可以做的一件事"]) {
|
||||
assert.ok(parents.includes(heading), heading);
|
||||
}
|
||||
assert.match(parents, /(4 宫母亲宫的宫主水星落在 10 宫事业宫;代表母亲的月亮也在 10 宫)/);
|
||||
// 原值: assert.match(parents, /(4 宫母亲宫的宫主水星落在 10 宫事业宫;代表母亲的月亮也在 10 宫)/)
|
||||
// 新值: 括号只放占位(4 宫主落 10 宫;月亮在 10 宫);爸爸段演示「受冲先说、用可能、段末请确认」;不再有「分量重」「问爸爸的意见」
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D1/D3(BUG-1164/1165):旧例预设父亲在场,回测里缺席的父亲被照抄成「话少、分量重」
|
||||
assert.match(parents, /(4 宫主落 10 宫;月亮在 10 宫)/);
|
||||
assert.match(parents, /他可能不常在你身边[\s\S]*如果实际情况不是这样,告诉我,我按真实情况重看。/);
|
||||
assert.doesNotMatch(parents, /分量重|标杆|爸爸的意见|靠做事/);
|
||||
const career = voice.slice(careerStart, badStart);
|
||||
assert.doesNotMatch(career, /## 你和/);
|
||||
assert.match(career, /## 盘上依据/);
|
||||
@@ -237,9 +245,16 @@ test("the pinned Good examples split by person and stay plain; the coined-patter
|
||||
const bad = voice.slice(badStart, voice.indexOf("\n", badStart));
|
||||
assert.match(bad, /自造格局名、大运写成角色、星名当形容词,爸妈揉成一段/);
|
||||
|
||||
assert.match(voice, /Good \(追问轮,用户说「你在说什么鬼」\): 说直接点:这份工作还值得留/);
|
||||
// 原值: /Good \(追问轮,用户说「你在说什么鬼」\): 说直接点:这份工作还值得留/
|
||||
// 新值: /…说直接点:现在这条路还值得走/
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D7(BUG-1168):示例不预设读者在上班
|
||||
assert.match(voice, /Good \(追问轮,用户说「你在说什么鬼」\): 说直接点:现在这条路还值得走/);
|
||||
|
||||
const badLine = voice.split("\n").find((line) => line.startsWith("Bad: 你更适合把已经积累的专业能力做成长期事业"));
|
||||
// 原值: startsWith("Bad: 你更适合把已经积累的专业能力做成长期事业")
|
||||
// 新值: startsWith("Bad: 你更适合长期做一件事"),理由不变;全篇示例不再有「专业能力」「升职」「做深」「现有工作」
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D7/T2b(BUG-1168):Bad 例的职业形态词被模型反向吸收
|
||||
assert.doesNotMatch(voice, /专业能力|升职|做深|现有工作|这份工作/);
|
||||
const badLine = voice.split("\n").find((line) => line.startsWith("Bad: 你更适合长期做一件事"));
|
||||
assert.ok(badLine, "旧 Good 例必须改挂在 Bad 上");
|
||||
assert.match(badLine!, /像顾问报告:只有结论,没有盘上依据/);
|
||||
});
|
||||
@@ -264,7 +279,11 @@ test("the voice answers the sentence asked: no motive guessing, yes/no first, co
|
||||
assert.match(voice, /无出生分钟与申报时段没有正文骨架,行动就写在回答结尾/);
|
||||
// Yes/no Good and Bad pair, fictional chart.
|
||||
assert.match(voice, /Good \(是非题,用户问「我妈是不是不太在意我」/);
|
||||
assert.match(voice, /不是。4 宫主水星逆行落 10 宫/);
|
||||
// 原值: assert.match(voice, /不是。4 宫主水星逆行落 10 宫/)
|
||||
// 新值: 「不是。(4 宫主落 10 宫)」;示例与提示词里不再有「逆行」,已婚预设的婚姻 Good 例删除
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D2/D3(BUG-1165):「逆行」被照抄成「反复、打回来」
|
||||
assert.match(voice, /不是。(4 宫主落 10 宫)她把心力放在你的前途上/);
|
||||
assert.doesNotMatch(voice, /逆行|已经在一起的关系/);
|
||||
assert.match(voice, /Bad \(同一问题\): 你其实要的是被听见/);
|
||||
// 原值: "…wants, needs, or feels; state where the chart puts them"(Never 列表里第二句读成也被禁止)
|
||||
// 新值: 两句拆开,第二句是替代做法 原因: Claude 验收改措辞
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user