Files
Jyotisha/docs/tasks/TASK-rectification-year-focus-overlay-20260916.md
T
Jesse_ChenandClaude Fable 5.1 abcfdac523 docs(tasks): 年月焦点单追加产品决策:删掉领域/年月录入卡,年月阶段改打字回答
推翻 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
2026-09-16 14:41:00 +00:00

131 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务书 · 删掉录入卡 + 年月阶段焦点被模型追问覆盖:问题块不对齐、答完撞步骤上限(2026-09-16)
## 0. 基线
- 基线 commit`05cf20fb``origin/staging` headstaging 已部署同一 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:<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. 根因(按层)
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-908911T3 按实证状态写 `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 后,出现的是一句「大概哪年几月?」,**没有**「哪一类事 + 发生年月」的卡;在输入框打字给年月后助手只说「记下了」,不追问「是不是你本人」;如有下一题则是下一题。
- 若助手仍口头追问了别的,回一句短句不应再出现「本轮达到步骤上限」。
- 任何独立问题块(题干 + 卡)左边线与助手正文对齐。