Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
158 lines
20 KiB
Markdown
158 lines
20 KiB
Markdown
# 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`)
|
||
|
||
用户在普通会话里问父母关系。用户原话反馈(产品转述):
|
||
|
||
1. 「内容我觉得还是太宽泛了」。
|
||
2. 「他有点揣测我:我问他『是不是……』,他像『你是否需要……』,在揣测我为什么要这样问」。
|
||
|
||
截图可见的正文片段(脱敏,只引结构):回答按「母亲这一头(4 宫 + 月亮)……父亲这一头(9 宫 + 太阳)……D12(家世分盘)……还要说一句公道话……」逐段铺开整张父母卡;其中出现「她问的是结果,你要的是被听」这类对用户内心的断言。用户问的是一句是非题,得到的是一份父母领域的通盘解读,外加对提问动机的解释。
|
||
|
||
**产品补交的完整原文(2026-09-27 晚,会话首轮,「已完成 10 步」)**。问句是「我和父母的亲疏关系如何」——**不是是非题,也不是追问轮**。答句结构(盘面数值不入任务书,只记结构与不含出生资料的句子):
|
||
|
||
1. 口语开场约 350 字:反差「外顺内远」写的是盘(空宫 vs 宫主离家),这一步合格;结尾三条行动「本周替父母办掉一件具体的杂事 / 约父亲一次正事,说清时间 / 给母亲打电话只聊她的日子」。
|
||
2. `## 先回答你的问题`:把开场已经说过的「母亲一头 / 父亲一头」再讲一遍,并出现对当事人和用户的动机断言:「她问的是结果,你要的是被听」「你自己这一端本来也是收着的——距离是双向拉出来的」。
|
||
3. `## 盘里支持这个判断的地方`:一张六行表,把第 1、2 步的内容第三次列出;之后功能吉凶、木星两段。
|
||
4. `## 时间怎么看`:四段子运,合格。
|
||
5. `## 这周可以做的一件事`:三条行动「挑一件具体的杂事替他们办掉 / 给父亲发一条带时间的信息 / 跟母亲通话时先接她的话头」,与开场三条一一对应,几乎原句重复;再加一句总结。
|
||
|
||
全文约 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」;`natalSpokenReportContract` Never 段:「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` 相关的思考栏节、续写提示、测试一并调整。
|
||
|
||
## 硬红线
|
||
|
||
1. 不改 `skills/**`(提示词都在 `frontend/src/mastra/` 与 `frontend/src/lib/`,Skill 版本不 bump);不改引擎、数据卡构建器、结算、扣点。
|
||
2. `frontend/src/app/page.tsx` 不增长;`scripts/jyotish_api_server.py` 不动。
|
||
3. 改既有断言写「原值 / 新值 / 原因」三栏(`consultation-voice-contract.test.ts`、`consultation-run-timeline.test.ts`、其他命中的);测试总数不低于开工时 `origin/staging` 实测。
|
||
4. 不得用正则或关键词表判定「是非题」「追问」「换领域」(D6);服务端只提供「本会话已有本命解读」这一事实。
|
||
5. 首轮解读的口语开场 + 四标题骨架不动(D3 只收窄追问轮);`consultationContinuePrompt` 的续写逻辑不动。
|
||
6. 前端红线全套:`tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、`next build` 后 `/` 仍 Static、首屏 gzip ±2%。
|
||
7. 禁止 `git stash`;只 `git add <具体路径>`;提交前 `git diff --cached --stat`;不推送代码分支,回报后由 Claude 验收再推 staging。
|
||
8. 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` 真机清单(虚构问句):
|
||
1. 新会话问「我和父母的关系怎么看」→ 口语开场 + 四标题(首轮不变)。
|
||
2. 接着问「我妈是不是不太在意我」→ 第一句是「是 / 不是 / 看情况」,全文 ≤ 200 字左右,无二级标题,无「你是否需要」「你要的是」。
|
||
3. 接着问「那我爸呢」→ 同上形状,不重述母亲段。
|
||
4. 接着问「完整看一下我的事业」→ 回到口语开场 + 四标题。
|
||
5. 步骤栏第一节显示「先回答你问的这件事」。
|
||
|
||
## 让步顺序
|
||
|
||
1. T2 的服务端事实若因 `history` 在溢出裁剪后拿不到(`lastConsultationPair` 路径),以「本轮请求带有会话 id 且该会话已有 assistant 消息」为准;仍拿不到则退回 `natalAnswerShapeInstruction()` 并在 PROGRESS 写明。
|
||
2. T4 若步骤栏标签被别的快照测试大量钉住(> 5 处),只改 `title`,步骤标签留待下一单。
|
||
3. T1 的 Good / Bad 对照若使 `productConversationVoice` 超出现有长度上限测试(如有),先压缩旧的 Bad 样例,不删禁写清单。
|
||
4. 任何一条做不到,写 `BLOCKED.md`,不得静默降级为「提示词加一句」。
|
||
|
||
## 开工前置命令
|
||
|
||
```bash
|
||
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)。开工时用上面的命令再核一次。
|