推翻 precision-gate-guided-collect 单 T3 与 SKILL.md:84 的「不得邀请自由打字」,BUG-750 失去对象。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
18 KiB
18 KiB
任务书 · 删掉录入卡 + 年月阶段焦点被模型追问覆盖:问题块不对齐、答完撞步骤上限(2026-09-16)
0. 基线
- 基线 commit:
05cf20fb(origin/staginghead,staging 已部署同一 SHA,含 BUG-747~752 的引导式补经历)。 - 分支:
codex/rectification-year-focus-overlay-20260916,git worktree add -b codex/rectification-year-focus-overlay-20260916 .worktrees/rectification-year-focus-overlay-20260916 origin/staging。 - 串行:与
TASK-console-noise-20260916.md无文件交集,可并行。 - BUG 段:BUG-908 起(BUG-906/907 已被控制台噪音单占用;开工时核对
docs/BUG_HISTORY.md最大号)。 - Skill:本单要改
SKILL.md的一句话(§5 T1c),按既有规则 bump 到 10.0.29,历史会话可打开的合同测试照跑(BUG-621 教训)。
1. 事故实证(产品负责人 2026-09-16 staging 真机)
对话顺序(用户原话已脱敏,只保留结构):
- 服务器定向补事存在题:「身体上有没有过住院、手术、受伤或特别难熬的一段时间,哪一年都算?」A/B/C/D。用户选 A「有,我来填时间」。
- 用户在输入框打字给了「YYYY年M月做过手术」。
- 助手正文:「记下了。」+ 「YYYY年M月这次手术,是你本人做的吗?」。时间轴「已完成 4 步 / 记下了:YYYY年M月做过手术。」→ 事件确实入账。
- 消息底部只读范围行:「目前范围 04:48–05:07,再说一件带年月的事就能继续」。
- 消息之后又出现一个独立问题块:同一句「是你本人做的吗?」,下面挂着「哪一类事(学业/事业/搬家/感情/家人/财务/健康)+ 发生年月」录入卡。产品原话:「为什么下面问问题了又出现个选择时间的卡,这个卡没用啊」「rectification-message-question UI 没跟消息对齐」。
- 用户回答了这个追问(是/不是一类的短句)→ 整轮报错 「本轮达到步骤上限,状态已记录。」
代码定位
| 现象 | 位置 | 说明 |
|---|---|---|
| 问题块 + 录入卡 | frontend/src/components/rectification-agentic-chat.tsx:1741-1780(questionGap === "persisted_question" 分支) |
渲染 currentQuestion.prompt,且只要 yearEntry 非空就挂 EventDateEntryCard;yearEntry = parseYearEntryQuestionId(currentQuestion.question_id)(:1427),只看题号是不是 collect:targeted:<domain>:year / collect:guided:window:…:year,不看题干写的是什么 |
| 题干被模型覆盖 | frontend/src/mastra/rectification-v9-tools.ts:1218-1270(rectification-set-focus,活动焦点与 input.questionId 同号的分支) |
withSpokenPrompt({...activeFocus.expectedAnswerSchema}, spoken.prompt)(spoken-prompt.ts:99-113):schema 不是 targeted_collect 就直接把 schema.prompt 换成模型的话,event_date_entry: true 原样保留。于是「大概哪年几月?」变成「是你本人做的吗?」,卡还在 |
| 校验拦不住 | spoken-prompt.ts:24-97 validateSpokenPrompt |
无 choice_frame 的采集题只查长度、单问号、领域关键词;「手术」命中健康关键词即通过 |
| 年月焦点为什么还活着 | rectification-v9-tools.ts:1628-1646(batch 写入后 resolve 活动采集焦点,条件 isCollectFocusSchema);collection-question-pool.ts:881-905 pendingTargetedYearDomain(存在题 resolved、领域未覆盖、年月题未关闭 → 继续出 :year) |
事件已入账(时间轴有回执)但最终画面仍是 :year 焦点。两条可能:(a) 选 A 后由 answer-choice.ts:934 persistNextInterviewAfterChoice 落的年月焦点 schema 不满足 isCollectFocusSchema,batch 没 resolve;(b) 模型先 set-focus 后 batch,或 batch 后计划层因事件 domain/kind 没映射到 health 又出了一次 :year。执行方必须用回放测试定出是哪一条(§5 T2) |
| 追问本身不该有 | scripts/rectification/contracts.py:192-200 subject_for_event_domain 默认 self;存在题题干就是「身体上」;rectification-v9-tools.ts:1515 batch 的 subject 默认 self |
服务器从没出过「主体」题。模型是按 SKILL.md:100「needs_clarification 不得猜补主体」自己加的追问;年月阶段的 hint(method-followup.ts:2493「若用户已说带年份的经历走 batch;否则继续问一件带大概年份的事」)没有禁止追问 |
| 不对齐 | frontend/src/app/globals.css:2969-2981 .rectification-message-question 无 margin-inline-start;同块里的 .rectification-event-entry(:3089)自带 margin-inline-start: var(--assistant-content-inset);.rectification-question-gap(:2985)也带 |
消息内的同名块(rectification-message-entry.tsx:169,经 chat-message-row.tsx:148 的 afterAnswer)在消息正文里,天然有缩进;独立的 persisted 块在消息外,题干顶到左边、卡片却缩进了,三者三个左边线 |
| 步骤上限 | agent-run.ts:733 evidence 动作预算 8 步;run-diagnostic.ts:88-90 无正文即 max_steps;agent-run.ts:898-910 host fallback 只在 batch completed 时兜底 |
用户回答「是/不是」时活动焦点仍是 :year 录入 schema,模型拿到的是「有年份走 batch,否则再问」的 hint,短句没有年月 → batch 写不进(needs_clarification/rejected)→ set-focus 同号幂等 → 循环到 8 步没正文;batch 没 completed 所以 fallback 也不接。这是推断,不是实证,本单按 investigating 记,T3 的回放测试要么复现要么写明复现不了 |
2. 根因(按层)
- 数据层:年月阶段焦点是服务器题(题干固定「大概哪年几月?」,答案走录入卡或带年月的自由文本),但
set-focus允许模型用同号覆盖它的题干。定向存在题在 BUG-673 已经堵了这条(targeted_collect只存spoken_prompt侧字段,不动prompt),年月阶段没堵。 - 计划层:事件入账后年月焦点没有随之关闭(§1 表 (a)/(b) 二选一,待回放定)。
- 提示层:年月阶段的 hint 没说「用户给了年月就到此为止,不追问主体/原因/细节」;
SKILL.md:100的「不得猜补主体」被模型读成「要去问主体」。 - 表现层:persisted 问题块没有和消息同一条左边线。
3. 决策记录
- 产品负责人 2026-09-16 授权:追问句下面不得出现录入卡;问题块与消息对齐;答完不得撞步骤上限。
- 产品负责人 2026-09-16 追加拍板:「哪一类事 + 发生年月」录入卡整个删掉,年月阶段直接打字回答。 这条推翻
TASK-rectification-precision-gate-guided-collect-20260916.mdT3「引导题用选项 + 年/月选择器录入,不得邀请自由打字」与SKILL.md:84同一句,也让 BUG-750(录入卡提交文本格式)失去对象。执行方据此删除,不得以旧任务书为由拒改。原则沿用用户偏好「多余入口宁可删除也不修」。 - 年月阶段的题干归服务器:模型的 spokenPrompt 只能作旁白侧字段,不能替换
prompt。这不推翻 BUG-673(同一原则扩到年月阶段)。 - 定向健康题主体默认本人,不追问。
truth-consent-boundaries.md:30「亲属主体必须显式标记」指的是用户自己说了「我妈住院」这类话时要标family,不是让模型去问。用户原话没有亲属就是self。 - 用户在年月阶段用自由文本给了年月,等同于录入卡提交;不得再要求用卡。
- 不改出卡时机、不改
precision_gate、不改 BUG-751 的「线没问完不出卡」。
4. 硬红线
page.tsx不增长;rectification-agentic-chat.tsx不新增 hook。- BUG-673 的防复发(定向存在题焦点只允许点选形态;
persisted_question块不得画成卡片绕数据层)原样保留;本单是补年月阶段,不是重写焦点层。 - BUG-750 的录入卡提交文本格式不变;BUG-749 的
GUIDED_ANY_DOMAIN解析不变。 - 既有断言只允许因 schema 形状变化而更新,写「原值 / 新值 / 原因」三栏;不得静默弱化。
- Skill bump 后
frontend/tests/里的历史会话可打开合同测试必须绿(BUG-621)。 - 不得用生日推年份;不得因为本单把
subject追问改成服务器题。
5. 任务分解
T0 删掉录入卡,年月阶段改成打字回答(BUG-908 的一部分,不另占号)
- 删除文件:
frontend/src/components/event-date-entry-card.tsx、frontend/src/components/event-date-picker.tsx;globals.css:3089-3130一带.rectification-event-entry*与选择器样式整段删;rectification-agentic-chat.tsx:1427的yearEntry/yearRange与:1760-1775的卡片分支删,persisted 块只剩题干(题干 + 点选卡的分支不动)。 - 数据层:
server-focus.ts:345-352的event_date_entry/default_domain/default_year/default_month不再写;collection-question-pool.ts的parseYearEntryQuestionId、eventDateYearRange、formatEventDateEntryMessage删除。:year题号与TARGETED_YEAR_PROMPT「大概哪年几月?」保留,它就是一道口述题,用户打字答;引导窗口题(collect:guided:window:…:year)同样口述,题干里已带年份和月份区间。 - 文案:
choice-card.ts:30/34选项 A「有,我来填时间」改「有,我来说」;TARGETED_COLLECT_WHY_USER「答有的话再用选择器填年月」改「答有的话直接打字说大概年月」。输入框占位RECTIFICATION_COLLECT_WAITING_PLACEHOLDER若提到选择器一并改。对照frontend/docs/VOICE.md。 - Skill:
SKILL.md:84「引导题用选项 + 年/月选择器录入,不得邀请自由打字」改为「引导题答『有』后,服务器口述题『大概哪年几月?』,用户打字回答;不要再出点选卡」。与 T1c 同一次 bump 到 10.0.29。 - 记录:
frontend/DESIGN.md删录入卡段落;docs/BUG_HISTORY.md的 BUG-750 补一行「2026-09-16 录入卡按产品决策删除,本条防复发『账本文本不得复用题干例子列表』仍适用于任何用户可见文本」;frontend/tests/rectification-guided-collect-20260916.test.ts里断言卡片的用例删除并在进度记录写「原值 / 新值 / 原因」,总数不减的要求用新增的 T1~T4 契约测试补足。 - 验收:
git grep -n "event_date_entry\|EventDateEntryCard\|EventDatePicker\|rectification-event-entry" frontend/src为 0 命中;选项 A 点击后快照活动问题为:year口述题,题干「大概哪年几月?」,无卡;/仍 Static,首屏 gzip 只降不升。
T1 年月阶段焦点不可被模型覆盖(BUG-908)
- T1a
spoken-prompt.tswithSpokenPrompt:T0 删掉event_date_entry后,以题号为准——set-focus的questionId经parseTargetedCollectQuestionId/parseGuidedWindowQuestionId解析出stage === "year"时,与targeted_collect === true同等对待,只写spoken_prompt,不动prompt(识别以题号前缀为准,与 BUG-673 同口径)。 - T1b
turn-decision.tsprojectCurrentQuestion(:85起):年月阶段题投影prompt时只用服务器prompt,忽略spoken_prompt。前端persisted_question分支的题干因此恒为「大概哪年几月?」类服务器题干。 - T1c 提示层:
method-followup.ts:2493那条active_focushint 与TARGETED_YEAR_PROMPT所在的年月 followupuser_prompt_hint,补一句「用户给了年月就走 batch 写入并结束本题;不追问主体、原因、细节;定向健康题主体默认本人」。SKILL.md:100改为「needs_clarification不得猜补日期、主体……;用户原话没有亲属时主体就是本人,不要追问『是不是你本人』」。Skill bump 10.0.29。 - 验收:
- 新契约测试:活动焦点为
collect:targeted:health:year时调用set-focus,同号、spokenPrompt 为一句不含年月的追问 → 焦点expected_answer_schema.prompt不变,spoken_prompt为模型句;projectCurrentQuestion得到的prompt是服务器题干。 rectification-guided-collect-20260916.test.ts与rectification-targeted-*.test.ts原断言绿。
- 新契约测试:活动焦点为
T2 事件入账后年月焦点必须关闭(BUG-909)
- 先写回放测试定根因:存在题答 A →
persistNextInterviewAfterChoice落年月焦点 → 用 batch 写一件同领域带年月事件 → 断言活动焦点status=resolved,且buildMethodFollowupPlan的next_followup不再是该领域:year。- 若红在 resolve:修
answer-choice.ts落年月焦点的 schema,使其满足isCollectFocusSchema(带COLLECT_FOCUS_SCHEMA_KEY与prompt),或修 batch 的 resolve 条件按题号前缀识别:year(与 BUG-673「识别以题号前缀为准」同口径)。 - 若红在计划层:修
coveredCollectKinds/ 事件kind→CollectKind映射,让「手术/住院」类事件覆盖health;并在pendingTargetedYearDomain里把「该领域已有本轮之后写入的带年月事件」也算yearClosed。
- 若红在 resolve:修
- 顺带:
rectification-propose-evidence(单条写入,rectification-v9-tools.ts:1708起)现在不 resolve 采集焦点,只有 batch 会。让两条写入路径走同一段 resolve 逻辑(抽函数),否则模型走单条工具时 T2 复发。 - 验收:回放测试绿;两条写入路径都有 resolve 断言;进度记录写明定出的是 (a) 还是 (b)。
T3 年月阶段收到无年月短句不得烧完步骤(BUG-910,先 investigating)
- 在
agent-run.ts:309-335日期可靠性分类器旁边加同形状的 host 前置:活动焦点题号为年月阶段(:year)且用户消息里没有年份(19xx/20xx或「x 年」)→ 不进模型,host 直接以服务器题干再问一次(复用emitVisibleSpoken路径),不扣点、不占步骤;有年份则照常进模型。 - 用回放测试复现 §1 的循环:活动
:year焦点 + 消息「是」→ 修前应到max_steps(或写明复现不了,改记investigating并保留 host 前置作为防御);修后 host 前置接住。 - 卡片消失的原因也在这条链上:录入卡只跟着快照里活动焦点的
:year题号走;失败轮的工具写入不回滚(agent-run.ts:1084-1087直接failedAttempt,previousFocusId只上报不恢复),模型在那 8 步里 set-focus 换题号(supersede)、resolve-focus 或 batch 关掉了年月焦点,轮结束loadCaseSnapshot重读后活动焦点已不是它,卡随之消失,而接替的题又没落成。回放测试要断言:失败轮不得只关掉年月焦点而不留下可答的题——要么焦点原样保留(卡还在),要么接替题已持久化。 - 验收:
run-diagnostic的max_steps映射不变;applyHostFallback不改条件;新测试绿。
T4 persisted 问题块与消息同一条左边线(BUG-911)
globals.css:给rectification-agentic-chat.tsx:1741的独立块加修饰类(如.rectification-message-question.is-standalone),margin-inline-start: var(--assistant-content-inset);块内的.rectification-choice-card.is-embedded把自己的margin-inline-start归零,避免双重缩进(录入卡已在 T0 删除)。消息内的同名块不动。- 验收:
chart-page-view.test.tsx同款的 CSS 声明断言:独立块有 inset、块内卡片 inset 为 0;frontend/DESIGN.md记一句「校正独立问题块与助手正文共用--assistant-content-inset」。
T5 记录
docs/BUG_HISTORY.md:BUG-908~911(T3 按实证状态写resolved或investigating)。CHANGELOG.md:年月阶段不再被追问覆盖;问题块对齐;Skill 10.0.29。docs/tasks/PROGRESS-rectification-year-focus-overlay-20260916.md:tsc / lint / test 数字与基线失败清单逐条比对、gzip、/Static。docs/testing/rectification-year-focus-overlay-20260916.md真机清单(§8)。
6. 让步顺序
- T3 的 host 前置若与
date_reliability分类器抢同一段(两者都看活动焦点 schema),按「先日期可靠性、后年月录入」排序,写进注释。 - T2 若两条根因都红,先修 resolve(数据层),计划层的
yearClosed扩展作第二提交。 - T1c 的 Skill 改动若因 hash 合同测试卡住,可只改 hint 不改 SKILL.md,进度记录写明,产品下一轮再 bump。
- T4、T5 不可让步。
7. 开工前置命令
git fetch origin --prune
git worktree add -b codex/rectification-year-focus-overlay-20260916 .worktrees/rectification-year-focus-overlay-20260916 origin/staging
cd .worktrees/rectification-year-focus-overlay-20260916/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -20 # 记基线总数与失败清单
grep -o "BUG-[0-9]\+" ../docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 核对起始号
grep -rn "10.0.28" ../skills/jyotish-birth-time-rectification/SKILL.md ../frontend/src/lib/rectification-agentic/*.ts | head # Skill 版本落点
验收口径:tsc 0 错;lint 0 error;npm test 失败清单与基线逐条一致且总数不减;next build 后 / 仍 Static;首屏 gzip ±2%;Python 不动则不跑快速门。
8. 环境缺口(留给产品负责人真机)
- 定向健康题选 A 后,出现的是一句「大概哪年几月?」,没有「哪一类事 + 发生年月」的卡;在输入框打字给年月后助手只说「记下了」,不追问「是不是你本人」;如有下一题则是下一题。
- 若助手仍口头追问了别的,回一句短句不应再出现「本轮达到步骤上限」。
- 任何独立问题块(题干 + 卡)左边线与助手正文对齐。