Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
20 KiB
TASK · 普通对话:回答问的那句话,不揣测动机、追问轮不重开整套骨架(2026-09-27)
基线
origin/staging=24a9a635(2026-09-27)。开工时git fetch origin --prune后以最新origin/staging为基线,写进 PROGRESS。- 分支
codex/consult-answer-the-question-20260927,工作树.worktrees/consult-answer-the-question-20260927。 - 与
TASK-cost-accounting-gaps-20260927无文件交集,可并行。
事故实证(2026-09-27 真机,staging,deepseek-v4-flash)
用户在普通会话里问父母关系。用户原话反馈(产品转述):
- 「内容我觉得还是太宽泛了」。
- 「他有点揣测我:我问他『是不是……』,他像『你是否需要……』,在揣测我为什么要这样问」。
截图可见的正文片段(脱敏,只引结构):回答按「母亲这一头(4 宫 + 月亮)……父亲这一头(9 宫 + 太阳)……D12(家世分盘)……还要说一句公道话……」逐段铺开整张父母卡;其中出现「她问的是结果,你要的是被听」这类对用户内心的断言。用户问的是一句是非题,得到的是一份父母领域的通盘解读,外加对提问动机的解释。
产品补交的完整原文(2026-09-27 晚,会话首轮,「已完成 10 步」)。问句是「我和父母的亲疏关系如何」——不是是非题,也不是追问轮。答句结构(盘面数值不入任务书,只记结构与不含出生资料的句子):
- 口语开场约 350 字:反差「外顺内远」写的是盘(空宫 vs 宫主离家),这一步合格;结尾三条行动「本周替父母办掉一件具体的杂事 / 约父亲一次正事,说清时间 / 给母亲打电话只聊她的日子」。
## 先回答你的问题:把开场已经说过的「母亲一头 / 父亲一头」再讲一遍,并出现对当事人和用户的动机断言:「她问的是结果,你要的是被听」「你自己这一端本来也是收着的——距离是双向拉出来的」。## 盘里支持这个判断的地方:一张六行表,把第 1、2 步的内容第三次列出;之后功能吉凶、木星两段。## 时间怎么看:四段子运,合格。## 这周可以做的一件事:三条行动「挑一件具体的杂事替他们办掉 / 给父亲发一条带时间的信息 / 跟母亲通话时先接她的话头」,与开场三条一一对应,几乎原句重复;再加一句总结。
全文约 1,500 字。同一个判断说了三遍,同一组行动给了两遍。用户说的「宽泛」,落在原文上就是重复:读完三遍同样的话,感觉是「说了很多但没多说什么」。「揣测」落在第 2 步那两句。产品转述里的「我问是不是 / 他答你是否需要」不在这一轮里,可能来自同会话另一轮;若能找到也贴进 PROGRESS。
环境缺口:Claude 没有登录态与数据库访问,上面的原文由产品贴出;执行方把问句与第 2、5 步的非盘面句子做成夹具(不带度数、星座、宫位数值)。
根因(按符号定位,基线 24a9a635)
普通对话每轮的提示词由四层拼成:productConversationVoice、natalSpokenReportContract(都在 frontend/src/mastra/product-voice.ts)、jyotishInstructions(frontend/src/mastra/index.ts)、每轮用户轮里的 natalAnswerShapeInstruction()(frontend/src/lib/consultation-thinking-plan.ts)。四层合起来把「回答用户问的那句话」换成了「按固定形状写一份领域解读」。
「揣测我为什么这样问」有三个来源
- R1 反差形状写到人身上。
productConversationVoice的 OPENER SHAPE 第 1 步:「反差:先说『表面 A,底下 B』,并把这个反差命名成一个格局」,且natalSpokenReportContract要求每个本命问题都用这个开场。定稿样例里的反差是盘上的(木星在事业宫 / 土星压工作宫)。但形状本身没有限定 A 和 B 必须是盘面结构,问的是人(父母、伴侣)时,模型最省事的「表面 / 底下」就是当事人的表面行为和内心动机,于是写出「她问的是结果,你要的是被听」。 - R2 「按我的理解答」规则。同文件 PERSONA 段:「问题含糊时不猜、也不反问一串——写『这句我按『……』理解了,不对你纠正我』,然后照这个理解把答案给完」。这条本意是防反问,副作用是鼓励模型先改写问题再回答:用户问「是不是 X」,模型改写成「你是否需要 Y」再答 Y。
frontend/tests/consultation-voice-contract.test.ts第 120 行把这句钉成了断言。 - R3 思考栏标题。
natalConsultationThinkingPlan第一节title: "先抓住你真正在问的事"(consultation-thinking-plan.ts),经 run timeline 显示在步骤栏(frontend/tests/consultation-run-timeline.test.ts第 41 行断言该标签)。用户每轮先看到系统在找他「真正在问的事」,再看到答案确实换了题,两者互相印证成「揣测」。
「太宽泛」有两个来源
-
R4 追问轮也走整套骨架。
jyotishInstructions:「Call run-jyotish-consultation before answering every turn, including short follow-ups」;natalSpokenReportContract末句:「One natal career/wealth/marriage/family question still uses the opener-plus-skeleton above」;每轮用户轮的natalAnswerShapeInstruction():「开场不要标题,先用口语回答问题;然后一次写完这四个二级标题」。没有任何一处区分「第一次问这个领域」和「对上一轮的追问 / 一句是非题」。于是一句「是不是 X」得到 ≤ 400 字口语开场 + 四个二级标题的完整解读。 -
R5 清单被当成段落大纲。
jyotishInstructions:「work through their mandatory modules against the evidence you were given」;natalSpokenReportContractNever 段:「Skip a card fact the question needs」。父母卡(consultation-evidence-card.ts第 144 行parents: spec({ vargas: ["D12"], houses: [4, 9], planets: ["Sun", "Moon"] }))有 4 宫、9 宫、太阳、月亮、D12 五块,模型把五块各写一段,问题本身反而没有一句正面回答。「宽泛」不是缺盘面细节(截图里 4 宫主水星逆行、月亮 Mula 都有),是细节不服务于问的那句话。 -
R6 首轮形状本身要求说三遍(原文证实,是首轮「宽泛」的主因)。
natalSpokenReportContract要求:口语开场(含判断 + 三条行动)→## 先回答你的问题(再答一次)→## 盘里支持这个判断的地方(表格再列一次)→## 这周可以做的一件事(行动再给一次)。natalAnswerShapeInstruction()同样写「先用口语回答问题;然后一次写完这四个二级标题」,第一个标题就是「先回答你的问题」。开场已经是答案,骨架又从「先回答」重新开始,模型只能复述;开场收尾三条行动与最后一节三条行动是同一份清单。这是 2026-09-17 口气定稿(84b293fb)把「口语开场」叠在既有四标题骨架上、而没有从骨架里减掉重合部分留下的。
不是根因的
- 服务端不改写问句:
resolveConsultationQuestion(consultation-entrypoint.ts)对普通入口返回kind: "plain",模型看到的就是用户原句。 - 数据卡体量已在 v2 压到单领域 ≤ 1.2 万字符(
TASK-consult-evidence-card-v2-20260927),不是本单范围。
决策记录(产品 2026-09-27)
- D1 回答对象是用户问的那句话。是非题先给「是 / 不是 / 看情况」,再给盘上的依据;不改写问题、不解释用户为什么问、不替用户说他「真正想要什么」。
- D2 反差只许写盘上的反差。「表面 A,底下 B」的 A 与 B 必须都是盘面结构(哪颗星、哪个宫、哪段大运);不得是用户或当事人的行为 / 动机 / 需求。这是对 2026-09-17 口气定稿(
84b293fb,TASK-agent-voice)的收窄,不是推翻:形状保留,填法限定。 - D3 追问轮不重开整套骨架。本会话已有一轮本命解读后的追问(包括是非题、「那 X 呢」、对上一轮某句的反问),按「直接回答」形状写:≤ 200 字,先答再证,不写四个二级标题,不重述上一轮已写过的盘面;只有用户明确换了领域或要求「完整看一下」才回到口语开场 + 骨架。这条推翻
natalSpokenReportContract末句「One natal … question still uses the opener-plus-skeleton above」对追问轮的适用,以及consultation-voice-contract.test.ts里相应断言;首轮解读的形状不变。 - D4 删掉「这句我按『……』理解了」规则,改成:问句只有在有两种实质不同读法时才写一句「我按 A 答」,且答完 A 不再解释为什么这样理解;单义问句直接答。
- D5 思考栏第一节标题改成「先回答你问的这件事」。
- D6 判断「追问轮」用服务端事实(本会话此前是否已有一轮本命解读),不用正则识别用户意图(沿用 BUG-976/977 的口径);是非题 / 换领域由模型按提示词判断。
- D7 排盘工具仍每轮调用(计算是请求级的,BUG-1053 后答案由看过工具结果的那一步写),本单只改答案形状,不改工具调用与结算。
- D8 首轮解读只说一遍(产品 2026-09-27 晚已确认,T6 必做;产品同时要求 Claude 直接执行本单):口语开场保留反差、谁推谁修、扮演哪个象,不再带三条行动;删掉
## 先回答你的问题这一节(开场就是回答);## 盘里支持这个判断的地方只写开场没说过的依据,表格与正文二选一,不得把开场的判断再列一遍;三条行动只出现在最后一节## 这周可以做的一件事。首轮全文目标 ≤ 900 字。这条修改 09-17 口气定稿的骨架顺序,但保留其口语开场形状;REPORT_HEADING.question相关的思考栏节、续写提示、测试一并调整。
硬红线
- 不改
skills/**(提示词都在frontend/src/mastra/与frontend/src/lib/,Skill 版本不 bump);不改引擎、数据卡构建器、结算、扣点。 frontend/src/app/page.tsx不增长;scripts/jyotish_api_server.py不动。- 改既有断言写「原值 / 新值 / 原因」三栏(
consultation-voice-contract.test.ts、consultation-run-timeline.test.ts、其他命中的);测试总数不低于开工时origin/staging实测。 - 不得用正则或关键词表判定「是非题」「追问」「换领域」(D6);服务端只提供「本会话已有本命解读」这一事实。
- 首轮解读的口语开场 + 四标题骨架不动(D3 只收窄追问轮);
consultationContinuePrompt的续写逻辑不动。 - 前端红线全套:
tsc --noEmit0 错、npm run lint0 error、相关套件 fail=0、next build后/仍 Static、首屏 gzip ±2%。 - 禁止
git stash;只git add <具体路径>;提交前git diff --cached --stat;不推送代码分支,回报后由 Claude 验收再推 staging。 - Bug 历史、PROGRESS、测试夹具里不得出现真实用户的问句原文中带出生资料、姓名的部分;夹具用虚构问句(例:「我妈是不是不太在意我」)。
任务分解
T1 口气层:反差限定在盘上,删「按我的理解答」(R1、R2、D1、D2、D4)
改 frontend/src/mastra/product-voice.ts 的 productConversationVoice:
- OPENER SHAPE 第 1 步改成:反差的 A 与 B 都是盘面结构(星、宫、大运、分盘),命名成格局;问的是别人(父母、伴侣、子女、同事)时,反差写那个人在你盘上对应的宫位与代表星,不写那个人的心思。
- PERSONA 段:删「问题含糊时不猜、也不反问一串——写『这句我按『……』理解了,不对你纠正我』」,改成 D4 的写法。
- 新增一段 禁写清单(与「Never write 一切都会好…」并列):不得写「你其实想问的是」「你真正在意的是」「你要的是」「你是否需要」「你问这个是因为」「你更想知道」,以及任何替用户或当事人陈述动机 / 需求 / 感受的句子;盘上有证据可以写「4 宫主落 10 宫,母亲的注意力在你的前途上」,不得写「她问的是结果,你要的是被听」。
- 新增 是非题答法:问句是「是不是 / 会不会 / 能不能 / 有没有 / ……吗」形状时,第一句就是「是」「不是」或「看情况:……」,第二句给盘上的依据;不先讲背景。
- Good / Bad 对照各加一组(虚构盘面):Bad 是「你其实要的是被听见……」这类;Good 是「不是。4 宫主水星逆行落 10 宫,她把心力投在你的前途上,不是不在意,是不会用你想要的方式在意。」
- 同步
frontend/docs/VOICE.md人设段与「口语层的形状」段:加「反差只写盘上」「是非题先答」「禁写动机句」三条与对照表。
验收:consultation-voice-contract.test.ts 断言更新(三栏),新增断言:voice 含禁写清单里至少四个短语;不含「这句我按『……』理解了」;含是非题答法。
T2 追问轮形状:服务端给事实,用户轮换指令(R4、D3、D6、D7)
frontend/src/lib/consultation-thinking-plan.ts新增followUpAnswerShapeInstruction():拿到本轮计算结果后直接答;≤ 200 字;第一句回答问的那句话;只引与这句话直接相关的盘面事实,最多三处;不写二级标题、不写行动清单(用户问了才给);不重述上一轮已经写过的结构;用户明确换领域或要求完整看时才按首轮形状写。frontend/src/app/api/consult/route.ts组用户轮处(natalInstruction附近):本会话history里已有至少一条 assistant 正文时用followUpAnswerShapeInstruction(),否则用natalAnswerShapeInstruction()。daily_starlanguage入口不变。仍然拼在consultationUserTurnContent的instruction位。natalSpokenReportContract末句改成:首轮解读用口语开场 + 骨架;追问轮按用户轮里的指令。jyotishInstructions的「Call run-jyotish-consultation before answering every turn, including short follow-ups」保留(D7)。
验收:记录提示词的假模型测试(沿用 consult-single-pass-answer 单里的测试模式):① 空历史 → 用户轮含四个二级标题指令;② 历史里有一轮 assistant 正文 → 用户轮含「≤ 200 字」「不写二级标题」,不含四标题指令;③ 两种情况工具仍被调用一次;④ daily_starlanguage 不受影响。stepScopedAnswer 对无标题正文的截留规则(标题或 160 字放出)在追问轮下仍能放出正文,写一条测试。
T3 清单是检查项不是段落大纲(R5)
jyotishInstructions(frontend/src/mastra/index.ts)在「work through their mandatory modules…」之后加一句:清单是核对项,不是段落清单;只把与这句问题直接相关的结构写进正文,核对过但与问题无关的不写;「Never skip a card fact the question needs」保留,改成「the question needs」的字面即可,不扩到整张卡。natalSpokenReportContract第 2 条「Ground the judgment in the evidence card's chart fields」后加:只引支撑这个判断的字段。
验收:voice 契约测试加断言(含「核对项」「与问题直接相关」)。
T4 思考栏标题(R3、D5)
natalConsultationThinkingPlan第一节title改「先回答你问的这件事」;追问轮(T2 的服务端事实)下该节唯一步骤标签改「直接回答这句话」,其余节照旧。consultation-run-timeline.test.ts第 41 行三栏更新;frontend/DESIGN.md步骤栏文案处同步。
验收:timeline 测试通过;组件快照若有变化写三栏。
T6 首轮只说一遍(R6、D8;产品已确认)
natalSpokenReportContract、natalAnswerShapeInstruction()、skill-binding.ts的NATAL_REPORT_SKELETON、index.ts里「Write in Simplified Chinese: a heading-free spoken opener first (… → 最多三条短行动 …)」四处同步:开场不带行动;骨架三个标题## 盘里支持这个判断的地方、## 时间怎么看、## 这周可以做的一件事;依据节只写开场没说过的,表格或正文二选一。consultation-thinking-plan.ts:REPORT_HEADING.question与natalConsultationThinkingPlan第一节按 D5 改名后保留为思考栏节,但不再对应正文标题(或删除该节,执行方按快照测试改动量选,写进 PROGRESS);consultationContinuePrompt的标题清单同步。frontend/docs/VOICE.md「口语层的形状」与「七条原则」同步;frontend/DESIGN.md若有正文标题清单同步。- 验收:voice 契约测试与 route 用户轮测试更新(三栏);用夹具问句跑一次假模型测试确认指令里不再同时要求「开场三条行动」与「这周可以做的一件事」;真机清单第 1 条改成:首轮开场无行动、无「先回答你的问题」标题、行动只在最后一节出现一次、全文约 900 字内。
T5 记录
docs/BUG_HISTORY.md:从 BUG-1070 起(开工时核对最大号,TASK-cost-accounting-gaps-20260927已预留到 1069):- BUG-1070 普通对话把是非题改写成「你是否需要」并解释提问动机(R1、R2)。
- BUG-1071 追问轮重开 ≤ 400 字开场 + 四标题骨架,一句是非题得到整份领域解读(R4、R5)。
- BUG-1072 步骤栏显示「先抓住你真正在问的事」(R3)。
- BUG-1073 首轮解读同一判断写三遍、三条行动给两遍(R6)。
- 三条都关联
84b293fb(口气定稿)与 BUG-1053(答案由看过工具结果的步写);修复版本写本单 commit;状态在 Claude 验收前写fixed_pending_acceptance,不写resolved。
CHANGELOG.md一条:普通对话追问轮直接回答、不再揣测动机;Skill 不 bump。docs/tasks/PROGRESS-consult-answer-the-question-20260927.md:基线、测试数字(开工 / 完工)、三栏表、偏离。docs/testing/consult-answer-the-question-20260927.md真机清单(虚构问句):- 新会话问「我和父母的关系怎么看」→ 口语开场 + 四标题(首轮不变)。
- 接着问「我妈是不是不太在意我」→ 第一句是「是 / 不是 / 看情况」,全文 ≤ 200 字左右,无二级标题,无「你是否需要」「你要的是」。
- 接着问「那我爸呢」→ 同上形状,不重述母亲段。
- 接着问「完整看一下我的事业」→ 回到口语开场 + 四标题。
- 步骤栏第一节显示「先回答你问的这件事」。
让步顺序
- T2 的服务端事实若因
history在溢出裁剪后拿不到(lastConsultationPair路径),以「本轮请求带有会话 id 且该会话已有 assistant 消息」为准;仍拿不到则退回natalAnswerShapeInstruction()并在 PROGRESS 写明。 - T4 若步骤栏标签被别的快照测试大量钉住(> 5 处),只改
title,步骤标签留待下一单。 - T1 的 Good / Bad 对照若使
productConversationVoice超出现有长度上限测试(如有),先压缩旧的 Bad 样例,不删禁写清单。 - 任何一条做不到,写
BLOCKED.md,不得静默降级为「提示词加一句」。
开工前置命令
cd /workspace/Jyotisha && git status -sb | head -1
git fetch origin --prune
git worktree add -b codex/consult-answer-the-question-20260927 .worktrees/consult-answer-the-question-20260927 origin/staging
cd .worktrees/consult-answer-the-question-20260927/frontend
npm ci
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5 # 记下开工测试总数
grep -rhoE "BUG-1[0-9]{3}" ../docs/BUG_HISTORY.md ../docs/tasks/*.md | sort -u | tail -1 # 核对编号起点
BUG 编号起点
BUG-1070(TASK-cost-accounting-gaps-20260927 已预留至 1069)。开工时用上面的命令再核一次。