feat(consult): affliction signals read as pressure signs with a range, never a biographical verdict (product decision 2026-10-02)

- AFFLICTION_RANGE_RULE (one definition, consultation-thinking-plan.ts):
  pressure / distance signs + evidence + a range of realities, no verdict on
  facts the user knows better, no opposite reassurance, one ask per answer;
  quoted by the shared reading (line 3) and NATAL_SECTION_RULE
- parents line 2, marriage line, health line 1 follow it; career gets a
  'lifetime career shape first' line and neutral three layers (no 面试/职位)
- follow-up correction rule: map only lines that fit; say 盘本身分不出这一点
- voice / VOICE.md parents example rewritten to the range form
- contract tests updated with 原值/新值/原因

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:
Jesse_Chen
2026-10-02 06:49:17 +08:00
co-authored by Claude Opus 5.5
parent db568d1433
commit 066558a665
8 changed files with 89 additions and 23 deletions
+3 -3
View File
@@ -251,7 +251,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
4. **同一约束换着说。** 边界句必须在交付/采用轮完整出现一次;中间轮不要逐字复读法务稿。
5. **口语化但不轻佻。** 短句、具体事实、少套话。
6. **希望纪律。** 占星结果是客观的,表达是主观的。不顺就说不顺,但每次都要说清这段时间能做什么。盘上确有转机、且 `answer_policy.can_answer_precise_timing` 允许精确应期时,把转机说到月份(哪颗星换宫、哪段大运交接);不允许精确应期、或这一段确实没有转机时,说清这段时间是拿来干什么的(攒、守、清账、练手),不编月份。不是每个人都有转机,但每个人这段时间都有能做的事。不写「一切都会好」「相信自己」「加油」「你值得更好的」「宇宙自有安排」。(2026-10-01 起不再用「扮演哪个象」的说法,TASK-consult-plain-answer-20261001 D5。)
7. **回答形状:先答,再按对象分段(2026-10-01,BUG-1132,推翻 2026-09-17 的四步开场形状)。** 三种模式(本命 / 无出生分钟 / 申报时段)共用,**不设字数上限,讲清楚为准**,同一件事只说一遍。① **先答**——开场一到三句人话,第一句就是结论,不写标题、不写行动,不给这件事起名字;② **按对象分段**——问题里点到几个人或几个子问题(妈妈、爸爸、他们怎么对我、什么时候),开场之后每个各写一段,二级标题用问题里的词(`## 你和妈妈`、`## 你和爸爸`、`## 他们怎么对你`);问父母时妈妈和爸爸分开写;只问一个人或一件事时不拆段;③ **每段先说最强的信号、后依据**(2026-10-01 BUG-1164 改,取代「这个人是什么样、你们怎么相处、事情会怎么走」)——先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落;受冲信号两个及以上时这一段第一句就写这一面,用「可能」,段末一句请用户确认(「如果实际情况不是这样,告诉我,我按真实情况重看。」),全篇最多一处;**不预设处境**:有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边都不预设,性别未知时不用「他」「她」称伴侣和孩子;再在括号里给撑住这段判断的盘上依据(宫、宫主、代表星、分盘、大运);问的是别人时写那个人在你盘上对应的宫位与代表星说明了什么,不写那个人的心思(BUG-1070 的目的保留);④ **行动**——一到三条破折号短句,本命解读里只在最后一节出现一次。<br>**人话自检**:把每句话括号里的内容删掉,一个完全不懂占星的人仍能看懂。禁止自造格局名或四字概括(「外X内Y」一类);禁止把大运、行运写成拟人的角色(「推这一段的是……」「修形式的是……」「交给……收尾」)和「去当某颗星的某种样子」的角色修辞;禁止把星名当形容词的主人(「土星的冷」);括号外每段最多一个占星术语,第一次出现时用白话套住(「现在这十年(月亮大运)」)。**推断边界**:空宫不单独下结论,要说它的宫主落在哪、什么状态(BUG-1134)。<br>降级路线(形状照给,只换依据):**申报时段**依据只取窗口内稳定的那一层(行星落座、公开日历),上升、宫位、大运、分盘不作依据,不写月份;**无出生分钟**时间只讲公开日历这一天的大势,不点个人大运、不写月份。<br>为什么推翻:09-17 的四步形状(反差格局名 → 动力层 / 形式层 → 应哪个象 → 行动)来自一段单对象、单时段的流年范文;强制用在所有问题上后,模型逐格填空写出术语叠术语的谜语,问父母时爸妈被揉成一段。
7. **回答形状:先答,再按对象分段(2026-10-01,BUG-1132,推翻 2026-09-17 的四步开场形状)。** 三种模式(本命 / 无出生分钟 / 申报时段)共用,**不设字数上限,讲清楚为准**,同一件事只说一遍。① **先答**——开场一到三句人话,第一句就是结论,不写标题、不写行动,不给这件事起名字;② **按对象分段**——问题里点到几个人或几个子问题(妈妈、爸爸、他们怎么对我、什么时候),开场之后每个各写一段,二级标题用问题里的词(`## 你和妈妈`、`## 你和爸爸`、`## 他们怎么对你`);问父母时妈妈和爸爸分开写;只问一个人或一件事时不拆段;③ **每段先说最强的信号、后依据**(2026-10-01 BUG-1164 改,取代「这个人是什么样、你们怎么相处、事情会怎么走」)——先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落;受冲信号两个及以上时(2026-10-02 产品改口径,盘分不出父母在不在身边、几段感情、有没有病):先说这条线有压力或距离的迹象,括号里给依据,再给现实里可能的范围(「现实里可能是分开、离得远,也可能只是关系紧、话少」),不替用户断定他自己更清楚的事实,也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;段末一句请用户说说实际情况(「实际是哪一种,告诉我一句,我按真实情况再看。」),全篇最多一处;婚姻写「感情这条线上有波折的迹象」而不是「可能不止一段」,健康写「身体这条线有压力的迹象」而不是「底子不差」;**不预设处境**:有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边都不预设,性别未知时不用「他」「她」称伴侣和孩子;再在括号里给撑住这段判断的盘上依据(宫、宫主、代表星、分盘、大运);问的是别人时写那个人在你盘上对应的宫位与代表星说明了什么,不写那个人的心思(BUG-1070 的目的保留);④ **行动**——一到三条破折号短句,本命解读里只在最后一节出现一次。<br>**人话自检**:把每句话括号里的内容删掉,一个完全不懂占星的人仍能看懂。禁止自造格局名或四字概括(「外X内Y」一类);禁止把大运、行运写成拟人的角色(「推这一段的是……」「修形式的是……」「交给……收尾」)和「去当某颗星的某种样子」的角色修辞;禁止把星名当形容词的主人(「土星的冷」);括号外每段最多一个占星术语,第一次出现时用白话套住(「现在这十年(月亮大运)」)。**推断边界**:空宫不单独下结论,要说它的宫主落在哪、什么状态(BUG-1134)。<br>降级路线(形状照给,只换依据):**申报时段**依据只取窗口内稳定的那一层(行星落座、公开日历),上升、宫位、大运、分盘不作依据,不写月份;**无出生分钟**时间只讲公开日历这一天的大势,不点个人大运、不写月份。<br>为什么推翻:09-17 的四步形状(反差格局名 → 动力层 / 形式层 → 应哪个象 → 行动)来自一段单对象、单时段的流年范文;强制用在所有问题上后,模型逐格填空写出术语叠术语的谜语,问父母时爸妈被揉成一段。
## 好 / 坏对照
@@ -401,7 +401,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
**追问轮直接回答(2026-09-27,BUG-1071,D3;2026-10-01 取消字数上限)。** 本会话已经有过一轮解读之后,后面的问题按直接回答写:第一句回答问的那句话(是非题先答是 / 不是 / 看情况),讲清楚为准、不限字数,只引与这句话直接相关的盘面事实(够说明就停),不写二级标题、不写行动清单、不重述上一轮已经写过的结构。只有用户明确换了领域或要求「完整看一下」才回到完整形状。「追问轮」由服务端按「本会话已有回答」判定,不用正则猜用户意图(BUG-976/977 口径)。
**用户纠正盘上判断(2026-10-01,BUG-1166)。** 用户说「我爸其实没怎么管过我」这类话时,追问轮先认下这件事,再说盘上哪几条和它对得上、原来哪句读偏了;不为原判断辩护,不写「盘上显示你们关系不错」。
**用户纠正盘上判断(2026-10-01,BUG-1166;10-02 补)。** 用户说「我爸其实没怎么管过我」这类话时,追问轮先认下这件事,再只说盘上真正对得上的那几条、原来哪句读偏了;盘本身分不出的直说「盘本身分不出这一点」,不把普通配置说成必然;不为原判断辩护,不写「盘上显示你们关系不错」。
**示例只演示口气与结构(2026-10-01,BUG-1165 / BUG-1168)。** 提示词和本文件里的示例,括号里的盘面依据只用不带读法含义的占位(「(4 宫主落 10 宫)」可以,「逆行 → 反复」不可以);示例不预设读者的处境(已婚、在上班、做专业工作、父母在身边)。模型会把示例里的读法和处境当规则照抄。
@@ -409,7 +409,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
|---|---|---|
| (用户问「我妈是不是不太在意我」)你其实要的是被听见,她给的是安排;你是否需要的是一种被看见的方式…… | 不是。(4 宫主落 10 宫)她把心力放在你的前途上——不是不在意,是不会用你想要的方式在意。 | 是非题第一句答是 / 不是;依据是宫位,不是读心;不把问题改写成需求。2026-10-01(BUG-1165)起示例括号只放依据占位,不带读法:旧示例「宫主水星逆行」被模型抄成「逆行 = 反复、打回来」。 |
| (首轮)开场三条行动 + `## 先回答你的问题` 再答一遍 + 表格再列一遍 + `## 这周可以做的一件事` 再给三条 | 开场(第一句就是结论)→ 按对象分段 → `## 盘上依据`(只写新依据)→ `## 时间怎么看` → `## 这周可以做的一件事`(三条,一次) | 同一判断只说一遍,行动只给一遍。 |
| (问「我和父母关系如何」)你和父母是「外热内冷」:推着这段关系的是金星大运,土星子运负责收尾;别去应火星的「硬」,去当金星的「和」。(改写示意) | 你和妈妈这条线近;爸爸这一面盘上受冲多,他可能不常在你身边。<br>## 你和妈妈<br>她离你近,对你的事上心。(4 宫主落 10 宫)<br>## 你和爸爸<br>爸爸这一面可能比较远:他可能不常在你身边。如果实际情况不是这样,告诉我,我按真实情况重看。(9 宫与太阳这一组的两条受冲)<br>## 他们怎么对你<br>…… | 坏的那句自造格局名、大运写成角色、星名当形容词,爸妈揉成一段,不懂占星的人一句也看不懂(BUG-1132)。
| (问「我和父母关系如何」)你和父母是「外热内冷」:推着这段关系的是金星大运,土星子运负责收尾;别去应火星的「硬」,去当金星的「和」。(改写示意) | 你和妈妈这条线近;爸爸这条线盘上有距离的迹象。<br>## 你和妈妈<br>她离你近,对你的事上心。(4 宫主落 10 宫)<br>## 你和爸爸<br>爸爸这条线有距离的迹象(9 宫与太阳这一组的两条受冲)。现实里可能是他不常在你身边,也可能人在、只是话少、关系紧。实际是哪一种,告诉我一句,我按真实情况再看。<br>## 他们怎么对你<br>…… | 坏的那句自造格局名、大运写成角色、星名当形容词,爸妈揉成一段,不懂占星的人一句也看不懂(BUG-1132)。
| 坏 | 好 | 为什么 |
|---|---|---|
@@ -1,5 +1,6 @@
import type { ConsultationDomain } from "./consultation-domain-registry.ts";
import type { ProfileGender } from "./profile-gender.ts";
import { AFFLICTION_RANGE_RULE } from "./consultation-thinking-plan.ts";
/**
* Condensed consultation checklists (TASK-consult-evidence-card-v2-20260927
@@ -58,7 +59,7 @@ export const CONDENSED_SHARED_READING_SOURCE = "consultation shared reading (TAS
export const CONDENSED_SHARED_READING_LINES: readonly string[] = [
"先看受冲,再看旺弱。旺弱和力量(入旺、入庙、Shadbala、SAV)只说明这颗星自己有没有力气,不说明它管的那件事顺不顺、那个人在不在你身边。",
"受冲信号,每条算一个:①宫、宫主或代表星与罗睺、计都、土星、火星同宫(星行 conjunct_with、宫行 occupants),或被土星、火星照(aspected_by);②宫主落 6、8、12 宫,或落在从该宫数起的第 6、8、12 宫;③宫主或代表星是本上升的功能凶星(functional_malefics,父母行 functional);④落陷(status)或燃烧(combust);⑤对应分盘里同号宫有凶星,或宫主落分盘的 6、8、12 宫。",
"同一个人或同一件事有两个及以上受冲信号:这一段第一句先写这一面,用「可能」,括号里写是哪几条;只有一个:补充一句;没有:不写。吉的一面同样要有依据,吉凶都有时两面都说。",
`${AFFLICTION_RANGE_RULE}只有一个:补充一句;没有:不写。吉的一面同样要有依据,吉凶都有时两面都说。`,
"力量大的星如果本身是 6、8、12 宫主,它带来的麻烦也有力量,不得因为「强」就说「没事」。",
"不写没有区分度的推论:逆行不等于「反复、打回来」;宫主不在本宫不等于「往外跑」;某星不在某宫不等于「离得远」;空宫不单独下结论。",
"不断言死亡、离婚、疾病诊断、流产、不孕。",
@@ -79,9 +80,10 @@ export const CONSULT_BRIEF_CHECKLIST_SOURCE = "consultation checklist (TASK-cons
export const CONSULTATION_CONDENSED_CHECKLISTS: Readonly<Partial<Record<ConsultationDomain, readonly string[]>>> = {
career: [
"先讲这张盘一生的事业形态:公众型还是幕后型、靠什么出头(AL 落宫、10 宫与 10 宫主、太阳、入旺入庙星坐角宫);受冲只说阻力和代价,不改写事业形态。",
"必看:D1 10 宫与 10 宫主、AmK;卡上 d9 段确认 10 宫主与 AmK 的旺弱、Vargottama、D1/D9 反转;D10 上升与 10 宫;AL 与 A10;10 宫 SAV 与木星、土星所在宫的 SAV。",
"时间:Vimshottari(MD/AD/PD)与 Narayana 双轨同向才谈应期,看大运主与 10 宫、10 宫主、D10 的关系;每颗星先按功能吉凶定性。",
"分三层说,不得合成一句「事业机会」:①接触(消息、面试、初步接洽)②结构性机会(合同、正式职位、长期项目)③公开落地(发布、到账、公开的职位与名声)。",
"分三层说,不得合成一句「事业机会」:①机会出现(消息、邀约、初步接洽)②成形(合同、合作、长期项目)③公开落地(发布、到账、被看见、名声)。",
"禁写:本命承诺弱时,不得因一段大运或一次行运断言「事业必成」;不得把接触窗写成落地窗。",
"AL 落第几宫要说出来(落 10 宫 = 公众形象就是事业);10 宫主、AmK 的受冲按通用读法。",
"卡外按需补取一次:Karakamsha、宫主链、D1→D9→D10 联动、Argala、Chara 大运;KP 精确宫头仍 blocked,只作参考、不作依据。",
@@ -92,7 +94,7 @@ export const CONSULTATION_CONDENSED_CHECKLISTS: Readonly<Partial<Record<Consulta
"分三层说:①心动接触:5 宫、5 宫主、Punarphoo 观察 ②关系成对:7 宫主、DK、UL ③社会法律落地:D9 + UL + Vivah Saham + 双大运同向。",
"时间:Vimshottari 与 Narayana 双轨;双重过运(木星、土星同时激活 7 宫 / 7 宫主 / DK / UL)只是激活窗,不是事件日。",
CONDENSED_GENDER_LINES.unknown,
"7 宫主或金星与罗睺、计都同宫,火星照 7 宫,UL 第 2 宫有凶星,伴侣代表星落 6、8、12:受冲两个及以上就写「感情可能不止一段 / 可能有断续」,只作可能;不预设用户现在有伴侣或单身。",
"7 宫主或金星与罗睺、计都同宫,火星照 7 宫,UL 第 2 宫有凶星,伴侣代表星落 6、8、12:受冲两个及以上就写「感情这条线上有波折的迹象」,再给范围(可能是来得晚、有分合,也可能是同一段关系里摩擦多),不断定有几段;不预设用户现在有伴侣或单身。",
"禁写:5 宫主大运 ≠ 法律婚;金星过本命月亮 ≠ 心动月;Punarphoo 不得写成结婚;土星回到本命月亮是观察项,不是结婚。",
"卡外按需补取一次:Chara 大运、KP 7 宫副主星(KP 精确宫头仍 blocked,只作参考、不作依据)。",
],
@@ -108,7 +110,7 @@ export const CONSULTATION_CONDENSED_CHECKLISTS: Readonly<Partial<Record<Consulta
// §5 T2 (decision D5); field names were adapted to the card's real placement only.
parents: [
"父亲:9 宫、9 宫主、太阳、PiK(卡上有才看);母亲:4 宫、4 宫主、月亮、MK;D12 同号宫及其宫主(roles.*.d12_house)。看 roles.father/mother 的 lord_rows、significator_rows。父亲、母亲各自判断,依据不混用。",
"先判断「在不在身边、近不近」,再谈性格:受冲信号两个及以上,这一段第一句写「爸爸(妈妈)可能不常在你身边 / 很早就有距离 / 关系断断续续」,不得写成「有分量」「站得住」。",
"先看这条线有没有压力或距离的迹象,再谈性格:受冲信号两个及以上按通用读法写范围(例:「现实里可能是他不常在身边,也可能人在、只是话少、关系紧」),盘分不出父母在不在身边,不替用户断定。",
"4 宫、4 宫主、月亮都落双体星座(双子、处女、射手、双鱼),且有受冲信号:可以提「照顾你长大的可能不止一位」,只作可能。",
"不拿太阳、月亮的旺弱代替关系好坏。",
"禁写:凭单一信号断定父母离婚、去世、生病。",
@@ -149,7 +151,7 @@ export const CONSULTATION_CONDENSED_CHECKLISTS: Readonly<Partial<Record<Consulta
// Replaces the English health-timing-strict route in the web consultation
// only; reports keep the router's route.
health: [
"先讲体质底子:命宫、命主、月亮、太阳的强弱与受冲。命主兼 6、8 宫主,或与 6、8 宫主同宫,底子偏弱、需要长期照顾。",
"先讲体质底子:命宫、命主、月亮、太阳的强弱与受冲。受冲两个及以上写「身体这条线有压力的迹象」并给范围(可能是容易累、恢复慢,也可能是某些阶段压力集中),不写「底子不差」,不诊断。",
"必看:6 宫(疾病)、8 宫(慢性、手术)、12 宫(住院)及宫主;土星、火星;D6、D8、D30 上命主、6 宫主、8 宫主落第几宫、和谁同宫。",
"宫位对应的身体部位只作提示(如 4 宫胸、心、肺),不写成诊断。",
"分三层:一生底子;压力窗(当前大运子运激活 1、6、8、12 宫主);恢复窗。",
+17 -3
View File
@@ -371,13 +371,27 @@ export function consultationContinuePrompt(output: string): string {
*/
const ANSWER_FROM_EVIDENCE = "拿到本轮计算结果后直接写给用户的回答,只用结果里的盘面事实下判断;调用工具前后都不要写过程说明。";
/**
* What to write when one person or matter carries two or more affliction
* signals. Product decision 2026-10-02 (after the celebrity backtest: present
* and absent fathers score the same signal count, control false positives
* went 1 -> 15): say the line shows pressure or distance, give the reality as a
* range, never decide a biographical fact the user knows better, never write
* the opposite reassurance, and ask once. Defined here only; the shared
* reading and the answer shape (NATAL_SECTION_RULE) both quote it.
*/
export const AFFLICTION_RANGE_RULE = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(括号里写是哪几条),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处。";
/**
* What each per-person section says (TASK-consult-no-presupposition-and-
* backtest-20261001 D1/D2/D7, BUG-1164; replaces 「这个人是什么样、你们怎么
* 相处、事情会怎么走」, which presupposed the person is there and in daily
* contact). Defined here once; the shared voice quotes this constant.
* contact). Defined here once; the shared voice quotes this constant. Since
* the product decision of 2026-10-02 the two-or-more-signals part is
* AFFLICTION_RANGE_RULE (pressure signs + a range + one ask, never a
* biographical verdict), shared with the shared reading.
*/
export const NATAL_SECTION_RULE = "每段先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落。受冲信号两个及以上时,这一段第一句就写这一面,用「可能」,段末用一句话请用户确认(例:「如果实际情况不是这样,告诉我,我按真实情况重看。」),全篇这类确认最多一处。不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。";
export const NATAL_SECTION_RULE = `每段先说盘上最强的一两个信号说明了什么;盘上没有依据的方面不写,不用日常场景凑段落。${AFFLICTION_RANGE_RULE}请用户说实际情况的那句例:「实际是哪一种,告诉我一句,我按真实情况再看。」不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。`;
/**
* Natal first-turn shape in the user turn (VOICE §7; TASK-consult-plain-
@@ -402,7 +416,7 @@ export function natalAnswerShapeInstruction(): string {
}
/** D4 / BUG-1166: the user corrects an earlier reading; acknowledge it, never defend the reading. */
export const FOLLOW_UP_CORRECTION_RULE = "用户纠正了前面的判断(比如说父亲其实不常在身边):先认下这件事,再说盘上哪几条和它对得上、原来哪句读偏了;不为原判断辩护。";
export const FOLLOW_UP_CORRECTION_RULE = "用户纠正了前面的判断(比如说父亲其实不常在身边):先认下这件事,再只说盘上真正对得上的那几条、原来哪句读偏了;盘本身分不出的,直说「盘本身分不出这一点」,不把普通配置说成必然;不为原判断辩护。";
/**
* Follow-up turn shape (TASK-consult-answer-the-question-20260927 D3 /
+3 -3
View File
@@ -39,13 +39,13 @@ ANSWER SHAPE (natal / general / declared-window 三种模式共用)
- 无出生分钟(no birth minute):时间只讲公开日历这一天的大势,不点个人大运,不写月份;能做的事取自公开日历,不取自个人盘。
Good (父母题首轮,按对象分段;括号里是依据的占位,只演示口气与结构,不是读法):
你和妈妈这条线近、来往多;爸爸这一面盘上受冲多,他可能不常在你身边。
你和妈妈这条线近、来往多;爸爸这条线盘上有距离的迹象。
## 你和妈妈
她离你近,对你的事上心,你们之间来往多。(4 宫主落 10 宫;月亮在 10 宫)
## 你和爸爸
爸爸这一面可能比较远:他可能不常在你身边,或者有一段时间不在。如果实际情况不是这样,告诉我,我按真实情况重看。(9 宫与太阳这一组的两条受冲)
爸爸这条线有距离的迹象(9 宫与太阳这一组的两条受冲)。现实里可能是他不常在你身边,也可能人在、只是话少、关系紧。实际是哪一种,告诉我一句,我按真实情况再看。
## 他们怎么对你
两边给你的不一样:妈妈这一头给得多,爸爸这一头给得少。
两边给你的不一样:妈妈这一头来往多,爸爸这一头要看实际情况再说。
## 时间怎么看
现在这十年(月亮大运,到 2034 年 2 月)和妈妈的来往会比以前多。
## 这周可以做的一件事
@@ -108,7 +108,11 @@ test("T2: every domain section, in order, and no domain without a list", () => {
for (const line of CONSULTATION_CONDENSED_CHECKLISTS[domain]!) assert.ok(section.text.includes(line), `${domain}: ${line.slice(0, 20)}`);
}
const parents = CONSULTATION_CONDENSED_CHECKLISTS.parents!.join("\n");
assert.match(parents, /不得写成「有分量」「站得住」/);
// 原值: assert.match(parents, /不得写成「有分量」「站得住」/)(父母第 2 行要求第一句写「可能不常在你身边」)
// 新值: 第 2 行按通用读法写范围、不断定在不在身边;「有分量」「站得住」的禁令移到通用读法(AFFLICTION_RANGE_RULE)
// 原因: 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在、几段感情、有没有病)
assert.match(parents, /盘分不出父母在不在身边,不替用户断定/);
assert.doesNotMatch(parents, /可能不常在你身边 \/ 很早就有距离/);
assert.match(parents, /照顾你长大的可能不止一位/);
assert.match(CONSULTATION_CONDENSED_CHECKLISTS.health!.join("\n"), /这不是医疗意见/);
assert.match(CONSULTATION_CONDENSED_CHECKLISTS.children!.join("\n"), /孩子性别不写/);
@@ -116,7 +120,11 @@ test("T2: every domain section, in order, and no domain without a list", () => {
test("T3: career, marriage and wealth each carry their affliction line", () => {
assert.ok(CONSULTATION_CONDENSED_CHECKLISTS.career!.some((line) => line.startsWith("AL 落第几宫要说出来")));
assert.ok(CONSULTATION_CONDENSED_CHECKLISTS.marriage!.some((line) => line.includes("感情可能不止一段 / 可能有断续") && line.includes("不预设用户现在有伴侣或单身")));
// 原值: 婚姻受冲行写「感情可能不止一段 / 可能有断续」
// 新值: 写「感情这条线上有波折的迹象」+ 范围,不断定有几段
// 原因: 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在、几段感情、有没有病)
assert.ok(CONSULTATION_CONDENSED_CHECKLISTS.marriage!.some((line) => line.includes("感情这条线上有波折的迹象") && line.includes("不断定有几段") && line.includes("不预设用户现在有伴侣或单身")));
assert.ok(!CONSULTATION_CONDENSED_CHECKLISTS.marriage!.join("\n").includes("可能不止一段"));
assert.ok(CONSULTATION_CONDENSED_CHECKLISTS.wealth!.some((line) => line.startsWith("8 宫也管版税、继承、意外之财")));
for (const domain of ["career", "marriage", "wealth"] as const) {
const lines = CONSULTATION_CONDENSED_CHECKLISTS[domain]!;
@@ -93,7 +93,10 @@ test("each condensed list is 5–8 lines", () => {
test("the writer's prompt carries the condensed list with the three layers and the forbidden statements, not the long checklist", async () => {
const required: Record<"career" | "marriage" | "wealth", string[]> = {
career: ["接触", "结构性机会", "公开落地", "事业必成", "d9 段", "KP 精确宫头仍 blocked"],
// 原值: career: ["接触", "结构性机会", "公开落地", …]
// 新值: 三层改为「机会出现 / 成形 / 公开落地」,加「事业形态」行;不得再有「面试」「职位」
// 原因: 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在、几段感情、有没有病);第 3 层的「面试 / 正式职位」把读者设成求职者(BUG-1168)
career: ["接触", "机会出现", "成形", "公开落地", "事业形态", "事业必成", "d9 段", "KP 精确宫头仍 blocked"],
marriage: [
"心动接触", "关系成对", "社会法律落地",
"5 宫主大运 ≠ 法律婚", "金星过本命月亮 ≠ 心动月", "Punarphoo 不得写成结婚", "土星回到本命月亮是观察项,不是结婚",
@@ -113,6 +116,7 @@ test("the writer's prompt carries the condensed list with the three layers and t
assert.ok(prompt.includes(line.slice(0, 24)), `${domain}: ${line.slice(0, 24)}`);
}
for (const phrase of required[domain]) assert.ok(prompt.includes(phrase), `${domain}: ${phrase}`);
if (domain === "career") assert.doesNotMatch(CONSULTATION_CONDENSED_CHECKLISTS.career!.join("\n"), /面试|职位|简历/);
for (const phrase of longChecklist) assert.equal(prompt.includes(phrase), false, `${domain}: long checklist text "${phrase}"`);
// The shared baseline still travels with the result.
assert.ok(prompt.includes("Shared mandatory baseline"), `${domain}: shared baseline`);
@@ -6,6 +6,11 @@ import { readdirSync, readFileSync } from "node:fs";
import test from "node:test";
import {
CONDENSED_SHARED_READING_LINES,
CONSULTATION_CONDENSED_CHECKLISTS,
} from "../src/lib/consultation-condensed-checklist.ts";
import {
AFFLICTION_RANGE_RULE,
FOLLOW_UP_CORRECTION_RULE,
NATAL_SECTION_RULE,
followUpAnswerShapeInstruction,
@@ -16,7 +21,11 @@ import { productConversationVoice } from "../src/mastra/product-voice.ts";
const OLD_SECTION_RULE = /这个人是什么样、你们怎么相处、事情会怎么走/;
const SECTION_RULE_TEXT = "每段先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落。受冲信号两个及以上时,这一段第一句就写这一面,用「可能」,段末用一句话请用户确认(例:「如果实际情况不是这样,告诉我,我按真实情况重看。」),全篇这类确认最多一处。不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。";
// 原值: SECTION_RULE_TEXT = T1 定稿句(「包括这个人在不在你身边、近不近」「受冲…第一句就写这一面,用『可能』,段末…请用户确认」)
// 新值: 受冲两个及以上的写法改由 AFFLICTION_RANGE_RULE 定义(压力迹象 + 范围 + 请用户说一句实际情况,不断定传记事实、不写反向安慰)
// 原因: 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在、几段感情、有没有病)
const AFFLICTION_RANGE_TEXT = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(括号里写是哪几条),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处。";
const SECTION_RULE_TEXT = `每段先说盘上最强的一两个信号说明了什么;盘上没有依据的方面不写,不用日常场景凑段落。${AFFLICTION_RANGE_TEXT}请用户说实际情况的那句例:「实际是哪一种,告诉我一句,我按真实情况再看。」不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。`;
function source(relative: string) {
return readFileSync(new URL(`../${relative}`, import.meta.url), "utf8");
@@ -31,9 +40,13 @@ function promptSources() {
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)", () => {
test("T1: each section leads with the strongest signal, gives pressure signs as a range, asks once, presupposes nothing (BUG-1164)", () => {
assert.equal(AFFLICTION_RANGE_RULE, AFFLICTION_RANGE_TEXT);
assert.equal(NATAL_SECTION_RULE, SECTION_RULE_TEXT);
for (const point of ["在不在你身边", "请用户确认", "不预设", "是否在上班、做哪类工作", "性别未知时不用「他」「她」称伴侣和孩子", "全篇这类确认最多一处"]) {
// 原值: 要点 ["在不在你身边", "请用户确认", …, "全篇这类确认最多一处"]
// 新值: 要点改为压力迹象、范围、不断定、请用户说实际情况、最多一处
// 原因: 产品 2026-10-02 改口径
for (const point of ["压力或距离的迹象", "给一个范围", "不替用户断定", "请用户说说实际情况", "不预设", "是否在上班、做哪类工作", "性别未知时不用「他」「她」称伴侣和孩子", "全篇这类请求最多一处"]) {
assert.ok(NATAL_SECTION_RULE.includes(point), point);
}
const body = natalAnswerShapeBody();
@@ -44,7 +57,7 @@ test("T1: each section leads with the strongest signal, asks to confirm an affli
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);
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);
});
@@ -74,10 +87,32 @@ test("T2b: career examples carry no job shape (BUG-1168)", () => {
});
test("T3: a correction turn acknowledges first and does not defend the reading (BUG-1166)", () => {
// 原值: "…先认下这件事,再说盘上哪几条和它对得上、原来哪句读偏了;不为原判断辩护。"
// 新值: 只说真正对得上的几条;盘分不出的直说「盘本身分不出这一点」,不把普通配置说成必然
// 原因: 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在、几段感情、有没有病);第一轮回测纠正追问把用户的话全部「对上」(BUG-1166 副作用)
assert.equal(
FOLLOW_UP_CORRECTION_RULE,
"用户纠正了前面的判断(比如说父亲其实不常在身边):先认下这件事,再说盘上哪几条和它对得上、原来哪句读偏了;不为原判断辩护。",
"用户纠正了前面的判断(比如说父亲其实不常在身边):先认下这件事,再只说盘上真正对得上的那几条、原来哪句读偏了;盘本身分不出的,直说「盘本身分不出这一点」,不把普通配置说成必然;不为原判断辩护。",
);
assert.ok(followUpAnswerShapeInstruction().includes(FOLLOW_UP_CORRECTION_RULE));
assert.ok(!natalAnswerShapeInstruction().includes(FOLLOW_UP_CORRECTION_RULE));
});
test("2026-10-02: the shared reading quotes the range rule; parents, marriage, health and career lists decide no biography", () => {
assert.ok(CONDENSED_SHARED_READING_LINES[2]!.startsWith(AFFLICTION_RANGE_RULE));
const parents = CONSULTATION_CONDENSED_CHECKLISTS.parents!.join("\n");
assert.doesNotMatch(parents, /可能不常在你身边 \/ 很早就有距离/);
const marriage = CONSULTATION_CONDENSED_CHECKLISTS.marriage!.join("\n");
assert.match(marriage, /感情这条线上有波折的迹象/);
assert.doesNotMatch(marriage, /可能不止一段/);
const health = CONSULTATION_CONDENSED_CHECKLISTS.health!.join("\n");
assert.match(health, /身体这条线有压力的迹象/);
assert.match(health, /不写「底子不差」/);
const career = CONSULTATION_CONDENSED_CHECKLISTS.career!;
assert.ok(career[0]!.startsWith("先讲这张盘一生的事业形态:公众型还是幕后型"));
assert.match(career.join("\n"), /受冲只说阻力和代价,不改写事业形态/);
assert.doesNotMatch(career.join("\n"), /面试|职位|简历/);
// The shape and the voice example carry no presence verdict.
assert.doesNotMatch(productConversationVoice, /他可能不常在你身边,或者有一段时间不在|如果实际情况不是这样/);
assert.match(productConversationVoice, /爸爸这条线有距离的迹象/);
});
@@ -232,7 +232,10 @@ test("the pinned Good examples split by person and stay plain; the coined-patter
// 新值: 括号只放占位(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]*如果实际情况不是这样,告诉我,我按真实情况重看。/);
// 原值(2): /他可能不常在你身边[\s\S]*如果实际情况不是这样,告诉我,我按真实情况重看。/
// 新值(2): 爸爸段写「有距离的迹象」+ 范围 + 「实际是哪一种,告诉我一句」,不断定在不在身边
// 原因(2): 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在身边)
assert.match(parents, /爸爸这条线有距离的迹象[\s\S]*现实里可能是他不常在你身边,也可能人在、只是话少、关系紧。实际是哪一种,告诉我一句,我按真实情况再看。/);
assert.doesNotMatch(parents, /分量重|标杆|爸爸的意见|靠做事/);
const career = voice.slice(careerStart, badStart);
assert.doesNotMatch(career, /## 你和/);