docs(consult): plain answer records — VOICE, BUG-1132~1134, progress, device checklist
Independent Staging Quality Gate / validate (push) Failing after 13m27s
Independent Staging Quality Gate / publish (push) Skipped

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-01 09:48:51 +08:00
co-authored by Claude Opus 5.5
parent 2ec2762dcf
commit 44cdd9c9b9
6 changed files with 161 additions and 10 deletions
+43
View File
@@ -15182,6 +15182,49 @@
- 复发自:无(新迁移引入,未部署)
- 修复版本:`codex/rectification-varga-resolution-fix-20261001`(未部署)。
## BUG-1132 | 普通对话回答像谜语:四步开场形状逼出格局名、大运拟人、扮演象,问父母时爸妈揉成一段
- 状态:resolved(代码 + 合同测试;未部署,真机待产品)
- 首次发现 / 最近更新:2026-10-01 / 2026-10-01
- 影响面:普通对话(本命 / 无出生分钟 / 申报时段)的回答形状提示词:`frontend/src/mastra/product-voice.ts`(`productConversationVoice`、`natalSpokenReportContract`)、`frontend/src/mastra/index.ts`、`frontend/src/mastra/skill-binding.ts`、`frontend/src/lib/consultation-thinking-plan.ts`、`frontend/src/app/api/consult/route.ts`(旧路径用户回合)。生时校正不受影响。
- 现象:产品真机(staging,deepseek-v4-flash)问「我和父母关系如何」,开场是自造四字格局名、「推这一段的是某某大运,修形式的是某某子运」「别去应某星的冷,去扮演某星的照看」,爸妈揉在一段里,没有分开讲和妈妈、和爸爸、他们怎么对我;读者评价「还是废话」。
- 触发条件:任何本命首轮回答,问到多个对象时最明显。
- 根因:2026-09-17(`84b293fb`)定的四步开场形状(反差并命名格局 → 谁在推谁在修 → 别去应 X 的象去扮演 Y 的象 → 行动,≤ 400 字无标题)来自一段单对象、单时段的流年范文,被强制用在所有问题上;模型逐格填空。≤ 400 字、只容一个反差,问多个人时没有位置分开讲。同一形状文字抄在 7 处。不是 BUG-1070 复发:1070 只把反差的取材收窄到盘上,形状本身没改。
- 修复:产品 10-01 授权推翻四步形状(TASK-consult-plain-answer-20261001 D1–D3、D5、D7、D8)。新 ANSWER SHAPE:开场一到三句人话、第一句是结论;问题里点到几个人或子问题就分几段(H2 用问题里的词,父母必分妈妈 / 爸爸);每段先人话、括号里给依据;固定标题 `## 盘上依据`(原「盘里支持这个判断的地方」,可省)→ `## 时间怎么看`(可省)→ `## 这周可以做的一件事`。人话自检:删掉括号后不懂占星的人也能看懂;禁格局名、大运拟人与角色修辞、星名当形容词的主人。希望纪律去「扮演象」。取消开场 400 / 首轮 900 / 追问 200 字上限,讲清楚为准,保留「只说一遍」与追问不重开骨架。形状只在 `consultation-thinking-plan.ts` 定义一处(`NATAL_ANSWER_SHAPE_SUMMARY`、`natalAnswerShapeBody()`),其余位置引用。思考栏开场节改为在任意 H2 出现时完成、被省略的节在后面标题出现时完成。
- 验证:`consultation-voice-contract`、`consultation-thinking-plan`、`consult-answer-the-question-20260927`、`consultation-entrypoint` 合同测试改断言(三栏见 PROGRESS)并新增「形状只定义一处」「开场节在对象段标题处完成」两条;`git grep "扮演\|谁在推\|命名成一个格局\|外松内紧" -- frontend/src` 0 行;tsc 0、lint 0 error、npm test 失败名单与基线一致、build `/` Static。真实模型输出:本机无模型凭据,留待部署后按 `docs/testing/consult-plain-answer-20261001.md`。
- 防复发:回答形状只在一处定义,合同测试锁定摘要、合同、用户轮、思考栏一致且不含旧修辞与字数上限;新口吻样例必须是「多对象分段」与「单对象不拆段」各一,且通过人话自检。
- 相关记录:BUG-1070、BUG-1071、BUG-1072、BUG-1073、BUG-1133、BUG-1134
- 复发自:无(推翻 2026-09-17 产品定稿的形状)
- 修复版本:分支 `codex/consult-plain-answer-20261001`(未部署)
## BUG-1133 | 父母数据卡不分母亲、父亲,模型混用依据
- 状态:resolved(代码 + golden 测试;未部署)
- 首次发现 / 最近更新:2026-10-01 / 2026-10-01
- 影响面:`frontend/src/lib/consultation-evidence-card.ts` `EVIDENCE_CARD_SPECS.parents`。
- 现象:父母题回答把太阳、月亮、4 宫、9 宫放在一句里讲,分不清哪条说妈妈、哪条说爸爸。
- 触发条件:任何 parents 领域回答。
- 根因:parents 卡只给 `houses: [4, 9]` 与 `planets: ["Sun", "Moon"]`,没有标注角色,也没有 Jaimini 母亲代表星 MK。
- 修复:parents 规格加 `karakas: ["MK"]` 与 `roles`;卡上 `roles.mother`(4 宫、宫主、月亮、MK、D12 第 4 宫星座与落星)、`roles.father`(9 宫、宫主、太阳、D12 第 9 宫);PiK 只在引擎表里有时才加(当前引擎 7 karaka 方案没有 PiK,不造、不记缺口);「母亲 / 父亲」标签进 `EVIDENCE_CARD_LABELS`;Agent 指令说明 roles 的用法。
- 验证:`consult-evidence-card-20260927.test.ts` 新增两条:golden 三张盘逐字段核对 roles(宫主取自本节、MK 存在、D12 宫位按 D12 上升推算、无缺口);删掉 MK 时记 `parents.karaka.MK` 缺口。原有「每个卡值都是引擎值」与体量上限测试照常通过。
- 防复发:说多个人的领域,卡上必须写明每条依据属于谁。
- 相关记录:BUG-1132
- 修复版本:分支 `codex/consult-plain-answer-20261001`(未部署)
## BUG-1134 | 回答拿空宫直接下结论(「4 宫、9 宫空宫,感情不靠自动流动」)
- 状态:resolved(提示词 + 合同测试;未部署,真机待产品)
- 首次发现 / 最近更新:2026-10-01 / 2026-10-01
- 影响面:普通对话提示词。
- 现象:回答把「空宫」当成结论依据。Jyotish 判宫看宫主,空宫本身不说明弱。
- 触发条件:问题宫没有落星时。
- 根因:没有任何规则约束空宫推断。
- 修复:共享口吻「推断边界:空宫不单独下结论——要说这个宫,就说它的宫主落在哪、什么状态」;本命合同 Never 一条;中文用户轮与英文形状摘要各一句。
- 验证:`consultation-voice-contract.test.ts`、`consultation-thinking-plan.test.ts` 锁定这几句。真实模型输出待部署后真机第 1 步。
- 防复发:同上合同测试。
- 相关记录:BUG-1132
- 修复版本:分支 `codex/consult-plain-answer-20261001`(未部署)
## BUG-1140 | staging 镜像打包失败:`frontend/scripts` 下的研究脚本引用了 `frontend/tests` 的夹具
- 状态:resolved(修复已推 staging;以门禁 publish 通过与 `/api/health` 部署版本为最终证据)
@@ -0,0 +1,86 @@
# PROGRESS · 普通对话说人话、按人分段(2026-10-01)
- 任务书:`docs/tasks/TASK-consult-plain-answer-20261001.md`(origin/staging `00420f89`,含 D8 不限字数)
- 执行:Claude fork 子代理(直接执行模式,产品授权)
- 分支 / worktree:`codex/consult-plain-answer-20261001` / `.worktrees/consult-plain-answer-20261001`,基线 `00420f89`
- BUG 编号:开工核对 `docs/BUG_HISTORY.md` 最大 BUG-1131;staging 上另一份任务书预留 1135~1139;本单用 1132~1134
## 基线与交付门(Node 22.14,`/exec-daemon/node`)
| 项 | 基线 `00420f89` | 本分支 |
| --- | --- | --- |
| `tsc --noEmit` | 0 错 | 0 错 |
| `npm run lint` | 0 error / 126 warning | 0 error / 126 warning |
| `npm test` 总数 | 4,809(pass 4,744 / fail 24 / skip 41) | 4,813(pass 4,748 / fail 24 / skip 41) |
| 失败名单 | 24 条(全是数据库 / 部署脚本类,无 Docker) | 与基线逐条一致(`diff` 无差) |
| `next build` | `/` ○ Static | `/` ○ Static |
| `.next/static` 全部 js 逐个 gzip 合计 | 1,632,304 B | 1,632,341 B(+37 B,+0.002%) |
## 实现
| 任务 | 落点 |
| --- | --- |
| T1 新形状(BUG-1132) | `frontend/src/mastra/product-voice.ts`:删 `OPENER SHAPE`,新写 `ANSWER SHAPE`(先答 → 按对象分段 → 每段人话 + 括号依据 → 行动;人话自检与四条禁止;推断边界;降级路线两条);`HOPE DISCIPLINE` 去「扮演象」;Good 换成父母题按对象分段、单对象事业题不拆段;新增格局名 Bad(改写示意,非事故原文);顾问报告 Bad 理由改写;`natalSpokenReportContract` 改为对象段 → `## 盘上依据`(可省)→ `## 时间怎么看`(可省)→ `## 这周可以做的一件事`,不设字数上限,Never 加空宫与格局名两条 |
| T2 收敛一处(D7) | `frontend/src/lib/consultation-thinking-plan.ts` 新增 `NATAL_ANSWER_SHAPE_SUMMARY`(英文唯一摘要)与 `natalAnswerShapeBody()`(中文用户轮唯一定义);`REPORT_HEADING.support` 改「盘上依据」;`product-voice.ts`、`index.ts`、`skill-binding.ts`、`consultationSpokenHeadingRule("natal")` 引用摘要;`route.ts` 旧路径用户回合引用 `natalAnswerShapeBody()`;思考栏「用盘上的反差回答这个问题」→「先用一句话回答」,「这周可以扮演哪个象」→「这周可以做什么」;续写提示先补未写完的对象段 |
| T2 附带 | `applyThinkingSectionProgress`:开场节在出现任何 H2 时完成(对象段标题不是固定标题);某节标题出现后,它前面被省略的节一并算完成(`盘上依据` 可省)。原逻辑下开场节要等到「盘上依据」出现才完成,而它现在可能不出现 |
| T3 父母卡(BUG-1133) | `frontend/src/lib/consultation-evidence-card.ts`:`parents` 规格加 `karakas: ["MK"]` 与 `roles`;卡上 `roles.mother`(4 宫、宫主、月亮、MK、D12 第 4 宫星座与落星)/`roles.father`(9 宫、宫主、太阳、D12 第 9 宫);PiK 只在引擎表里有时才进(golden 三张盘都是 7 karaka,无 PiK,不记缺口);MK 值只在 `section.karakas` 存一份,角色里写代号;「母亲 / 父亲」进 `EVIDENCE_CARD_LABELS`。`index.ts` 数据卡说明加一句 roles 的用法 |
| T4 空宫(BUG-1134) | 共享口吻「推断边界」一行 + 合同 Never 一条 + 中文用户轮与英文摘要各一句 |
| T5 文档 | `frontend/docs/VOICE.md` 原则 6、7、口语层形状、首轮 / 追问说明与示例表;`CHANGELOG.md`;`docs/BUG_HISTORY.md` BUG-1132~1134;`docs/tasks/README.md`;真机清单 `docs/testing/consult-plain-answer-20261001.md` |
`git grep -n "扮演\|谁在推\|命名成一个格局\|外松内紧" -- frontend/src` → 0 行。普通对话提示词(`product-voice.ts`、`index.ts` 本命 / general / window、`skill-binding.ts`、`consultation-thinking-plan.ts` 本命与追问、`route.ts`)里没有字数上限。
## 改动的既有断言(原值 / 新值 / 原因)
| 文件 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `consultation-voice-contract.test.ts` | `open with the OPENER SHAPE above, heading-free` | `follow the ANSWER SHAPE above. ${NATAL_ANSWER_SHAPE_SUMMARY}` | D1/D7 |
| 同上 | 有「别去应 X 的象,去扮演 Y 的象」「Spoken-layer total ≤ 400」 | 不得出现「扮演」「≤ 400」;须有「不设字数上限,讲清楚为准」 | D1/D8 |
| 同上 | binding 有「after a 3-6 sentence spoken reply」 | binding 有「as the evidence of the product's answer shape … ${NATAL_ANSWER_SHAPE_SUMMARY}」 | D2/D7 |
| 同上 | 合同里「## 盘里支持这个判断的地方, then ## 时间怎么看, then …」 | 对象段规则 + `${REPORT_HEADING.*}` 三标题顺序且「盘上依据」可省 | D2/D7 |
| 同上 | 「The opener carries no action list」 | 「开场不写标题、不写行动」 | 合同引用唯一定义 |
| 同上 | 「First-turn total ≤ 900」 | 不得含 900;「There is no length cap」 | D8 |
| 同上 | 「only what the opener did not already say」 | 「only what the opener and the sections did not already cite」 | D2 |
| 同上 | mastra「each thing said once, first-turn total ≤ 900 characters」 | mastra 引用摘要,不含 ≤900 / ≤400 / 反差 / 扮演 | D7/D8 |
| 同上 | binding「no action list in the opener」「the actions, once」 | binding 不含反差 / 扮演 / ≤400;「the actions appear once」 | D7(语义由摘要携带) |
| 同上 | route「先用不超过 400 字口语…」「别去应 X 的象…」 | route 引用 `natalAnswerShapeBody()`,不含扮演 / 400 / 900 字 | D1/D7/D8 |
| 同上 | 整条 test「the opener is a shape — 反差…」 | 同位置改为「the answer is a shape — conclusion first…」,锁新形状各条 | D1–D4/D8 |
| 同上 | 「不是每个人都有转机,但每个人都有能扮演的象」 | 「…每个人这段时间都有能做的事」+「每次都要说清这段时间能做什么」 | D5 |
| 同上 | 整条 test「the pinned Good example carries the new shape…」(外松内紧定稿) | 「the pinned Good examples split by person…」:父母分段 Good、单对象 Good、格局名 Bad;顾问报告 Bad 理由改「没有盘上依据」 | D1/D2 |
| 同上 | 「A 和 B 都必须是盘上的结构」「反差写那个人…不写心思」 | 「问的是别人时,写那个人在你盘上对应的宫位与代表星说明了什么,不写那个人的心思」 | D1(BUG-1070 目的保留) |
| 同上 | 「本命解读里这三条不写在开场…」「…行动就写在开场结尾」 | 「本命解读里只写在最后一节…出现一次」「…行动就写在回答结尾」 | D2 |
| `consultation-thinking-plan.test.ts` | 步骤标签「用盘上的反差回答这个问题」 | 「先用一句话回答」 | D1/T2 |
| 同上 | `natal` 含「这三个二级标题」 | 含对象分段规则与「然后依次写:」 | D2 |
| 同上 | `natal` 含「全文不超过 900 字」 | 含「不限字数,讲清楚为准」,且无「不超过 N 字」 | D8 |
| 同上 | `natal` 含「反差只写盘上的结构」 | 含人话自检与空宫规则,且无反差 / 扮演 / 谁在推 | D1/D3/D4 |
| 同上 | 追问含「不超过 200 字」 | 含「讲清楚为准,不限字数」,且无「不超过 N 字」 | D8 |
| `consult-answer-the-question-20260927.test.ts` | 写作提示含「不超过 200 字」 | 含「讲清楚为准,不限字数」,且无「不超过 N 字」 | D8 |
| 同上 | 首轮开场步骤标签「用盘上的反差回答这个问题」 | 「先用一句话回答」 | D1/T2 |
| `consultation-entrypoint.test.ts` | route 含「先用不超过 400 字口语直接回答下面的问题…」 | route 含 `natalAnswerShapeBody()…resolvedQuestion.modelQuestion` | D7/D8 |
| `consult-evidence-card-20260927.test.ts` | 「每个卡值都是引擎值」白名单 = `EVIDENCE_CARD_LABELS` 两条 | 同一来源多「母亲」「父亲」两条,断言代码不变 | D6 |
新增测试 4 条:开场节在对象段标题处完成、被省略的节在后面标题出现时完成;形状只定义一处(摘要、合同、用户轮、思考栏标题与摘要一致,均无字数上限与旧修辞);父母卡 roles 逐字段对 golden 三张盘(宫主取自本节、MK、无 PiK 不记缺口、D12 宫位按 D12 上升推算);缺 MK 记缺口。测试总数 4,809 → 4,813。
## T6 效果验证
**模型离线对比:环境缺口。** 本机 `frontend/.env*` 只有 `.example`,环境变量里没有任何模型 key,无法用新旧提示词各跑一次。四个问题(父母 / 只问妈妈 / 事业 / 伴侣)的新旧对比留到部署 staging 后按真机清单走。
**长度副作用核对(只核对,未改任何上限):**
| 项 | 代码里的值 | 位置 |
| --- | --- | --- |
| 回答输出上限 | 16,384 token;回答步开 provider 思考时上限 = 16,384 + 8,192(思考)= 24,576 | `frontend/src/lib/agent-generation-settings.ts` `AGENT_ANSWER_OUTPUT_TOKENS` / `AGENT_THINKING_OUTPUT_TOKENS`;`consultation-tools.ts` 回答步 `thinking: "enabled"` |
| 答题时钟 | 70 s(工具阶段 110 s 另算,合计 180 s) | `frontend/src/mastra/consultation-tools.ts` `CONSULTATION_ANSWER_TIMEOUT_MS`;续写与重试共用同一个钟,钟到就中止并按未写完处理 |
| 时钟依据(代码注释里的实测) | 默认模型约 90–100 token/s(含首字延迟)→ 70 s 约 6,300–7,000 token;吞吐减半时约 3,000 token | 同上注释,引 `PROGRESS-report-writer-failure-20260902` |
估算:中文正文按每字约 0.7–1.5 token 计,1,500–2,500 字 ≈ 1,100–3,750 token,远低于 16,384 的输出上限,**不会被 token 上限截断**。时钟方面:思考 token 也吃这 70 s;正常吞吐下 2,500 字正文 + 1,000–2,000 思考 token ≈ 2,100–5,750 token,放得下;**吞吐掉到一半(约 45 token/s)时,2,000 字以上的回答加上思考就可能撞上 70 s**,结果是最后一节写不完。所以风险只在「慢模型 / 高峰期 + 很长的回答」,已写进真机清单第 7 步,请产品遇到截断时报来;是否调钟由 Claude 与产品决定,本单未动。
## 偏离与未做
- 今日入口(`dailyAnswerShapeInstruction` / `consultationSpokenHeadingRule("daily")` 的「≤ 400」「每句 ≤ 30 字」)与 `index.ts` 今日星语短句、个人报告 headline 的字数限制**没动**:它们不是普通对话的回答形状,任务书 D8 列举的也只是开场 / 首轮 / 追问。如需一起放开另开单。
- `chat-answer-split.ts` 仍把旧标题「盘里支持这个判断的地方」缩写成「盘上依据」:历史会话里存的是旧标题,保留兼容;新回答直接就是「盘上依据」。
- 纯寒暄豁免那句仍叫「不要套开场形状」(未改字,三处测试依赖这句原文,语义仍对)。
- `skills/` 未改,Skill 版本不 bump。
## 变基复跑
推分支前 `origin/staging` 前进到 `c304cf3a`(BUG-1140 构建修复 + 新增 `runtime-sources-no-test-imports` 测试 2 条)。本分支变基到其上后复跑:tsc 0;lint 0 error / 126 warning;`npm test` 4,815(pass 4,750 / fail 24 / skip 41),失败名单与基线逐条一致;`next build` `/` ○ Static。`docs/BUG_HISTORY.md` 冲突按编号顺序合并(1132~1134 在 1140 之前)。
+1 -1
View File
@@ -395,4 +395,4 @@
- 一轮结束后不删除文件;结论沉淀到 `docs/BUG_HISTORY.md`(Bug)、`CHANGELOG.md`(行为/skill 变化)、`frontend/DESIGN.md`(视觉与交互合同)。
- 任务书里的行号会随代码漂移,定位以符号名为准。
- **实现合入 `staging` 的同一次推送里,必须同时把状态板那一行改掉。** 2026-09-16 的对账发现 10 份早已合入的单仍写着「待领取 / 待验收」,会导致重复派活。
| `TASK-consult-plain-answer-20261001.md` | `PROGRESS-consult-plain-answer-20261001.md` | 普通对话「还是废话」:09-17 四步开场形状(格局名 → 谁推谁修 → 扮演哪个象)逼出谜语,问父母时爸妈被揉成一段;产品授权推翻该形状,改成先答 + 按问题里的对象分段 + 人话自检 + 空宫不单独下结论 + 父母卡标 mother/father(BUG-1132~1134) | **待领取** | 分支 `codex/consult-plain-answer-20261001` |
| `TASK-consult-plain-answer-20261001.md` | `PROGRESS-consult-plain-answer-20261001.md` | 普通对话「还是废话」:09-17 四步开场形状(格局名 → 谁推谁修 → 扮演哪个象)逼出谜语,问父母时爸妈被揉成一段;产品授权推翻该形状,改成先答 + 按问题里的对象分段 + 人话自检 + 空宫不单独下结论 + 父母卡标 mother/father(BUG-1132~1134) | **已实现,待 Claude 验收**(fork 子代理直接执行);未推 staging、未部署;模型对比为环境缺口,真机清单 `docs/testing/consult-plain-answer-20261001.md` | 分支 `codex/consult-plain-answer-20261001` |
@@ -0,0 +1,13 @@
# 真机清单 · 普通对话说人话、按人分段(2026-10-01)
对应任务书 `docs/tasks/TASK-consult-plain-answer-20261001.md`(BUG-1132~1134)。在 staging 上,用自己的账号走;`deepseek-v4-flash` 走一遍,再换一个模型走一遍。每一步只看结果对不对,不用看代码。
| 步 | 怎么做 | 通过标准 |
| --- | --- | --- |
| 1 | 新开对话,问「我和父母关系如何,他们怎么对待我」 | 开场第一句就是结论;下面有「你和妈妈」「你和爸爸」「他们怎么对你」三段(标题用词可以略有不同,但妈妈、爸爸必须分开);没有「外X内Y」这类四字格局名,没有「推这一段的是……」「修形式的是……」「去扮演……」;没提空宫,或提了也讲了这个宫的宫主落在哪 |
| 2 | 把第 1 步每段括号外的话念给一个不懂占星的人听 | 他能复述大意 |
| 3 | 同一对话里追问「那我爸呢」 | 第一句就答,只讲爸爸,不把整份解读重讲一遍,没有标题(长短不限) |
| 4 | 新开对话,问「我的事业接下来怎么走」 | 不拆成多个人的段落,开场后直接是依据、时间、这周可以做的事 |
| 5 | 新开对话,打「你好」 | 只回一句寒暄 |
| 6 | 用没填出生分钟的人物档案问事业 | 不点个人大运,不写月份 |
| 7 | 第 1 步的回答如果明显被截断(最后一节没写完就停了) | 记下时间和模型名报给 Claude——这是取消字数上限后要盯的风险(见进度记录「长度副作用核对」) |