# 任务书 · 删掉录入卡 + 年月阶段焦点被模型追问覆盖:问题块不对齐、答完撞步骤上限(2026-09-16) ## 0. 基线 - 基线 commit:`05cf20fb`(`origin/staging` head,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 真机) 对话顺序(用户原话已脱敏,只保留结构): 1. 服务器定向补事存在题:「身体上有没有过住院、手术、受伤或特别难熬的一段时间,哪一年都算?」A/B/C/D。用户选 A「有,我来填时间」。 2. 用户在输入框打字给了「YYYY年M月做过手术」。 3. 助手正文:「记下了。」+ **「YYYY年M月这次手术,是你本人做的吗?」**。时间轴「已完成 4 步 / 记下了:YYYY年M月做过手术。」→ 事件确实入账。 4. 消息底部只读范围行:「目前范围 04:48–05:07,再说一件带年月的事就能继续」。 5. 消息之后又出现一个**独立问题块**:同一句「是你本人做的吗?」,下面挂着「哪一类事(学业/事业/搬家/感情/家人/财务/健康)+ 发生年月」录入卡。产品原话:「为什么下面问问题了又出现个选择时间的卡,这个卡没用啊」「rectification-message-question UI 没跟消息对齐」。 6. 用户回答了这个追问(是/不是一类的短句)→ 整轮报错 **「本轮达到步骤上限,状态已记录。」** ### 代码定位 | 现象 | 位置 | 说明 | | --- | --- | --- | | 问题块 + 录入卡 | `frontend/src/components/rectification-agentic-chat.tsx:1741-1780`(`questionGap === "persisted_question"` 分支) | 渲染 `currentQuestion.prompt`,且只要 `yearEntry` 非空就挂 `EventDateEntryCard`;`yearEntry = parseYearEntryQuestionId(currentQuestion.question_id)`(`:1427`),**只看题号**是不是 `collect:targeted::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. 根因(按层) 1. **数据层**:年月阶段焦点是服务器题(题干固定「大概哪年几月?」,答案走录入卡或带年月的自由文本),但 `set-focus` 允许模型用同号覆盖它的题干。定向存在题在 BUG-673 已经堵了这条(`targeted_collect` 只存 `spoken_prompt` 侧字段,不动 `prompt`),年月阶段没堵。 2. **计划层**:事件入账后年月焦点没有随之关闭(§1 表 (a)/(b) 二选一,待回放定)。 3. **提示层**:年月阶段的 hint 没说「用户给了年月就到此为止,不追问主体/原因/细节」;`SKILL.md:100` 的「不得猜补主体」被模型读成「要去问主体」。 4. **表现层**:persisted 问题块没有和消息同一条左边线。 ## 3. 决策记录 - 产品负责人 2026-09-16 授权:追问句下面不得出现录入卡;问题块与消息对齐;答完不得撞步骤上限。 - **产品负责人 2026-09-16 追加拍板:「哪一类事 + 发生年月」录入卡整个删掉,年月阶段直接打字回答。** 这条**推翻** `TASK-rectification-precision-gate-guided-collect-20260916.md` T3「引导题用选项 + 年/月选择器录入,不得邀请自由打字」与 `SKILL.md:84` 同一句,也让 BUG-750(录入卡提交文本格式)失去对象。执行方据此删除,不得以旧任务书为由拒改。原则沿用用户偏好「多余入口宁可删除也不修」。 - **年月阶段的题干归服务器**:模型的 spokenPrompt 只能作旁白侧字段,不能替换 `prompt`。这不推翻 BUG-673(同一原则扩到年月阶段)。 - **定向健康题主体默认本人,不追问**。`truth-consent-boundaries.md:30`「亲属主体必须显式标记」指的是用户自己说了「我妈住院」这类话时要标 `family`,不是让模型去问。用户原话没有亲属就是 `self`。 - 用户在年月阶段用自由文本给了年月,等同于录入卡提交;不得再要求用卡。 - 不改出卡时机、不改 `precision_gate`、不改 BUG-751 的「线没问完不出卡」。 ## 4. 硬红线 1. `page.tsx` 不增长;`rectification-agentic-chat.tsx` 不新增 hook。 2. BUG-673 的防复发(定向存在题焦点只允许点选形态;`persisted_question` 块不得画成卡片绕数据层)原样保留;本单是补年月阶段,不是重写焦点层。 3. BUG-750 的录入卡提交文本格式不变;BUG-749 的 `GUIDED_ANY_DOMAIN` 解析不变。 4. 既有断言只允许因 schema 形状变化而更新,写「原值 / 新值 / 原因」三栏;不得静默弱化。 5. Skill bump 后 `frontend/tests/` 里的历史会话可打开合同测试必须绿(BUG-621)。 6. 不得用生日推年份;不得因为本单把 `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.ts` `withSpokenPrompt`:T0 删掉 `event_date_entry` 后,以题号为准——`set-focus` 的 `questionId` 经 `parseTargetedCollectQuestionId` / `parseGuidedWindowQuestionId` 解析出 `stage === "year"` 时,与 `targeted_collect === true` 同等对待,只写 `spoken_prompt`,不动 `prompt`(识别以题号前缀为准,与 BUG-673 同口径)。 - **T1b** `turn-decision.ts` `projectCurrentQuestion`(`:85` 起):年月阶段题投影 `prompt` 时只用服务器 `prompt`,忽略 `spoken_prompt`。前端 `persisted_question` 分支的题干因此恒为「大概哪年几月?」类服务器题干。 - **T1c** 提示层:`method-followup.ts:2493` 那条 `active_focus` hint 与 `TARGETED_YEAR_PROMPT` 所在的年月 followup `user_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`。 - 顺带:`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. 让步顺序 1. T3 的 host 前置若与 `date_reliability` 分类器抢同一段(两者都看活动焦点 schema),按「先日期可靠性、后年月录入」排序,写进注释。 2. T2 若两条根因都红,先修 resolve(数据层),计划层的 `yearClosed` 扩展作第二提交。 3. T1c 的 Skill 改动若因 hash 合同测试卡住,可只改 hint 不改 SKILL.md,进度记录写明,产品下一轮再 bump。 4. T4、T5 不可让步。 ## 7. 开工前置命令 ```bash 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 后,出现的是一句「大概哪年几月?」,**没有**「哪一类事 + 发生年月」的卡;在输入框打字给年月后助手只说「记下了」,不追问「是不是你本人」;如有下一题则是下一题。 - 若助手仍口头追问了别的,回一句短句不应再出现「本轮达到步骤上限」。 - 任何独立问题块(题干 + 卡)左边线与助手正文对齐。