diff --git a/frontend/docs/VOICE.md b/frontend/docs/VOICE.md
index e248b6d6..40fda313 100644
--- a/frontend/docs/VOICE.md
+++ b/frontend/docs/VOICE.md
@@ -97,7 +97,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
工具结果里的清单(受冲信号编号与条数、「分三件事看」的层名、「旺弱不说明顺不顺」这类规则、「从某宫数起第几宫」)是让模型判断用的,回答里只说判断结果。真机婚恋回答出现过「先说心动这一层」「再看成对居住的那一层」「宫主火星落在从它数起的第 6 位」「这条只说这颗星自己有劲,不说明事情就顺」,以及给 yogakaraka 自造的「合力星」——读者一句都看不懂。
- 婚恋三件事的说法:遇到喜欢的人、谈恋爱 / 定下来、长期在一起 / 结婚、领证;只说用户问到的那件,不报层名。
-- 括号里的依据最多两条,挑最能撑住这句话的,用白话写。
+- 说出来的依据最多两条,挑最能撑住这句话的,用白话写进句子(2026-10-27 起不再放段尾括号,见第七条原则的 C1 补充)。
- 没有日常中文叫法的字段按 `PLAIN_SPEECH_RULE`(`consultation-thinking-plan.ts`,只定义这一处)的对照说:功能凶星 → 对你这个上升来说偏添麻烦的星;yogakaraka → 不提或「这张盘里最帮你的那颗星」,不自造译名;DK → 代表伴侣的星;落陷 → 力气弱……对照里没有的名词就不提。
## 内容审核的三句话(2026-09-30,合规轮)
@@ -355,7 +355,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
4. **同一约束换着说。** 边界句必须在交付/采用轮完整出现一次;中间轮不要逐字复读法务稿。
5. **口语化但不轻佻。** 短句、具体事实、少套话。
6. **希望纪律。** 占星结果是客观的,表达是主观的。不顺就说不顺,但每次都要说清这段时间能做什么,用一句话说进正文,不变成行动清单。盘上确有转机、且 `answer_policy.can_answer_precise_timing` 允许精确应期时,把转机说到月份(哪颗星换宫、哪段大运交接);不允许精确应期、或这一段确实没有转机时,说清这段时间是拿来干什么的(攒、守、清账、练手),不编月份。不是每个人都有转机,但每个人这段时间都有能做的事。不写「一切都会好」「相信自己」「加油」「你值得更好的」「宇宙自有安排」。(2026-10-01 起不再用「扮演哪个象」的说法,TASK-consult-plain-answer-20261001 D5。2026-10-06 起这句话不再落到固定行动节。)
-7. **回答形状:先答,再按对象分段落(2026-10-06,BUG-1244,推翻 2026-10-01 D2 的按对象二级标题和三个固定节,以及 2026-09-27 D8 里「行动只在最后一节」的标题部分;BUG-1073「同一件事只说一遍」保留)。** 三种模式(本命 / 无出生分钟 / 申报时段)共用。首轮通常三到六段:讲清结论、最关键的一两条依据,相关时带上时间;不设硬截断。追问轮仍讲清楚为准、不限字数。同一件事只说一遍。① **先答**——开场一到三句人话,第一句就是结论,不写标题、不写行动,不给这件事起名字;**开场同样守受冲规则**(2026-10-02,BUG-1177):不替用户断定在不在身边、几段感情、有没有病,不写「距离感明显」「不是能硬扛的类型」这类程度结论,只说哪条线顺、哪条线有压力迹象;② **按对象分段落**——问题里点到几个人或几个子问题(妈妈、爸爸、他们怎么对我、什么时候),开场之后每个各写一段,段首用人话点名(「你妈妈这边……」「爸爸那边……」),不写二级标题;问父母时妈妈和爸爸分开写;只问一个人或一件事时不拆段;③ **每段先说最强的信号、后依据**(2026-10-01 BUG-1164 改,取代「这个人是什么样、你们怎么相处、事情会怎么走」)——先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落;受冲信号两个及以上时(2026-10-02 产品改口径,盘分不出父母在不在身边、几段感情、有没有病):先说这条线有压力或距离的迹象,括号里给依据,再给现实里可能的范围(「现实里可能是分开、离得远,也可能只是关系紧、话少」),不替用户断定他自己更清楚的事实,也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;也不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边),这一方的行动建议也不写给他打电话、去问他(2026-10-02,BUG-1182);段末一句请用户说说实际情况(「实际是哪一种,告诉我一句,我按真实情况再看。」),全篇最多一处,问过之后别的段落不再另问;婚姻写「感情这条线上有波折的迹象」而不是「可能不止一段」,健康写「身体这条线有压力的迹象」而不是「底子不差」;**不预设处境**:有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边都不预设,性别未知时不用「他」「她」称伴侣和孩子;再在括号里给撑住这段判断的盘上依据(宫、宫主、代表星、分盘、大运),每段最多一处括号;问的是别人时写那个人在你盘上对应的宫位与代表星说明了什么,不写那个人的心思(BUG-1070 的目的保留);④ **行动不强制**——只有盘上确实指向一件和问题相关的具体事情时,才在话里顺口说一句,不列清单、不放固定位置、全篇最多一处;用户问「我该怎么办」或「怎么做」时,那一轮可以给几条。并列的点默认写成段落。只有用户要求列举,或确有三个以上候选时间窗时才用列表,且不用加粗标签加一句的格式。首轮正文不写二级标题,不写「这周可以做」「盘上依据」「时间怎么看」这类节名,也不用加粗短语冒充小标题。
**人话自检**:把每句话括号里的内容删掉,一个完全不懂占星的人仍能看懂。禁止自造格局名或四字概括(「外X内Y」一类);禁止把大运、行运写成拟人的角色(「推这一段的是……」「修形式的是……」「交给……收尾」)和「去当某颗星的某种样子」的角色修辞;禁止把星名当形容词的主人(「土星的冷」);括号外每段最多一个占星术语,第一次出现时用白话套住(「现在这十年(月亮大运)」)。**推断边界**:空宫不单独下结论,要说它的宫主落在哪、什么状态(BUG-1134)。
降级路线(形状照给,只换依据):**申报时段**依据只取窗口内稳定的那一层(行星落座、公开日历),上升、宫位、大运、分盘不作依据,不写月份;**无出生分钟**时间只讲公开日历这一天的大势,不点个人大运、不写月份。
为什么推翻:09-17 的四步形状(反差格局名 → 动力层 / 形式层 → 应哪个象 → 行动)来自一段单对象、单时段的流年范文;强制用在所有问题上后,模型逐格填空写出术语叠术语的谜语,问父母时爸妈被揉成一段。2026-10-01 改成按对象二级标题,再接三个固定节,真机上仍像一份汇报。2026-10-06 产品要求首轮改成聊天:取消全部二级标题,多人改成分段落、段首点名;行动不再强制交清单。
+7. **回答形状:先答,再按对象分段落(2026-10-06,BUG-1244,推翻 2026-10-01 D2 的按对象二级标题和三个固定节,以及 2026-09-27 D8 里「行动只在最后一节」的标题部分;BUG-1073「同一件事只说一遍」保留)。** 三种模式(本命 / 无出生分钟 / 申报时段)共用。首轮通常三到六段:讲清结论、最关键的一两条依据,相关时带上时间;不设硬截断。追问轮仍讲清楚为准、不限字数。同一件事只说一遍。① **先答**——开场一到三句人话,第一句就是结论,不写标题、不写行动,不给这件事起名字;**开场同样守受冲规则**(2026-10-02,BUG-1177):不替用户断定在不在身边、几段感情、有没有病,不写「距离感明显」「不是能硬扛的类型」这类程度结论,只说哪条线顺、哪条线有压力迹象;② **按对象分段落**——问题里点到几个人或几个子问题(妈妈、爸爸、他们怎么对我、什么时候),开场之后每个各写一段,段首用人话点名(「你妈妈这边……」「爸爸那边……」),不写二级标题;问父母时妈妈和爸爸分开写;只问一个人或一件事时不拆段;③ **每段先说最强的信号、后依据**(2026-10-01 BUG-1164 改,取代「这个人是什么样、你们怎么相处、事情会怎么走」)——先说盘上最强的一两个信号说明了什么,包括这个人在不在你身边、近不近;盘上没有依据的方面不写,不用日常场景凑段落;受冲信号两个及以上时(2026-10-02 产品改口径,盘分不出父母在不在身边、几段感情、有没有病):先说这条线有压力或距离的迹象,括号里给依据,再给现实里可能的范围(「现实里可能是分开、离得远,也可能只是关系紧、话少」),不替用户断定他自己更清楚的事实,也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;也不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边),这一方的行动建议也不写给他打电话、去问他(2026-10-02,BUG-1182);段末一句请用户说说实际情况(「实际是哪一种,告诉我一句,我按真实情况再看。」),全篇最多一处,问过之后别的段落不再另问;婚姻写「感情这条线上有波折的迹象」而不是「可能不止一段」,健康写「身体这条线有压力的迹象」而不是「底子不差」;**不预设处境**:有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边都不预设,性别未知时不用「他」「她」称伴侣和孩子;再给撑住这段判断的盘上依据(2026-10-27 issue #113 C1 起写进正文,见下方补充);问的是别人时写那个人在你盘上对应的宫位与代表星说明了什么,不写那个人的心思(BUG-1070 的目的保留);④ **行动不强制**——只有盘上确实指向一件和问题相关的具体事情时,才在话里顺口说一句,不列清单、不放固定位置、全篇最多一处;用户问「我该怎么办」或「怎么做」时,那一轮可以给几条。并列的点默认写成段落。只有用户要求列举,或确有三个以上候选时间窗时才用列表,且不用加粗标签加一句的格式。首轮正文不写二级标题,不写「这周可以做」「盘上依据」「时间怎么看」这类节名,也不用加粗短语冒充小标题。
**人话自检**:把每句话括号里的内容删掉,一个完全不懂占星的人仍能看懂。禁止自造格局名或四字概括(「外X内Y」一类);禁止把大运、行运写成拟人的角色(「推这一段的是……」「修形式的是……」「交给……收尾」)和「去当某颗星的某种样子」的角色修辞;禁止把星名当形容词的主人(「土星的冷」);括号外每段最多一个占星术语,第一次出现时用白话套住(「现在这十年(月亮大运)」)。**推断边界**:空宫不单独下结论,要说它的宫主落在哪、什么状态(BUG-1134)。
**C1 补充(2026-10-27,issue #113,产品拍板「R1 C1」;推翻本条「依据放段尾括号、每段最多一处括号」)**:依据写进正文,判断之后用半句白话说为什么(「因为管母亲的 4 宫(母亲、家和住处)这条线没有受冲……」),说出是哪颗星、哪个宫、它管生活里的哪一块;段尾不加括号把正文再用行话说一遍,括号只用来给宫位、术语套白话。宫位第一次出现必须带生活领域,十二宫叫法统一用 `HOUSE_LIFE_AREA_RULE` 那张表(例「8 宫(突发的变化、没摊开的事、别人的钱)」)。讲时间最多三件天象或运期变化,由近到远;入庙、本垣、小运这类词第一次出现带一句白话。用户自己的心只在问情绪、关系感受时读,问事业、钱、时间、某一天的运势时结尾不猜动机。为什么:产品负责人贴来的一段现行回答里,「落在第 9 宫」「走过你的第 8 宫」只有编号,段尾括号「(命主太阳力量足、落第 9 宫;土星在本垣)」把正文再说一遍,读者不知道 9 宫为什么等于方向感;竞品「像占星师」主要是因为把「为什么」说了出来(`docs/research/voice_vs_competitor_2026_10_27.md`)。
降级路线(形状照给,只换依据):**申报时段**依据只取窗口内稳定的那一层(行星落座、公开日历),上升、宫位、大运、分盘不作依据,不写月份;**无出生分钟**时间只讲公开日历这一天的大势,不点个人大运、不写月份。
为什么推翻:09-17 的四步形状(反差格局名 → 动力层 / 形式层 → 应哪个象 → 行动)来自一段单对象、单时段的流年范文;强制用在所有问题上后,模型逐格填空写出术语叠术语的谜语,问父母时爸妈被揉成一段。2026-10-01 改成按对象二级标题,再接三个固定节,真机上仍像一份汇报。2026-10-06 产品要求首轮改成聊天:取消全部二级标题,多人改成分段落、段首点名;行动不再强制交清单。
8. **事业题不猜行业,问用户(2026-10-02 产品决定,BUG-1176;取代同日「按星的本性看行业」两行)。** 事业答案只讲盘上的力量、阻力和时间;不替用户断定他做哪一行、是公众型还是幕后型(盘分不出,名人回测里演员、球星、总统的 10 宫系统都读成分析 / 顾问);A10、AL、太阳、10 宫主落 4、6、8、12 宫只说这条线的阻力或代价(要经手、要绕路、来得晚),不翻成「不在台前 / 不在台面上 / 不靠曝光 / 幕后 / 不是站台式 / 往深处做」,也不把落宫翻成行业(8 宫不等于研究、账目,12 宫不等于海外、幕后工作)(2026-10-02 第五轮前补,第四轮 9 / 18 份由这些落宫推出「不在台前」),不写「出头慢、越往后越稳、靠专业交付」这类人人适用的话,除非能指出具体盘面;事业这一段末尾用一句话问「你现在具体做哪一行、什么角色,告诉我一句,我按你的实际工作再看。」,这一句就是全篇唯一一处请用户说实际情况。用户说了自己的工作后,按这份工作读助力、阻力和时间,不说「盘早就显示你会做这一行」,不编依据。卡上的「传统格局,本站未验证,只作参考」只用来说明某颗星有力量,不单独下结论,不算受冲,也不抵消受冲(BUG-1175)。
## 好 / 坏对照
@@ -417,7 +417,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
| (校正会话上普通输入框仍可打字) | 正在打开生时校正… | 校正会话上普通输入框只有禁用态。打开失败时按钮写「重新打开生时校正」。 |
| 9月14日 · 生时校正 / 生时校正 · 9月14日 / 生时校正 · 09:35(创建钟点) | 生时校正 · 05:07 / 生时校正 · 05:00–05:15 / 生时校正 | 校正标题写结果:已确认优先于已采用,写 `生时校正 · HH:MM`;否则当前范围写 `生时校正 · HH:MM–HH:MM`(间隔号,不是连字符);都没有则只写「生时校正」。今日节奏仍是 `今日节奏 · M月D日`。同日不再用墙钟去重。副标题仍是最后活动时间。旧标题按结果批量改;手改过的名字不动。 |
| 这么晚还醒着,{名字}。直接说说最在意的问题吧。 | 夜里好,{名字} | 开场只一行、不追问、不写场景;提问交给输入框占位符。2026-10-01 起开场语是知心姐姐的口吻:只打招呼、关心,不评论用户(不写「夜猫子」「还没睡呀」——深夜打开的人可能失眠、可能心里有事,不需要被点评) |
-| 你更适合长期做一件事,而不是频繁换方向。眼下更值得把手上的事做稳,而不是立刻另起一摊。 | 今年事业上机会不少,但真正决定你走多远的是规矩和身体,不是人缘。
机会和帮你的人今年都在;同时事情多、要求严,身体也容易被拖累。(木星行运 10 宫;土星行运 6 宫)
9 月木星换宫之前,机会多、事情也不稳。 | 坏的那句像顾问报告:只有结论,没有盘上依据。好的那段是几段人话,依据在括号里,不写小标题,也不交行动清单。括号里是依据的占位。2026-10-01(BUG-1168)起事业示例不带职业形态:不写「升职」「专业能力」「做深现有工作」,旧示例让 18 份事业回测答案里 16 份把总统、球星、影星都写成「专业型 / 顾问 / 幕后」。2026-10-06(BUG-1244)起这段不再用二级标题。 |
+| 你更适合长期做一件事,而不是频繁换方向。眼下更值得把手上的事做稳,而不是立刻另起一摊。 | 今年事业上机会不少,但真正决定你走多远的是规矩和身体,不是人缘。
机会和帮你的人今年都在,因为木星今年走在你的 10 宫(事业、社会地位);同时事情多、要求严,身体也容易被拖累,因为土星走在 6 宫(工作里的麻烦、竞争、病痛、欠债)。
9 月木星换宫之前,机会多、事情也不稳。 | 坏的那句像顾问报告:只有结论,没有盘上依据。好的那段是几段人话,依据用「因为……」写进正文(2026-10-27 起,issue #113 C1),不写小标题,也不交行动清单。括号里是依据的占位。2026-10-01(BUG-1168)起事业示例不带职业形态:不写「升职」「专业能力」「做深现有工作」,旧示例让 18 份事业回测答案里 16 份把总统、球星、影星都写成「专业型 / 顾问 / 幕后」。2026-10-06(BUG-1244)起这段不再用二级标题。 |
| (用户说「你在说什么鬼」)抱歉让你困惑了,我再解释一下:从星盘来看,您的事业宫…… | 说直接点:现在这条路还值得走,但你得换个走法,不是换方向。哪句没看懂,指给我。 | 追问轮不重开一遍报告,也不道歉铺垫。一句白话结论 + 一句把球踢回去。 |
## 服务端探针 → Agent 题干
@@ -500,7 +500,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
本命类回答就是几段人话。技法审计和统一参数留在依据面板,不写进正文。历史消息里已经写出的二级标题仍按原样显示。
-首轮通常三到六段,不设硬截断。形状见上面第七条原则:先答,再按对象分段落(段首点名,每段先人话、括号里给依据,每段最多一处括号),相关时把时间写进段落。句子必须是完整句子,不得写成名词短语枚举。不写二级标题,也不交固定行动清单。
+首轮通常三到六段,不设硬截断。形状见上面第七条原则:先答,再按对象分段落(段首点名,每段先人话、再用「因为……」把依据写进正文,宫位带生活领域),相关时把时间写进段落。句子必须是完整句子,不得写成名词短语枚举。不写二级标题,也不交固定行动清单。
**首轮只说一遍(2026-09-27,BUG-1073,产品拍板 D8;2026-10-01 BUG-1132 改;2026-10-06 BUG-1244 再改)。** 开场就是回答,正文不再从「先回答你的问题」重来。2026-10-01 曾在按对象的段后面再接三个固定标题。2026-10-06 产品看了真机,认为固定标题加行动清单读起来像机器在汇报,所以首轮取消全部二级标题:多人改成分段落、段首点名;行动不再强制,盘上有具体指向才顺口一句。同一个判断仍只说一遍(BUG-1073 保留)。此前一份约 1,500 字的父母解读把同一个判断说了三遍、三条行动给了两遍,用户读到的是「说了很多没多说什么」。
@@ -512,13 +512,13 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、
| 坏 | 好 | 为什么 |
|---|---|---|
-| (用户问「我妈是不是不太在意我」)你其实要的是被听见,她给的是安排;你是否需要的是一种被看见的方式…… | 不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上——不是不在意,是不会用你想要的方式在意。 | 是非题第一句答是 / 不是;依据是宫位,不是读心;不把问题改写成需求。2026-10-01(BUG-1165)起示例括号只放依据占位,不带读法:旧示例「宫主水星逆行」被模型抄成「逆行 = 反复、打回来」。 |
-| (首轮)开场三条行动,再用小标题重答一遍,表格再列一遍,最后再交一份行动清单 | 开场(第一句就是结论)→ 按对象分段落、段首点名 → 依据写在该段括号里(每段最多一处)→ 相关时把时间写进段落。行动不强制,有具体指向才顺口一句 | 同一判断只说一遍。2026-10-06 起首轮不写二级标题,也不交行动清单(BUG-1244)。 |
+| (用户问「我妈是不是不太在意我」)你其实要的是被听见,她给的是安排;你是否需要的是一种被看见的方式…… | 不是。管母亲的 4 宫(母亲、家和住处)这条线没有受冲,4 宫的主星落在 10 宫(事业、社会地位)。她把心力放在你的前途上——不是不在意,是不会用你想要的方式在意。 | 是非题第一句答是 / 不是;依据是宫位,不是读心;不把问题改写成需求。2026-10-01(BUG-1165)起示例括号只放依据占位,不带读法:旧示例「宫主水星逆行」被模型抄成「逆行 = 反复、打回来」。 |
+| (首轮)开场三条行动,再用小标题重答一遍,表格再列一遍,最后再交一份行动清单 | 开场(第一句就是结论)→ 按对象分段落、段首点名 → 依据用「因为……」写进正文(2026-10-27 C1 起)→ 相关时把时间写进段落。行动不强制,有具体指向才顺口一句 | 同一判断只说一遍。2026-10-06 起首轮不写二级标题,也不交行动清单(BUG-1244)。 |
| (问「我和父母关系如何」)你和父母是「外热内冷」:推着这段关系的是金星大运,土星子运负责收尾;别去应火星的「硬」,去当金星的「和」。(改写示意) | 你和妈妈这条线近、来往多;爸爸这条线盘上有距离的迹象。
你妈妈这边,她离你近,对你的事上心,你们之间来往多。(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)
爸爸那边,爸爸这条线有距离的迹象(9 宫与太阳这一组的两条受冲)。现实里可能是他不常在你身边,也可能人在、只是话少、关系紧。实际是哪一种,告诉我一句,我按真实情况再看。 | 坏的那句自造格局名、大运写成角色、星名当形容词,爸妈揉成一段,不懂占星的人一句也看不懂(BUG-1132)。好的那几段分段落、段首点名,不写二级标题(BUG-1244)。
| 坏 | 好 | 为什么 |
|---|---|---|
-| 你的事业宫在第十宫,D10 上升与本命一致。当前大运土星、副运木星,从度数上看今明两年有岗位变动的窗口。下面进入统一参数与原始结构。 | 今年事业上机会不少,但真正决定你走多远的是规矩和身体,不是人缘。
机会和帮你的人今年都在;同时事情多、要求严,身体也容易被拖累。(木星行运 10 宫;土星行运 6 宫) | 先回人问的方向,再把依据放进括号;不写小标题,不交可以照抄的行动句。度数留在依据里。 |
+| 你的事业宫在第十宫,D10 上升与本命一致。当前大运土星、副运木星,从度数上看今明两年有岗位变动的窗口。下面进入统一参数与原始结构。 | 今年事业上机会不少,但真正决定你走多远的是规矩和身体,不是人缘。
机会和帮你的人今年都在,因为木星今年走在你的 10 宫(事业、社会地位);同时事情多、要求严,身体也容易被拖累,因为土星走在 6 宫(工作里的麻烦、竞争、病痛、欠债)。 | 先回人问的方向,再用「因为……」把依据写进正文;不写小标题,不交可以照抄的行动句。度数留在依据里。 |
| 事业、财富、变动窗口、土星大运 | 现在这十年(土星大运)偏向慢慢攒、靠规矩立住。 | 句子是完整句子,不是公文枚举;术语第一次出现用白话套住。 |
## 今日星语句库(2026-09-30,BUG-1109)
diff --git a/frontend/src/lib/consultation-thinking-plan.ts b/frontend/src/lib/consultation-thinking-plan.ts
index d99414d3..baa3fa2a 100644
--- a/frontend/src/lib/consultation-thinking-plan.ts
+++ b/frontend/src/lib/consultation-thinking-plan.ts
@@ -31,7 +31,7 @@ export const REPORT_HEADING = {
*/
export const CHECKLIST_ANALYSIS_ONLY_NOTE = "(分析用:本清单的层名、编号、计数和规则原句只决定你怎么判断,不写进回答,也不对用户复述;说给用户的是判断结果,用生活里的话。)";
-export const PLAIN_SPEECH_RULE = "分析清单只用来想,不用来说:工具结果里的清单(受冲信号的编号与条数、「分三件事看」的层名、「旺弱不说明顺不顺」这类规则原句、「从某宫数起第几宫」这类数法)只决定你怎么判断,不写进回答,也不对用户解释规则;说给用户的是判断的结果,用生活里的话。括号里的依据最多两条,挑最能撑住这句话的,用白话写,不把信号列全。盘上字段没有日常中文叫法时按这张对照说,对照里没有的名词就不提:功能吉星 → 对你这个上升来说帮得上忙的星;功能凶星 → 对你这个上升来说偏添麻烦的星;yogakaraka → 不提,或说这张盘里最帮你的那颗星,不自造译名;DK → 代表伴侣的星;UL、Vivah Saham、Punarphoo → 不提名字,说婚姻这条线、结婚的时机;AmK → 代表事业的星;MK、PiK、PK → 代表母亲、父亲、孩子的星;AL、A10 → 外人眼里的你、事业在外人眼里的样子;SAV、Shadbala → 只说强、中、弱;被照(aspected_by)→ 某星照着;燃烧(combust)→ 被太阳压住;落陷 → 力气弱;Narayana 大运 → 不提名字,说两套算法都指向这段时间。";
+export const PLAIN_SPEECH_RULE = "分析清单只用来想,不用来说:工具结果里的清单(受冲信号的编号与条数、「分三件事看」的层名、「旺弱不说明顺不顺」这类规则原句、「从某宫数起第几宫」这类数法)只决定你怎么判断,不写进回答,也不对用户解释规则;说给用户的是判断的结果,用生活里的话。说出来的依据最多两条,挑最能撑住这句话的,用白话写进句子里,不把信号列全。盘上字段没有日常中文叫法时按这张对照说,对照里没有的名词就不提:功能吉星 → 对你这个上升来说帮得上忙的星;功能凶星 → 对你这个上升来说偏添麻烦的星;yogakaraka → 不提,或说这张盘里最帮你的那颗星,不自造译名;DK → 代表伴侣的星;UL、Vivah Saham、Punarphoo → 不提名字,说婚姻这条线、结婚的时机;AmK → 代表事业的星;MK、PiK、PK → 代表母亲、父亲、孩子的星;AL、A10 → 外人眼里的你、事业在外人眼里的样子;SAV、Shadbala → 只说强、中、弱;被照(aspected_by)→ 某星照着;燃烧(combust)→ 被太阳压住;落陷 → 力气弱;Narayana 大运 → 不提名字,说两套算法都指向这段时间。";
/**
* The one statement of the first-turn natal answer shape (TASK-consult-
@@ -39,7 +39,7 @@ export const PLAIN_SPEECH_RULE = "分析清单只用来想,不用来说:工
* the shape quotes this, and the Chinese user-turn instruction below says the
* same thing.
*/
-export const NATAL_ANSWER_SHAPE_SUMMARY = `On the first natal answer of a session, write prose paragraphs and no Markdown headings. The first sentence is the conclusion. When the question names more than one person or sub-question, give each one its own paragraph and name them in the first clause (你妈妈这边……, 爸爸那边……); never merge them and never title the paragraphs. One person or one matter is not split by person. The tool's checklists are for reasoning only: never quote their layer names, signal counts, numbered rules or house-counting, and say card fields in the plain words of the 白话对照 rule. Separate paragraphs with a blank line; a single line break does not start a new paragraph on screen. Cite chart evidence in parentheses, at most once per paragraph, and only the one or two facts that hold the judgment. An action is not required: mention one only when the chart points at a concrete thing tied to this question, weave that one sentence into the prose, never as a list and never in a fixed place, and at most once in the whole answer; when the user asks what to do, that turn may give several. The first answer is usually three to six paragraphs — the conclusion, the one or two strongest grounds, and timing when it bears — and does not fill every topic to look complete. There is no hard cutoff. Say each thing once. Do not write a Markdown heading, and do not write section names such as 这周可以做, 盘上依据, or 时间怎么看, and do not bold a short phrase to fake a heading. Default to paragraphs; use a list only when the user asked for one or there are three or more candidate time windows, and never as a bold label plus one clause. Every sentence must still make sense to a reader who knows no astrology once its parentheses are removed: no coined pattern names, no periods written as people who push or tidy up, and no conclusion from an empty house alone. Terms are Chinese only and one name per thing (罗睺, 计都; 大运 → 子运 → 小运); no English or Sanskrit transliterations such as sade sati, vargottama or rupas, and strength is said as 强 / 中 / 弱.`;
+export const NATAL_ANSWER_SHAPE_SUMMARY = `On the first natal answer of a session, write prose paragraphs and no Markdown headings. The first sentence is the conclusion. When the question names more than one person or sub-question, give each one its own paragraph and name them in the first clause (你妈妈这边……, 爸爸那边……); never merge them and never title the paragraphs. One person or one matter is not split by person. The tool's checklists are for reasoning only: never quote their layer names, signal counts, numbered rules or house-counting, and say card fields in the plain words of the 白话对照 rule. Separate paragraphs with a blank line; a single line break does not start a new paragraph on screen. Give the chart reason inside the sentence as a plain half-clause (因为……) that names the planet or house and what part of life it governs, using only the one or two facts that hold the judgment; never close a paragraph with a parenthesis that repeats the body in jargon. Parentheses are only for glossing a house's life area or a term. The first time a house is named, gloss its life area from the house table (第 8 宫(突发的变化、没摊开的事、别人的钱)). Timing names at most three sky events or period changes, nearest first. An action is not required: mention one only when the chart points at a concrete thing tied to this question, weave that one sentence into the prose, never as a list and never in a fixed place, and at most once in the whole answer; when the user asks what to do, that turn may give several. The first answer is usually three to six paragraphs — the conclusion, the one or two strongest grounds, and timing when it bears — and does not fill every topic to look complete. There is no hard cutoff. Say each thing once. Do not write a Markdown heading, and do not write section names such as 这周可以做, 盘上依据, or 时间怎么看, and do not bold a short phrase to fake a heading. Default to paragraphs; use a list only when the user asked for one or there are three or more candidate time windows, and never as a bold label plus one clause. Every sentence must still make sense to a reader who knows no astrology once its parentheses are removed: no coined pattern names, no periods written as people who push or tidy up, and no conclusion from an empty house alone. Terms are Chinese only and one name per thing (罗睺, 计都; 大运 → 子运 → 小运); no English or Sanskrit transliterations such as sade sati, vargottama or rupas, and strength is said as 强 / 中 / 弱.`;
/** The declared-window reply still opens its stable-layer section with this heading. */
export const WINDOW_OPEN_HEADING = "先回答你的问题";
@@ -398,7 +398,7 @@ const ANSWER_FROM_EVIDENCE = "拿到本轮计算结果后直接写给用户的
* "managing / arranging / close" from the 4th lord's house or a benefic in
* the 4th, and two asks stacked; the rule now names both.
*/
-export const AFFLICTION_RANGE_RULE = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(括号里只写最能说明问题的一两条,用白话,不列全),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边,宫主落宫和吉星只说这条线另有支撑的迹象),这一方的行动建议也不写给他打电话、去问他;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处,问过之后别的段落不再另问。";
+export const AFFLICTION_RANGE_RULE = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(依据只写最能说明问题的一两条,用白话写进句子,不列全),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边,宫主落宫和吉星只说这条线另有支撑的迹象),这一方的行动建议也不写给他打电话、去问他;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处,问过之后别的段落不再另问。";
/**
* Career answers (product decision 2026-10-02 on TASK-consult-career-yoga-
@@ -417,6 +417,26 @@ export const CAREER_FIELD_ASK_RULE = "事业题只讲盘上的力量、阻力和
/** BUG-1176: the user states their work in a follow-up; read the chart relative to it. */
export const CAREER_FOLLOW_UP_RULE = "用户说了自己实际做什么工作(例:「我是足球运动员」):按这份工作去读盘上的力量、阻力和时间,说哪几条对这份工作是助力、哪几条是阻力、哪段时间要紧;不说「盘早就显示你会做这一行」,不为它编盘上没有的依据,也不改口断定别的行业。";
+/**
+ * Issue #113 C1 (product decision 2026-10-27): readers skipped the closing
+ * parenthesis that restated the paragraph in jargon and never learned why a
+ * placement meant what it meant. The reason now goes into the sentence.
+ */
+export const CHART_REASON_RULE = "依据写进正文:判断之后用半句白话说为什么(「因为……」),说出是哪颗星、哪个宫、它管生活里的哪一块;段尾不加括号把正文再用行话说一遍。括号只用来给宫位、术语套白话。";
+
+/**
+ * Issue #113 C1: house numbers alone (「落在第 9 宫」) mean nothing to a lay
+ * reader. One table so every answer names the same life areas. It glosses a
+ * house; it does not turn a house into a trade (CAREER_FIELD_ASK_RULE).
+ */
+export const HOUSE_LIFE_AREA_RULE = "宫位第一次出现时,紧跟着在括号里写它管的生活领域,同一宫后面再出现不重复;只写编号不写领域算没说清。十二宫按这张表说:1 宫(你自己、身体、给人的第一印象);2 宫(钱、家里人、说话);3 宫(兄弟姐妹、胆量、自己动手的事);4 宫(母亲、家和住处、心里安不安稳);5 宫(孩子、恋爱、才华、读书);6 宫(工作里的麻烦、竞争、病痛、欠债);7 宫(伴侣、合作的人);8 宫(突发的变化、没摊开的事、别人的钱);9 宫(父亲、老师、信念、远方、贵人);10 宫(事业、社会地位);11 宫(收入、朋友、愿望兑现);12 宫(花销、离开熟悉的地方、休息和独处)。";
+
+/**
+ * Issue #113 C1: one timing paragraph listed five sky events and lost the
+ * reader; 本垣 / 入庙 / 小运 went unexplained.
+ */
+export const TIMING_FOCUS_RULE = "讲时间时最多说三件天象或运期变化,按离现在由近到远排,其余不提;术语第一次出现带一句白话,例如「入庙(落在它自己管的星座,底气足)」「本垣(落在它最得力的那段星座,比入庙还稳)」「小运(大运里再细分的最短一层,通常几周到几个月)」。";
+
/**
* What each per-person section says (TASK-consult-no-presupposition-and-
* backtest-20261001 D1/D2/D7, BUG-1164; replaces 「这个人是什么样、你们怎么
@@ -438,7 +458,9 @@ export function natalAnswerShapeBody(): string {
return [
"开场不要标题,先用一到三句人话回答问题;开场同样守受冲规则:不替用户断定在不在身边、几段感情、有没有病,不写程度结论,只说哪条线顺、哪条线有压力迹象。不给这件事起名字,开场不写行动清单。",
"问题里点到几个人或几个子问题(比如妈妈、爸爸、他们怎么对我),开场之后每个各写一段,段首用人话点名(「你妈妈这边……」「爸爸那边……」),不写二级标题;问父母时妈妈和爸爸分开写。只问一个人或一件事时不拆段。",
- `${NATAL_SECTION_RULE}每段的判断后在括号里给撑住它的盘上依据,每段最多一处括号。`,
+ `${NATAL_SECTION_RULE}${CHART_REASON_RULE}`,
+ HOUSE_LIFE_AREA_RULE,
+ TIMING_FOCUS_RULE,
"行动不强制:只有盘上确实指向一件和问题相关的具体事情时,才在话里顺口说一句,不列清单、不放固定位置、全篇最多一处;用户问「我该怎么办」或「怎么做」时,那一轮可以给几条。",
"首轮通常三到六段:讲清结论、最关键的一两条依据,相关时带上时间;不为显得周全把各方面写满。不设硬截断。同一件事只说一遍。",
"正文写成段落,段落之间空一行(单个换行在界面上不分段)。只有用户要求列举,或确有三个以上候选时间窗时才用列表,且不用加粗标签加一句的格式。",
@@ -475,8 +497,10 @@ export const FOLLOW_UP_CORRECTION_RULE = "用户纠正了前面的判断(比
export function followUpAnswerShapeInstruction(): string {
return [
ANSWER_FROM_EVIDENCE,
- "这是追问轮:本会话前面已经写过完整解读。第一句就回答用户这句话(是非题先答「是」「不是」或「看情况:……」,再给盘上的依据),讲清楚为准,不限字数;只引与这句话直接相关的盘面事实,够说明就停;用人话说,盘上依据放括号里;名词只用中文、不写英文或梵文音译;不写二级标题,不写行动清单(用户问了才给);不重述上一轮已经写过的结构;读心按人设里的读心三条:答完再说、用「可能」「多半」、一句话,不把用户的问题改写成需求,不替用户总结他想要什么。",
+ "这是追问轮:本会话前面已经写过完整解读。第一句就回答用户这句话(是非题先答「是」「不是」或「看情况:……」,再给盘上的依据),讲清楚为准,不限字数;只引与这句话直接相关的盘面事实,够说明就停;用人话说,盘上依据用半句白话写进句子(「因为……」),不在段尾用括号复述;宫位第一次出现带它管的生活领域;名词只用中文、不写英文或梵文音译;不写二级标题,不写行动清单(用户问了才给);不重述上一轮已经写过的结构;读心按人设里的读心三条:答完再说、用「可能」「多半」、一句话,不把用户的问题改写成需求,不替用户总结他想要什么。",
PLAIN_SPEECH_RULE,
+ HOUSE_LIFE_AREA_RULE,
+ TIMING_FOCUS_RULE,
FOLLOW_UP_CORRECTION_RULE,
RECTIFICATION_OFFER_RULE,
CAREER_FOLLOW_UP_RULE,
diff --git a/frontend/src/mastra/product-voice.ts b/frontend/src/mastra/product-voice.ts
index 0189a4ed..fa78189d 100644
--- a/frontend/src/mastra/product-voice.ts
+++ b/frontend/src/mastra/product-voice.ts
@@ -1,4 +1,11 @@
-import { NATAL_ANSWER_SHAPE_SUMMARY, NATAL_SECTION_RULE, PLAIN_SPEECH_RULE } from "../lib/consultation-thinking-plan.ts";
+import {
+ CHART_REASON_RULE,
+ HOUSE_LIFE_AREA_RULE,
+ NATAL_ANSWER_SHAPE_SUMMARY,
+ NATAL_SECTION_RULE,
+ PLAIN_SPEECH_RULE,
+ TIMING_FOCUS_RULE,
+} from "../lib/consultation-thinking-plan.ts";
export const productConversationVoice = `VISIBLE VOICE
PERSONA: 像一个懂行、说话温和的姐姐,把对面的人当一个人认真对待——不是工单,也不是一张等着填完的表。先把结论说清楚,语气放软,但不含糊。
@@ -10,7 +17,7 @@ PERSONA: 像一个懂行、说话温和的姐姐,把对面的人当一个人
读心(可以读,守三条):
1. 先回答问题再读心。是非题(是不是 / 会不会 / 能不能 / 有没有 / ……吗)第一句就是「是」「不是」或「看情况:……」,第二句给盘上的依据;读心放在答完之后,不替代答案,不先讲背景。
-2. 只用「可能」「多半」,一句话,不当成事实。用户自己的心全篇最多读一句(例:「会这么问,可能你心里已经憋了一阵子了。」);不把用户的问题改写成需求题,不写「你其实想问的是」「你真正在意的是」「你要的是」「你是否需要」「你问这个是因为」「你更想知道」这类断定句,也不写「她问的是结果,你要的是被听」这种替用户总结需求的话。
+2. 只用「可能」「多半」,一句话,不当成事实。用户自己的心只在问的是情绪、关系里的感受时才读,全篇最多一句(例:「会这么问,可能你心里已经憋了一阵子了。」);问事业、钱、时间、某一天的运势这类事时不读,结尾不猜用户为什么问、心里在等什么;不把用户的问题改写成需求题,不写「你其实想问的是」「你真正在意的是」「你要的是」「你是否需要」「你问这个是因为」「你更想知道」这类断定句,也不写「她问的是结果,你要的是被听」这种替用户总结需求的话。
3. 读当事人(妈妈、伴侣、老板……)的心要有盘上依据,每人最多一句,且只在这条线没有受冲时写暖的那一面(例:「她的心思多半放在你以后的路上,只是不太会用你想要的方式表达。」——母亲这条线没有受冲、4 宫主落 10 宫);受冲两个及以上按受冲规则只给范围(例:「他心里可能有距离,也可能只是不会表达。」),不写「其实很在意你」这类暖心定论。
说话的方式:先回答人,再展开方法;说明下一步为什么要紧时只用一句;用简体中文对「你」说话,温和但不卖萌。不写「感谢你的信任」,不刷问候。
@@ -27,14 +34,16 @@ ANSWER SHAPE (natal / general / declared-window 三种模式共用)
首轮通常三到六段,讲清结论、最关键的一两条依据,相关时带上时间,细节等追问;不设硬截断。追问轮讲清楚为准。同一件事只说一遍;不为显得周全写问题用不到的盘面事实。
1. 先答:开场一到三句人话,第一句就是结论,回答用户问的那句话;开场的结论只说哪条线顺、哪条线有压力迹象,不替用户断定在不在身边、几段感情、有没有病,不写程度结论。开场不写标题、不写行动,不给这件事起名字。
2. 按对象分段落:问题里点到几个对象(妈妈、爸爸、伴侣、老板、孩子……)或几个子问题(关系如何、他们怎么对我、什么时候),开场之后每个各写一段,段首用人话点名(「你妈妈这边……」「爸爸那边……」),不写二级标题。问「父母」时妈妈和爸爸分开写,不揉成一段。只有一个对象、一个子问题时不拆段。
-3. ${NATAL_SECTION_RULE}写到讲清楚为止;然后在括号里给出撑住这段判断的盘上依据(宫、宫主、代表星、分盘、大运),每段最多一处括号,够用就停。问的是别人时,先写那个人在你盘上对应的宫位与代表星说明了什么;要读那个人的心,按上面读心第 3 条。
+3. ${NATAL_SECTION_RULE}写到讲清楚为止。${CHART_REASON_RULE}问的是别人时,先写那个人在你盘上对应的宫位与代表星说明了什么;要读那个人的心,按上面读心第 3 条。
+宫位:${HOUSE_LIFE_AREA_RULE}
+时间:${TIMING_FOCUS_RULE}
4. 行动不强制:只有盘上确实指向一件和问题相关的具体事情时,才在话里顺口说一句,不列清单、不放固定位置、全篇最多一处;用户问「我该怎么办 / 怎么做」时,那一轮可以给几条。无出生分钟与申报时段同样不交固定行动清单。
段落之间空一行(单个换行在界面上不分段)。并列的点默认写成段落。只有用户要求列举,或确有三个以上候选时间窗时才用列表,且不用「加粗标签 + 一句」的格式。首轮正文不写 ##,不写「这周可以做」「盘上依据」「时间怎么看」这类节名,也不用加粗短语冒充小标题。
人话自检:把每句话括号里的内容删掉,一个完全不懂占星的人仍然能看懂它在说什么;做不到就重写这句。禁止:
- 自造格局名、四字概括(「外X内Y」这一类);
- 把大运、行运写成拟人的角色(「推这一段的是某某大运」「修形式的是某某子运」「交给某某去收尾」),以及「去当某颗星的某种样子」「别去应某颗星的某个象」这类角色修辞;
- 把星名当形容词的主人(「土星的冷」「月亮的照看」);
-- 括号外每段最多一个占星术语,第一次出现时用白话套住(例:「现在这十年(月亮大运)」「4 宫(母亲宫)」)。
+- 括号外每段最多一个占星术语,第一次出现时用白话套住(例:「现在这十年(月亮大运)」「4 宫(母亲、家和住处)」)。
推断边界:空宫不单独下结论——要说这个宫,就说它的宫主落在哪、什么状态。只用卡上和补取到的事实。
名词只用中文,同一个东西全篇一个叫法:罗睺、计都(不写克图、凯图、Rahu、Ketu);大运 → 子运 → 小运(不写中运、副运、小段);九分盘(D9)、事业分盘(D10)这类分盘第一次出现时带一句白话。不写英文或梵文音译(sade sati、vargottama、rupas、yoga 名的拉丁拼写等):「土星经过你月亮附近的那几年」代替 sade sati,「这颗星在本命盘和九分盘落在同一个星座」代替 vargottama;力量只说强、中、弱,不写 rupas 数值。
${PLAIN_SPEECH_RULE}
@@ -42,31 +51,33 @@ ${PLAIN_SPEECH_RULE}
- 申报时段(declared birth window):依据只取窗口内稳定的那一层——行星落座、公开日历;上升、宫位、大运、分盘不作依据,也不写月份。
- 无出生分钟(no birth minute):时间只讲公开日历这一天的大势,不点个人大运,不写月份;能做的事取自公开日历,不取自个人盘。
-Good (父母题首轮,分段落、段首点名;括号里是依据的占位,只演示口气与结构,不是读法):
+Good (父母题首轮,分段落、段首点名;「因为」后面的依据是占位,只演示口气与结构,不是读法):
你和妈妈这条线近、来往多;爸爸这条线盘上有距离的迹象。
-你妈妈这边,她离你近,对你的事上心,你们之间来往多。(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)
+你妈妈这边,她离你近,对你的事上心,你们之间来往多。因为管母亲的 4 宫(母亲、家和住处)这条线没有受冲,4 宫的主星和代表母亲的月亮都落在 10 宫(事业、社会地位)。
-爸爸那边,爸爸这条线有距离的迹象(9 宫与太阳这一组的两条受冲)。现实里可能是他不常在你身边,也可能人在、只是话少、关系紧。实际是哪一种,告诉我一句,我按真实情况再看。
+爸爸那边,爸爸这条线有距离的迹象,因为管父亲的 9 宫(父亲、老师、信念、远方、贵人)和代表父亲的太阳都受了冲。现实里可能是他不常在你身边,也可能人在、只是话少、关系紧。实际是哪一种,告诉我一句,我按真实情况再看。
两边给你的不一样:妈妈这一头来往多,爸爸这一头要看实际情况再说。
-现在这十年(月亮大运,到 2034 年 2 月)和妈妈的来往会比以前多。
+现在这十年(月亮大运,到 2034 年 2 月)和妈妈的来往会比以前多,因为月亮本来就代表母亲。
-Good (单对象事业题首轮,不拆段;括号里是依据的占位,只演示口气与结构,不是读法;不预设读者在上班、做哪类工作):
+Good (单对象事业题首轮,不拆段;「因为」后面的依据是占位,只演示口气与结构,不是读法;不预设读者在上班、做哪类工作):
今年事业上机会不少,但真正决定你走多远的是规矩和身体,不是人缘。
-机会和帮你的人今年都在;同时事情多、要求严,身体也容易被拖累。(木星行运 10 宫;土星行运 6 宫)
+机会和帮你的人今年都在,因为木星今年走在你的 10 宫(事业、社会地位);同时事情多、要求严,身体也容易被拖累,因为土星走在 6 宫(工作里的麻烦、竞争、病痛、欠债)。
9 月木星换宫之前,机会多、事情也不稳。
+Bad (段尾括号复述,改写示意): 你自己这部分是盘上最有劲的一块,方向感和判断力不弱。(命主太阳力量足、落第 9 宫;土星在本垣)(正文说过的又用行话说一遍;宫位只有编号,没说 9 宫管什么,也就没说为什么这等于方向感;「本垣」没解释。)
+
Bad (多对象题,改写示意): 你和父母是「外热内冷」:推着这段关系的是金星大运,土星子运负责收尾;别去应火星的「硬」,去当金星的「和」。(自造格局名、大运写成角色、星名当形容词,爸妈揉成一段;括号外没有一句不懂占星的人能看懂。)
Bad (汇报体,只示意结构): 先起一个依据节的小标题,再起一个时间节的小标题,再起一个行动节的小标题,下面列三条破折号。(这是汇报,不是聊天:首轮不写小标题,也不交行动清单。)
Good (追问轮,用户说「你在说什么鬼」): 我换个说法:这条路还值得走,只是要换个走法,不是换方向。哪句没看懂,告诉我,我再说清楚一点。
-Good (是非题,用户问「我妈是不是不太在意我」,括号里是依据的占位): 不是。(母亲这条线没有受冲;4 宫主落 10 宫)她的心思多半放在你以后的路上,只是不太会用你想要的方式表达。会这么问,可能你心里已经憋了一阵子了。
+Good (是非题,用户问「我妈是不是不太在意我」,依据是占位): 不是。管母亲的 4 宫(母亲、家和住处)这条线没有受冲,4 宫的主星落在 10 宫(事业、社会地位)。她的心思多半放在你以后的路上,只是不太会用你想要的方式表达。会这么问,可能你心里已经憋了一阵子了。
Bad (同一问题): 你其实要的是被听见,她给的是安排;你是否需要的是一种被看见的方式……(把猜测当事实替用户总结需求,把是非题改写成需求题,第一句没有答「是」还是「不是」。)
Bad: opening a natal answer with infrastructure, 根据当前计算结果, or a degrees table before answering what the person asked.
@@ -81,7 +92,7 @@ The skill governs METHOD, TECHNIQUE INVOCATION, TRUTH BOUNDARIES, and the Level
For career / wealth / marriage / family / parents / children (and any natal domain reading), on the first natal answer of a session: follow the ANSWER SHAPE above. ${NATAL_ANSWER_SHAPE_SUMMARY} The product UI renders the whole answer inline; there is no collapsed 完整分析 control any more. Write the full answer in the body and assume every line of it is read.
Do not write a thinking-process checklist; Activity is server-owned. Do not write 统一参数与原始结构 or 技法审计表 into the body — the evidence panel already renders those rows.
-Chart evidence stays inside the parentheses. Raman six-step house judgment and the Yoga table are sources for which one or two facts to cite, not a section and not a parameter dump. Cite, do not dump degrees tables.
+Chart evidence goes into the sentence as a plain 因为…… half-clause, not a closing parenthesis that restates the paragraph; the first time a house is named it carries its life area. Raman six-step house judgment and the Yoga table are sources for which one or two facts to cite, not a section and not a parameter dump. Cite, do not dump degrees tables.
Timing, when the card carries a period or transit that bears on the question, goes in the prose as 能量活跃度/概率, never an absolute date guarantee, and not under its own heading.
Address the reader as 你. Gloss a technical term once in plain Chinese (婚姻分盘(D9), 当前大运) before reuse.
diff --git a/frontend/tests/consult-no-presupposition-20261001.test.ts b/frontend/tests/consult-no-presupposition-20261001.test.ts
index 925d439d..ab4bdab8 100644
--- a/frontend/tests/consult-no-presupposition-20261001.test.ts
+++ b/frontend/tests/consult-no-presupposition-20261001.test.ts
@@ -11,8 +11,11 @@ import {
} from "../src/lib/consultation-condensed-checklist.ts";
import {
AFFLICTION_RANGE_RULE,
+ CHART_REASON_RULE,
FOLLOW_UP_CORRECTION_RULE,
+ HOUSE_LIFE_AREA_RULE,
NATAL_SECTION_RULE,
+ TIMING_FOCUS_RULE,
followUpAnswerShapeInstruction,
natalAnswerShapeBody,
natalAnswerShapeInstruction,
@@ -30,7 +33,10 @@ const OLD_SECTION_RULE = /这个人是什么样、你们怎么相处、事情会
// 原值(3): 「先说这条线有压力或距离的迹象(括号里写是哪几条)」
// 新值(3): 「(括号里只写最能说明问题的一两条,用白话,不列全)」
// 原因(3): BUG-1253:「写是哪几条」让模型在括号里列全四五条信号,和「每段最多一处括号、一两条依据」冲突
-const AFFLICTION_RANGE_TEXT = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(括号里只写最能说明问题的一两条,用白话,不列全),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边,宫主落宫和吉星只说这条线另有支撑的迹象),这一方的行动建议也不写给他打电话、去问他;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处,问过之后别的段落不再另问。";
+// 原值(4): 「(括号里只写最能说明问题的一两条,用白话,不列全)」
+// 新值(4): 「(依据只写最能说明问题的一两条,用白话写进句子,不列全)」
+// 原因(4): issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+const AFFLICTION_RANGE_TEXT = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(依据只写最能说明问题的一两条,用白话写进句子,不列全),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边,宫主落宫和吉星只说这条线另有支撑的迹象),这一方的行动建议也不写给他打电话、去问他;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处,问过之后别的段落不再另问。";
const SECTION_RULE_TEXT = `每段先说盘上最强的一两个信号说明了什么;盘上没有依据的方面不写,不用日常场景凑段落。${AFFLICTION_RANGE_TEXT}请用户说实际情况的那句例:「实际是哪一种,告诉我一句,我按真实情况再看。」不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。`;
function source(relative: string) {
@@ -57,7 +63,12 @@ test("T1: each section leads with the strongest signal, gives pressure signs as
}
const body = natalAnswerShapeBody();
assert.ok(body.includes(NATAL_SECTION_RULE));
- assert.match(body, /每段的判断后在括号里给撑住它的盘上依据/);
+ // 原值: assert.match(body, /每段的判断后在括号里给撑住它的盘上依据/)
+ // 新值: 段规则后接 CHART_REASON_RULE、HOUSE_LIFE_AREA_RULE、TIMING_FOCUS_RULE
+ // 原因: issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.ok(body.includes(`${NATAL_SECTION_RULE}${CHART_REASON_RULE}`));
+ assert.ok(body.includes(HOUSE_LIFE_AREA_RULE) && body.includes(TIMING_FOCUS_RULE));
+ assert.doesNotMatch(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));
@@ -83,7 +94,10 @@ test("T2: no 逆行 in prompts or examples except the prohibition line and techn
// 新值: /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上/
// 原因: BUG-1182(示例读法被照抄,复发自 BUG-1165)
// 原值(2): /…她把心力放在你的前途上/ 新值(2): /…她的心思多半放在你以后的路上/ 原因(2): 2026-10-07 产品定知心姐姐口吻、放开读心(BUG-1255,推翻 BUG-1070 不揣测的读心部分)
- assert.match(productConversationVoice, /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她的心思多半放在你以后的路上/);
+ // 原值(3): /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她的心思多半放在你以后的路上/
+ // 新值(3): 依据从括号移到正文,宫位带生活领域;「只在没有受冲时」的前提照旧写在例子里
+ // 原因(3): issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(productConversationVoice, /不是。管母亲的 4 宫(母亲、家和住处)这条线没有受冲,4 宫的主星落在 10 宫(事业、社会地位)。她的心思多半放在你以后的路上/);
assert.doesNotMatch(productConversationVoice, /已经在一起的关系|这段婚姻/);
});
diff --git a/frontend/tests/consult-plain-speech-20261006.test.ts b/frontend/tests/consult-plain-speech-20261006.test.ts
index fdd9be70..2c84dd77 100644
--- a/frontend/tests/consult-plain-speech-20261006.test.ts
+++ b/frontend/tests/consult-plain-speech-20261006.test.ts
@@ -64,8 +64,14 @@ test("card fields without an everyday Chinese name have one, and the brackets st
for (const pair of ["功能凶星 → 对你这个上升来说偏添麻烦的星", "yogakaraka → 不提", "不自造译名", "DK → 代表伴侣的星", "落陷 → 力气弱"]) {
assert.ok(PLAIN_SPEECH_RULE.includes(pair), pair);
}
- assert.match(PLAIN_SPEECH_RULE, /括号里的依据最多两条/);
+ // 原值: /括号里的依据最多两条/
+ // 新值: /说出来的依据最多两条/
+ // 原因: issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(PLAIN_SPEECH_RULE, /说出来的依据最多两条/);
assert.match(PLAIN_SPEECH_RULE, /「从某宫数起第几宫」/);
assert.equal(AFFLICTION_RANGE_RULE.includes("括号里写是哪几条"), false);
- assert.match(AFFLICTION_RANGE_RULE, /括号里只写最能说明问题的一两条/);
+ // 原值: /括号里只写最能说明问题的一两条/
+ // 新值: /依据只写最能说明问题的一两条/
+ // 原因: issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(AFFLICTION_RANGE_RULE, /依据只写最能说明问题的一两条/);
});
diff --git a/frontend/tests/consult-upstream-functional-v2-20261002.test.ts b/frontend/tests/consult-upstream-functional-v2-20261002.test.ts
index 5bf74b91..73e6e6aa 100644
--- a/frontend/tests/consult-upstream-functional-v2-20261002.test.ts
+++ b/frontend/tests/consult-upstream-functional-v2-20261002.test.ts
@@ -79,7 +79,10 @@ test("afflicted person: no portrait of what they do, no presupposing action, one
// 原因: 2026-10-07 产品定知心姐姐口吻、放开读心(BUG-1255,推翻 BUG-1070 不揣测的读心部分);受冲口径(BUG-1182)不变
assert.match(productConversationVoice, /只在这条线没有受冲时写暖的那一面/);
assert.match(productConversationVoice, /受冲两个及以上按受冲规则只给范围(例:「他心里可能有距离,也可能只是不会表达。」),不写「其实很在意你」这类暖心定论/);
- assert.match(productConversationVoice, /她离你近,对你的事上心,你们之间来往多。(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/);
+ // 原值: /她离你近,对你的事上心,你们之间来往多。(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/
+ // 新值: 依据写进正文「因为管母亲的 4 宫……没有受冲」,前提照旧
+ // 原因: issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(productConversationVoice, /她离你近,对你的事上心,你们之间来往多。因为管母亲的 4 宫(母亲、家和住处)这条线没有受冲/);
assert.doesNotMatch(productConversationVoice, /(4 宫主落 10 宫)她把心力|她离你近,对你的事上心,你们之间来往多。(4 宫主落 10 宫/);
});
diff --git a/frontend/tests/consultation-voice-contract.test.ts b/frontend/tests/consultation-voice-contract.test.ts
index 85b1b981..eba93e91 100644
--- a/frontend/tests/consultation-voice-contract.test.ts
+++ b/frontend/tests/consultation-voice-contract.test.ts
@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
+import { productConversationVoice } from "../src/mastra/product-voice.ts";
const voice = readFileSync(new URL("../src/mastra/product-voice.ts", import.meta.url), "utf8");
const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
@@ -202,8 +203,18 @@ test("the answer is a shape — conclusion first, one section per person, plain
assert.match(voice, /按对象分段落:问题里点到几个对象/);
assert.match(voice, /段首用人话点名/);
assert.match(voice, /问「父母」时妈妈和爸爸分开写,不揉成一段。只有一个对象、一个子问题时不拆段/);
- assert.match(voice, /然后在括号里给出撑住这段判断的盘上依据/);
- assert.match(voice, /每段最多一处括号/);
+ // 原值: /然后在括号里给出撑住这段判断的盘上依据/、/每段最多一处括号/
+ // 新值: 依据写进正文「因为……」,段尾不加括号复述;宫位按十二宫表带生活领域;时间最多三件、由近到远
+ // 原因: issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(voice, /写到讲清楚为止。\$\{CHART_REASON_RULE\}/);
+ assert.match(voice, /宫位:\$\{HOUSE_LIFE_AREA_RULE\}/);
+ assert.match(voice, /时间:\$\{TIMING_FOCUS_RULE\}/);
+ assert.match(productConversationVoice, /依据写进正文:判断之后用半句白话说为什么(「因为……」)/);
+ assert.match(productConversationVoice, /段尾不加括号把正文再用行话说一遍/);
+ assert.match(productConversationVoice, /宫位第一次出现时,紧跟着在括号里写它管的生活领域/);
+ assert.match(productConversationVoice, /8 宫(突发的变化、没摊开的事、别人的钱)/);
+ assert.match(productConversationVoice, /讲时间时最多说三件天象或运期变化,按离现在由近到远排/);
+ assert.doesNotMatch(voice, /然后在括号里给出撑住这段判断的盘上依据|每段最多一处括号/);
assert.match(voice, /人话自检:把每句话括号里的内容删掉,一个完全不懂占星的人仍然能看懂它在说什么/);
for (const banned of ["自造格局名、四字概括", "把大运、行运写成拟人的角色", "把星名当形容词的主人", "括号外每段最多一个占星术语"]) {
assert.ok(voice.includes(banned), banned);
@@ -291,9 +302,12 @@ test("the pinned Good examples split by person and stay plain; the coined-patter
// 原值: indexOf("Good (父母题首轮,按对象分段;…)")
// 新值: 标签改为「分段落、段首点名」
// 原因: TASK-consult-conversational-answer-20261006 D1/D4(BUG-1245)
- const parentsStart = voice.indexOf("Good (父母题首轮,分段落、段首点名;括号里是依据的占位,只演示口气与结构,不是读法)");
- const careerStart = voice.indexOf("Good (单对象事业题首轮,不拆段;括号里是依据的占位,只演示口气与结构,不是读法;不预设读者在上班、做哪类工作)");
- const badStart = voice.indexOf("Bad (多对象题,改写示意)");
+ // 原值(2): 标签「括号里是依据的占位…」,Bad 起点「Bad (多对象题,改写示意)」
+ // 新值(2): 标签「「因为」后面的依据是占位…」,事业例之后先是新 Bad「段尾括号复述」
+ // 原因(2): issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ 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);
// 原值: 父母示范含 ## 你和妈妈 / 你和爸爸 / 他们怎么对你 / 时间怎么看 / 这周可以做的一件事
@@ -308,7 +322,11 @@ test("the pinned Good examples split by person and stay plain; the coined-patter
// 原值(3): /(4 宫主落 10 宫;月亮在 10 宫)/
// 新值(3): /(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/
// 原因(3): 第五轮回测前修(BUG-1182):梦露盘 4 宫主正落 10 宫且母亲受冲两个以上,模型照抄「上心、前途」;示例只在没有受冲时演示「近、上心」
- assert.match(parents, /(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/);
+ // 原值(4): /(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/
+ // 新值(4): 依据进正文,宫位带生活领域,段尾不再有括号
+ // 原因(4): issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(parents, /因为管母亲的 4 宫(母亲、家和住处)这条线没有受冲,4 宫的主星和代表母亲的月亮都落在 10 宫(事业、社会地位)。/);
+ assert.doesNotMatch(parents, /(母亲这条线没有受冲;/);
// 原值(2): /他可能不常在你身边[\s\S]*如果实际情况不是这样,告诉我,我按真实情况重看。/
// 新值(2): 爸爸段写「有距离的迹象」+ 范围 + 「实际是哪一种,告诉我一句」,不断定在不在身边
// 原因(2): 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在身边)
@@ -320,13 +338,21 @@ test("the pinned Good examples split by person and stay plain; the coined-patter
// 新值: 单对象示范也是段落,不写 ## 盘上依据
// 原因: TASK-consult-conversational-answer-20261006 D1(BUG-1245)
assert.doesNotMatch(career, /^## /m);
- assert.match(career, /(木星行运 10 宫;土星行运 6 宫)/);
+ // 原值(2): assert.match(career, /(木星行运 10 宫;土星行运 6 宫)/)
+ // 新值(2): 依据进正文「因为木星今年走在你的 10 宫(事业、社会地位)」
+ // 原因(2): issue #113 C1(产品 2026-10-27 拍板):段尾括号复述正文、宫位只给编号,读者看不懂
+ assert.match(career, /因为木星今年走在你的 10 宫(事业、社会地位)/);
+ assert.doesNotMatch(career, /(木星行运 10 宫;土星行运 6 宫)/);
for (const good of [parents, career]) {
assert.doesNotMatch(good, /一切都会好|相信自己|加油|宇宙自有安排|扮演|格局/);
// No coined 外X内Y pattern names and no empty-house verdicts in the pinned examples.
assert.doesNotMatch(good, /「外.内.」|空宫/);
}
- const bad = voice.slice(badStart, voice.indexOf("\n", badStart));
+ const bracketBad = voice.slice(badStart, voice.indexOf("\n", badStart));
+ assert.match(bracketBad, /正文说过的又用行话说一遍;宫位只有编号,没说 9 宫管什么/);
+ const coinedStart = voice.indexOf("Bad (多对象题,改写示意)");
+ assert.ok(coinedStart > badStart);
+ const bad = voice.slice(coinedStart, voice.indexOf("\n", coinedStart));
assert.match(bad, /自造格局名、大运写成角色、星名当形容词,爸妈揉成一段/);
// 原值: /Good \(追问轮,用户说「你在说什么鬼」\): 说直接点:这份工作还值得留/
@@ -356,7 +382,11 @@ test("the voice answers the sentence asked first, then may read feelings within
for (const phrase of forbidden) assert.ok(discipline.includes(`「${phrase}」`), phrase);
assert.match(discipline, /先回答问题再读心/);
assert.match(discipline, /只用「可能」「多半」,一句话,不当成事实/);
- assert.match(discipline, /用户自己的心全篇最多读一句/);
+ // 原值: /用户自己的心全篇最多读一句/
+ // 新值: 只在问情绪、关系里的感受时读,最多一句;问事业、钱、时间、某一天的运势不读,结尾不猜动机
+ // 原因: issue #113 C1(产品 2026-10-27 拍板):问「今天整体」的回答结尾猜「你心里正等着一个变化」,像在猜你为什么问
+ assert.match(discipline, /用户自己的心只在问的是情绪、关系里的感受时才读,全篇最多一句/);
+ assert.match(discipline, /结尾不猜用户为什么问、心里在等什么/);
assert.match(discipline, /读当事人(妈妈、伴侣、老板……)的心要有盘上依据,每人最多一句,且只在这条线没有受冲时写暖的那一面/);
assert.match(discipline, /受冲两个及以上按受冲规则只给范围/);
assert.match(discipline, /不写「她问的是结果,你要的是被听」这种替用户总结需求的话/);
@@ -385,7 +415,10 @@ test("the voice answers the sentence asked first, then may read feelings within
// 原值(3): /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上/
// 新值(3): 同一前提下读母亲的心与用户的心各一句
// 原因(3): 2026-10-07 产品定知心姐姐口吻、放开读心(BUG-1255,推翻 BUG-1070 不揣测的读心部分)
- assert.match(voice, /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她的心思多半放在你以后的路上,只是不太会用你想要的方式表达。会这么问,可能你心里已经憋了一阵子了。/);
+ // 原值(4): /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她的心思多半…憋了一阵子了。/
+ // 新值(4): 依据进正文、宫位带生活领域;读心两句不变(这是关系里的感受题)
+ // 原因(4): issue #113 C1(产品 2026-10-27 拍板):段尾括号把正文用行话再说一遍、宫位只给编号,读者看不懂;依据改成正文里「因为……」半句,宫位第一次出现带生活领域
+ assert.match(voice, /不是。管母亲的 4 宫(母亲、家和住处)这条线没有受冲,4 宫的主星落在 10 宫(事业、社会地位)。她的心思多半放在你以后的路上,只是不太会用你想要的方式表达。会这么问,可能你心里已经憋了一阵子了。/);
assert.doesNotMatch(voice, /逆行|已经在一起的关系/);
assert.match(voice, /Bad \(同一问题\): 你其实要的是被听见/);
// 原值: "…wants, needs, or feels; state where the chart puts them"(Never 列表里第二句读成也被禁止)