Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
12 KiB
PROGRESS · 普通对话回答问的那句话(2026-09-27)
任务书:TASK-consult-answer-the-question-20260927.md。执行方:Claude 子代理(产品要求直接执行)。分支 codex/consult-answer-the-question-20260927,工作树 .worktrees/consult-answer-the-question-20260927,未推送。
基线
origin/staging=2ce3c216(任务书含首轮原文与 R6 的那一版)。D8 已由产品确认,T6 已做。- 开工实测(Node 22.14,
npm test):4192 条 / pass 4096 / fail 66(TAP 行 24 条not ok,均为无 Docker 的 DB / 部署套件)/ skipped 30。失败清单存 scratchpadbaseline-fails.txt。 - BUG 编号:
BUG_HISTORY.md最大 1063,任务书预留至 1069,本单从 1070 起。
做了什么
| 任务 | 改动 | 落点 |
|---|---|---|
| T1 口气层 | 反差的 A、B 限定为盘面结构,问人时写宫位与代表星;删「这句我按『……』理解了」改成两种读法才写「我按 A 答」;新增「不揣测」段(六个禁写短语 + 禁替用户 / 当事人陈述动机需求感受);是非题答法;Good / Bad 是非题样例(虚构盘面);合同 Never 段加「写盘把人放在哪,不写人要什么」 | frontend/src/mastra/product-voice.ts、frontend/docs/VOICE.md |
| T6 首轮只说一遍(D8) | REPORT_HEADING 去掉 question(申报窗口保留 WINDOW_OPEN_HEADING);开场不带行动;正文三标题;依据节只写开场没说过的、表格与正文二选一;行动只在最后一节;首轮 ≤ 900 字。四处同步:natalSpokenReportContract、natalAnswerShapeInstruction()、NATAL_REPORT_SKELETON、index.ts 写作句;route.ts grounded 兜底两处内联指令合并为一份 legacyNatalInstruction;续写提示与 consultationSpokenHeadingRule("natal") 同步 |
consultation-thinking-plan.ts、product-voice.ts、skill-binding.ts、index.ts、route.ts |
| T2 追问轮 | followUpAnswerShapeInstruction()、hasPriorAssistantAnswer(history)、natalUserTurnShape({ entrypoint, history });route 用户轮按它切;grounded 兜底同样切;agent context 与思考计划带 followUpTurn。工具仍每轮调用 |
consultation-thinking-plan.ts、route.ts、consultation-tools.ts |
| T3 清单是核对项 | index.ts 加「The checklist is a list of things to check, not a list of paragraphs to write…」;合同第 2 条加「cite only the fields that support this judgment, and only what the opener did not already say」 |
index.ts、product-voice.ts |
| T4 步骤栏 | 第一节标题 OPENER_THINKING_TITLE = "先回答你问的这件事";首轮步骤「用盘上的反差回答这个问题」,追问轮「直接回答这句话」;该节 heading 与「盘里支持」共用 |
consultation-thinking-plan.ts、frontend/DESIGN.md |
| T5 记录 | BUG-1070~1073(fixed_pending_acceptance)、CHANGELOG 一条(Skill 不 bump)、真机清单、本文件、README 索引 |
docs/**、CHANGELOG.md |
思考栏节的 heading 选择(任务书允许执行方自选)
开场节保留为思考栏第一节,heading 指向第一个正文 H2(「盘里支持这个判断的地方」):第一个正文 H2 出现时开场节与依据节一起收口;追问轮整篇无 H2,四节在结算时收口(applyThinkingSectionProgress 的 settled 分支已有)。没有删节,thinkPlanSteps 的 id 序列不变,改动量最小。
测试
新增:frontend/tests/consult-answer-the-question-20260927.test.ts(2 条:追问轮真实 Agent + 记录提示词假模型 + 公开盘 golden:写作步提示词含追问形状、工具 1 次 / 模型 2 次、无标题短正文 < 160 字整段放出并扣点一次、思考栏步骤「直接回答这句话」;首轮对照);consultation-thinking-plan.test.ts +1(追问指令、hasPriorAssistantAnswer 四种历史、natalUserTurnShape 三种入口、追问计划);consultation-voice-contract.test.ts +1(禁写短语、反差限盘面、是非题答法、样例、行动位置)。测试支撑 consult-natal-agent-test-support.ts 加 instruction / followUpTurn 两个可选项(默认值不变)。
改既有断言(原值 / 新值 / 原因,注释也写在测试里)
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
consultation-voice-contract.test.ts |
match(voice, /## 先回答你的问题/) |
doesNotMatch + 三标题顺序、开场无行动、Say each thing once、≤ 900 字、只引支撑字段、行动只在最后一节、合同末句改追问轮 |
D8 / BUG-1073 |
consultation-voice-contract.test.ts |
match(voice, /这句我按『……』理解了,不对你纠正我/) |
doesNotMatch + 「两种实质不同的读法时才写『我按 A 答』」「单义的问题直接答,不改写它」 |
D4 / BUG-1070 |
consultation-thinking-plan.test.ts |
四节 heading [question, support, timing, action] |
[support, support, timing, action],首节 title「先回答你问的这件事」,不含「先抓住你真正在问的事」 |
D5 / D8 |
consultation-thinking-plan.test.ts |
window heading = REPORT_HEADING.question |
WINDOW_OPEN_HEADING(字面不变) |
正文标题表删掉 question,申报窗口不在本单范围 |
consultation-thinking-plan.test.ts |
拆分测试四标题、首节切片含「外松内紧」 | 三标题、开场留在 preamble | D8 |
consultation-thinking-plan.test.ts |
写出 ## 先回答你的问题 → 首节 done、第二节 active |
无标题时首节 active;写出 ## 盘里支持 后前两节 done、时间节 active |
开场与依据节共用 heading |
consultation-thinking-plan.test.ts |
指令含四个标题 | 三个标题 + 开场不写行动、只在这里出现一次、≤ 900 字、不写「先回答你的问题」、反差只写盘上 | D8 |
consultation-thinking-plan.test.ts |
续写夹具 / 续写标题清单用 question | 用 support;续写提示不含「## 先回答你的问题、」 | D8 |
consultation-run-timeline.test.ts |
正文夹具 ## 先回答你的问题;思考行「先抓住你真正在问的事」;写作行 write-先回答你的问题 |
口语开场 + ## 盘里支持…;「先回答你问的这件事」;write-盘里支持这个判断的地方 |
D5 / D8(BUG-1072/1073) |
consultation-run-timeline.test.ts |
settled 夹具以 ## 先回答你的问题 开头 |
口语开场无标题再接「盘里支持」 | D8 |
consult-single-pass-answer-20260927.test.ts |
match(route, /natalAnswerShapeInstruction\(\)/) |
natalUserTurnShape({ entrypoint: consultEntrypoint, history })、followUpTurn 接线、doesNotMatch 旧调用 |
D3 / D6(BUG-1071) |
consult-single-pass-answer / condensed-checklist / evidence-lookup / gender-card 四个 ANSWER 夹具 |
含 ## ${REPORT_HEADING.question} 一节 |
该节删除,开场直接到 ## 盘里支持 |
D8 |
consult-answer-truncation-20260926.test.ts |
夹具标题 REPORT_HEADING.question |
REPORT_HEADING.support(只需任意一个标题) |
D8 |
consultation-agentic-runtime.test.ts |
sections.some(heading === REPORT_HEADING.question);5 处正文夹具标题 question |
sections.some(title === OPENER_THINKING_TITLE);夹具改 support |
D5 / D8 |
consultation-smalltalk.test.ts |
match(contract, /One natal … question still uses the opener-plus-skeleton/) |
/The first natal … answer of a session uses the opener-plus-skeleton above/ + 追问轮句 |
D3(BUG-1071) |
frontend/tests/agent-voice-copy-contract.test.ts 里的 marriageConsultOpen: ["## 先回答你的问题", …] 是 Task 0 冻结的「修前快照」数据,不是对现状的断言,未改。chat-answer-split.ts 的 shortenAnswerHeading 保留「先回答你的问题 → 先回答」给历史消息渲染。Python tests/ 无按这些文案断言的用例(已 grep)。
门禁数字(完工实测,Node 22.14,本工作树)
| 项 | 开工基线 | 完工 | 结论 |
|---|---|---|---|
tsc --noEmit |
— | 0 错 | 通过 |
npm run lint |
— | 0 error(127 warning,既有) | 通过 |
npm test 总数 |
4192 | 4196(+4:新文件 2、thinking-plan 1、voice 合同 1) | 通过 |
npm test pass / fail / skipped |
4096 / 66(TAP 顶层 not ok 24 条)/ 30 |
4142 / 24 / 30 | 失败名单与基线 24 条逐条一致(diff 为空),均为无 Docker 的 DB / 部署套件;退出码 1 即这 24 条 |
| 消失的测试名 | — | 0(只有新增) | 通过 |
next build --webpack |
— | 退出 0;/、/chart、/ephemeris、/people 均 ○ Static |
通过 |
| 首屏 rootMainFiles gzip(9) | 130,933 B(PROGRESS-consult-evidence-card-v2 / single-pass 口径) |
130,933 B(0.00%) | 通过 |
index.html 引用的 _next/static JS gzip(9) |
660,412~661,444 B(36 个,gender 单口径) | 618,938 B(33 个,本单用正则从 .next/server/app/index.html 取,文件数不同,口径未必相同) |
供参考;rootMainFiles 为准 |
frontend/src/app/(app)/page.tsx |
— | 未改 | 通过 |
提交
9a42333afeat(consult):T1/T6/T2/T3/T4 代码与测试(18 个文件,+416/−70)。- 第二个提交:T5 记录(BUG-1070~1073、CHANGELOG、VOICE、DESIGN、真机清单、本文件、README、任务书 D8 确认)。
偏离与说明
- 提交分组:任务书要求按 T1/T6、T2/T3、T4、T5 分四次提交;T2(
followUp)与 T6(标题表)落在同一批文件(consultation-thinking-plan.ts、route.ts、同一批测试)的相邻改动里,按 hunk 拆分不可靠,改为两次提交:代码 + 测试一次,记录一次。 next build默认 Turbopack 对软链的node_modules报Symlink … points out of the filesystem root(与PROGRESS-consult-gender-optional-20260927同一现象),改用next build --webpack量/状态与首屏 gzip;数字口径与该记录一致(rootMainFiles,gzip level 9)。- lint 有 127 条既有 warning(0 error),不在本单范围,未动。
- 真实模型输出未验(无凭据):见
docs/testing/consult-answer-the-question-20260927.md。
Claude 验收补修(2026-09-27 晚)
- 发现:
hasPriorAssistantAnswer只看role === "assistant",而寒暄快路(BUG-976/977)的回复也以responseKind: "smalltalk"存成 assistant 消息(route.tsp_response_message),历史窗口又把responseKind丢掉。后果:用户先说「你好」再问第一个正式问题,会被当成追问轮(≤ 200 字、无骨架)。会话摘要把早先轮次折掉后 tail 里可能没有 assistant,则反过来误判为首轮。 - 修:
consultation-session-history.ts的StoredTurn/ConsultationHistoryMessage带可选responseKind,窗口 tail 透传;hasPriorAssistantAnswer(history, { summaryText })忽略寒暄回复、摘要存在即追问;natalUserTurnShape与route.ts两处调用带historyWindow.summaryText。product-voice.tsNever 段一句改措辞(原句第二个分句读成也被禁止)。 - 断言三栏:
consult-single-pass-answer-20260927.test.ts(route 源码匹配:原值不带 summaryText / 新值带 / 原因如上)、consultation-voice-contract.test.ts(Never 句:原值分号并列 / 新值拆两句 / 原因措辞)。新增用例见consultation-thinking-plan.test.ts、consultation-session-history.test.ts。 - 复跑门禁数字见下方「Claude 验收」。
Claude 验收(2026-09-27 晚,Node 22.14,本工作树,含补修)
| 项 | 结果 |
|---|---|
tsc --noEmit |
0 错 |
npm run lint |
0 error(既有 warning 未动) |
npm test |
4197(基线 4192,+5);pass 4143 / fail 24 / skipped 30;24 条失败按名称与基线逐条一致(无 Docker 的 DB / 部署套件) |
next build --webpack |
exit 0;/、/chart、/ephemeris、/people 仍 ○ Static;首屏 gzip 以执行方实测 130,933 B(=基线)为准,补修只改服务端与 lib 类型 |
| 真机 | 未做(无登录态、无模型凭据),见 docs/testing/consult-answer-the-question-20260927.md 8 条 |
结论:T1–T6 通过;补修一处(寒暄回复误判追问)已并入本分支。推 staging 后核对 /api/health 的 deployment.gitCommit。