From c1592c21ad585e47123db8b89bdd0f36a178643a Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sat, 26 Sep 2026 20:14:14 +0800 Subject: [PATCH] docs(tasks): rectification code split progress, changelog, index row Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8 --- CHANGELOG.md | 6 + ...GRESS-rectification-code-split-20260926.md | 171 ++++++++++++++++++ docs/tasks/README.md | 2 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 docs/tasks/PROGRESS-rectification-code-split-20260926.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 32e0d92b..f798f9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 印度占星 Skill 更新日志 +## 2026-09-26 — 生时校正代码内部拆分,无用户可见变化(待验收) + +- 生时校正的聊天组件、`/api/rectification/agent` 接口和 Agent 回合运行器三处大文件按职责拆成小文件(只搬代码、不改行为):聊天组件 2043 → 737 行、`useState` 35 → 12;接口主函数 927 → 114 行;回合运行器主函数 1046 → 25 行。 +- 界面、文案、接口、数据库、打分、计费和 Skill 均不变;新增增长上限测试,防止这三处再长回去。 +- Skill 版本不 bump。不改数据库。 + ## 2026-09-26 — 后台新增「校正统计」:生时校正匿名汇总(待验收) - 每个生时校正会话第一次给出范围卡时,服务端记一行匿名统计:起始窗口半径、出生时间来源类别、按类型分的提问数(定向 / 引导 / 带年月探针 / 性格 / 开放)、补充经历件数、最终范围宽度、候选分钟数、第一名与第二名差几个百分点、停止原因、精度门槛是否达标、用时、算法 / 策略 / Skill 版本。只有数字和类别,不含用户、校正记录或会话编号,不含出生资料、姓名、对话内容和模型原文,时间只精确到「哪一周」。 diff --git a/docs/tasks/PROGRESS-rectification-code-split-20260926.md b/docs/tasks/PROGRESS-rectification-code-split-20260926.md new file mode 100644 index 00000000..33e61028 --- /dev/null +++ b/docs/tasks/PROGRESS-rectification-code-split-20260926.md @@ -0,0 +1,171 @@ +# PROGRESS · 生时校正代码拆分(只搬不改)+ 增长上限(2026-09-26) + +## 当前结论 + +T1–T5 完成,D2 四个目标全部达到,没有走让步。没有新 BUG 号。没有推 `staging`,没有推 `main`。 + +- 工作树:`/workspace/Jyotisha/.worktrees/rectification-code-split-20260926` +- 分支:`codex/rectification-code-split-20260926` +- 开工基线:`origin/staging` = `12cbe6f8`(fewer-probes-card 与 telemetry 均已合入并部署) +- 提交(各自可单独回滚,每个都跑过全量): + +| 阶段 | SHA | 内容 | +| --- | --- | --- | +| T1 聊天组件 | `3a66c39f` | 组件拆成 hook / 参数函数 / 块;切片测试改调用;增长合同(聊天部分) | +| T2 路由 | `eb773f12` | `POST` 拆成分支处理函数;切片测试改调用;增长合同(路由部分) | +| T3 agent-run | `a00c40d4` | `runV9AgentTurn` 拆成准备 / 单次尝试 / 重跑 / 收尾;增长合同(agent-run 部分) | +| T5 记录 | 本提交 | PROGRESS、CHANGELOG、任务索引一行 | + +## 开工实测(`12cbe6f8`,与任务书 3ce11ee0 的数一致) + +| 文件 | 行数 | 函数体 | hook | +| --- | --- | --- | --- | +| `rectification-agentic-chat.tsx` | 2043 | 组件 1625 行 | useState 35、useRef 16、useEffect 7、useLayoutEffect 2、useCallback 12 | +| `app/api/rectification/agent/route.ts` | 1077 | `POST` 927 行(旁边另有 3 个顶层辅助函数) | — | +| `v9/agent-run.ts` | 1400 | `runV9AgentTurn` 1046 行(内嵌 4 个函数) | — | + +行数一律按 `\n` 计数(同 `wc -l`、同增长合同)。 + +## 前后对比与 D2 + +| 项 | 前 | 后 | D2 目标 | 结果 | +| --- | --- | --- | --- | --- | +| 聊天组件文件 | 2043 | 737 | — | — | +| 聊天组件本体 | 1625 | 630 | ≤ 900 | 达到 | +| 组件 useState | 35 | 12 | ≤ 20 | 达到 | +| 组件 useRef | 16 | 9 | — | 上限锁 9 | +| 组件 effect(useEffect + useLayoutEffect) | 9 | 5 | — | 上限锁 5 | +| 组件 useCallback | 12 | 6 | — | 上限锁 6 | +| route.ts 文件 | 1077 | 152 | — | — | +| `POST` | 927 | 114 | ≤ 250 | 达到 | +| agent-run.ts 文件 | 1400 | 135 | — | — | +| `runV9AgentTurn` | 1046 | 25 | ≤ 300 | 达到 | + +## 拆到哪里 + +### T1 聊天组件(`frontend/src/components/rectification-agentic-chat.tsx`) + +| 新文件 | 职责 | React hook | +| --- | --- | --- | +| `hooks/use-rectification-case-snapshot.ts` | 快照同步:17 个快照状态 + 缺题重试 / 修复计数、`applyCaseSnapshot`、`loadCaseSnapshot`、在途快照读取 | 有 | +| `hooks/use-rectification-board-layout.ts` | 盘面窄屏 / 抽屉 / 变化高亮 | 有 | +| `hooks/use-rectification-live-activity.ts` | 活动行计时标签与 4 秒刷新 | 有 | +| `lib/rectification-chat-turn-run.ts` | 原 `send` 函数体:POST、NDJSON 流读取、结算 / 失败 / 停止、回合后重读快照 | 无 | +| `lib/rectification-chat-choice-run.ts` | 原 `submitStructuredChoice` 函数体 | 无 | +| `lib/rectification-chat-accept-run.ts` | 原 `acceptCandidate` 函数体 | 无 | +| `lib/rectification-chat-message-actions.ts` | 复制、重新生成 | 无 | +| `lib/rectification-chat-question-repair.ts` | 缺题重读、「接着问」修复 | 无 | +| `lib/rectification-chat-messages.ts` | 持久 turn → 渲染消息、本地答题标记与撤回、回执视图 | 无 | +| `lib/rectification-chat-snapshot.ts` | 快照载荷的纯读取(当前题、题目来源、揭幕初值) | 无 | +| `lib/rectification-chat-view.ts` | 每次渲染的派生(重新生成按钮归属、点选卡、交付卡、只读范围、缺题态、题目挂载位置、时间轴) | 无 | +| `components/rectification-question-gap-notices.tsx` | 缺题块(独立题卡、准备中、已交付、快照失败、接着问、核对收尾)与只读范围行 | 无 | + +做法:函数体整段原样搬移;参数函数第一行把 `deps` 解构成原来的变量名,所以函数体逐字不变(只少一层缩进)。组件里只留 `useCallback` 包一层、依赖数组**逐字保留原样**(包括原来就漏掉的 `busy` / `conversationAnchor`,行为上等于原来的闭包时机)。渲染期 `setSelectionOfferLock` 仍在组件里、用派生函数返回的 `nextOfferLock`,注释随之移到调用处。 + +### T2 路由(`frontend/src/app/api/rectification/agent/route.ts`) + +| 新文件(`lib/rectification-agentic/v9/`) | 职责 | +| --- | --- | +| `agent-route-request.ts` | 请求闸:登录、产品开关、请求体 schema、提示词防提取、运行开关、Case↔Session 绑定、终态 | +| `agent-route-typed-message.ts` | 打字回答:报时段锁定回复、按当前 focus 的分类快路径、无 focus 时的写入分类 | +| `agent-route-structured-choice.ts` | 选择题(answer_choice / stop_and_review / skip_probe) | +| `agent-route-agent-turn.ts` | 开场 / 只读 / 打字进 agent 的 NDJSON 流、延迟回放、交付出口闸、错误码映射 | +| `agent-route-billing.ts` | 计费适配器与 Case 级请求号 | +| `agent-route-support.ts` | schema、一次性 NDJSON 回复、动作映射、上下文类型 | + +`route.ts` 只建 Supabase 客户端(安装失败映射留在这里,`api-service-unavailable` 合同照旧)并按原顺序装配各分支。原来跨分支的四个可变 `let`(`expectedWrite`、`collectIntent`、`writeClassified`、`classifierDiagnostic`)收成一个 `turnState` 对象,赋值与读取时机不变。 + +### T3 agent-run(`frontend/src/lib/rectification-agentic/v9/agent-run.ts`) + +| 新文件 | 职责 | +| --- | --- | +| `agent-run-prepare.ts` | 准备:日期可靠度、会话校验、年份重问、Skill 身份、已交付守卫、开场线索、预扣、turn 行与幂等回放 | +| `agent-run-retry.ts` | 重跑:attempt 循环 | +| `agent-run-attempt.ts` | 单次尝试:原内嵌 `streamAttempt` | +| `agent-run-finish.ts` | 收尾:结算 / 释放、终态回执、下一问、口述裁剪、finalize | +| `agent-run-support.ts` | outcome 类型、重试分类、RPC 解包、turn 级回执写入(原内嵌 `failedAttempt` / `persistCommittedPhase` / `finalizeTurn`,绑定同一 turn 后调用方式不变) | +| `agent-run-messages.ts` | `buildAgentMessages` / `buildOpeningBrief`(agent-run.ts 再导出,外部 import 不变) | + +**唯一改动的 token**:单次尝试把自己的参数 `previousErrorCode` 传给 `buildAgentMessages`,原内嵌版读的是外层 `lastAttemptError`。循环调用时传的正是 `lastAttemptError`,且在该次尝试内它不会被改写,所以值相同;原来 `previousErrorCode` 未使用的 lint warning 随之消失。 + +## 测试对比(Node 22.14.0,本机无 Docker) + +开工基线:未改动的 `origin/staging` 独立工作树跑全量 `npm test`。 + +| 时点 | tests | pass | fail | skipped | 失败名单 vs 基线 | 测试名 vs 基线 | +| --- | --- | --- | --- | --- | --- | --- | +| 基线 `12cbe6f8` | 4037 | 3984 | 25 | 28 | — | 4019 条 | +| T1 后(合同测试加入前) | 4037 | 3984 | 25 | 28 | 逐条一致 | 无缺失、无新增 | +| T2 后 | 4043 | 3990 | 25 | 28 | 逐条一致 | 无缺失,新增 6 条(增长合同) | +| T3 后(最终) | 4045 | 3992 | 25 | 28 | 逐条一致 | 无缺失,新增 8 条(增长合同) | + +25 条失败全是开工基线就有的无 Docker / 部署类用例(数据库、staging sync、Gitea 门禁等),名单三次比对逐条一致。比对命令: + +```bash +grep -E '^not ok' | sed 's/^not ok [0-9]* - //; s/ # .*//' | sort # 失败名单 +grep -E "^(not )?ok [0-9]+ - " | sed -E 's/^(not )?ok [0-9]+ - //; s/ # .*//' | sort # 测试名 +``` + +- `tsc --noEmit`:0 错(含 tests)。 +- `npm run lint`:0 error;warning 126 → 128。多出的 3 条都是 `react-hooks/exhaustive-deps`:`setPending` 依赖里没列 hook 返回的两个 setter、卸载 effect 没列 `snapshotAbort`、活动行计时 effect 没列 `setMessages`。它们来自自定义 hook 的返回值,lint 不知道是稳定引用;依赖数组按原样保留,没有为了消 warning 改依赖(CLAUDE.md「不要为迎合工具改业务代码」,且 `rectification-surface-contract` 锁着卸载 effect 的 `}, []);`)。少掉的 1 条是 agent-run 的未使用参数(见上)。另有 3 条已有 warning 的依赖名单变长(同一原因)。 +- `npm run build -- --webpack`:成功;`/`、`/chart`、`/ephemeris`、`/people` 均 `○ Static`;`rootMainFiles` gzip 130933 → 130933(0%)。构建产生的 `frontend/frontend/` 已删除,未提交。 +- Python 门禁集(`gate-pytest-args.txt`,949 条):基线工作树与本分支结果完全一致:937 passed / 11 failed / 1 skipped,失败名单逐条一致。11 条全是 `test_qizheng_*`,报「vendored 七政四余 engine is unavailable」——`vendor/stem-branch/dist/cli.cjs` 不在 `origin/staging` 的树里,工作树里也没有,属环境缺口(需要构建 vendored 包,本轮禁止装依赖)。给定的 948 / 1 基线应是在构建过该产物的检出上测的。本轮改动的三组文件没有任何 Python 测试读取(已 grep `tests/*.py` 与 `scripts/*.py`)。 + +## 切源码测试怎么改的 + +两类: + +1. **整文件断言**(`match` / `doesNotMatch` 在整份源码上):改读拼接源码 `tests/rectification-chat-surface.ts`、`tests/rectification-agent-route-surface.ts`、`tests/rectification-agent-run-surface.ts`(同 `home-surface.ts` 的做法,容器文件排第一)。断言本身一字不改,读取处统一写了一行「原值 / 新值 / 原因」。`doesNotMatch` 的覆盖面从一个文件变成整组文件,只会更严。共 56 个测试文件、100 处读取改为读拼接源码。 +2. **切片断言**(`indexOf` / `slice` / `sourceBetween` 两个锚点之间):拆分后锚点落进不同文件或切片变空的,逐条改写,每条就地写三栏。能调用的改成调用抽出的函数或真渲染抽出的块;只能按源码查的静态合同(「生产代码不含语义正则」等)按代码去处重组切片。 + +逐条改写清单: + +| 测试 | 原值 | 新值 | 类型 | +| --- | --- | --- | --- | +| `rectification-agentic-entry` · receipts / live tool progress | 切 `completedReceiptFromPersisted` 源码,匹配 tools / methods / 两个 filter、无 phases | 直接调用:旧形状与 `tool_activities` 形状各一例,只留公开工具(去重)与公开方法,phases 不进步骤 | 调用 | +| `rectification-agentic-entry` · aborting a live run | 切 `submitStructuredChoice` / `acceptCandidate`,匹配 `runAbort.current = abortController` 与 `signal` | 调用两个参数函数(假 fetch):请求的 signal 就是 `runAbort` 里的控制器,结束后清空;URL 各自正确 | 调用 | +| `chat-composer-queue` · rectification abort settles as stopped | 同上 | 同上 | 调用 | +| `rectification-agentic-entry` · time-selection cards | `messageLoop` 含 persisted 题卡 token;切 `showSelectionCards` 段匹配 `!busy \|\| keep…`、无 `offeredSelectionOnce` | 容器 map 段挂 ``,无按钮、无「先这样」 | 渲染 | +| `rectification-adopt-cards-stay` · 更像这个 不卸卡 | 切 `keepSelectionCardsWhileBusy` 段三条 token | 派生函数四种状态:空闲出卡 / busy 收卡 / 采用中留卡 / 已采用带持久 offer 留卡 | 调用 | +| `rectification-probe-replay-loss` · P3 selection cards | 切 `showSelectionCards` 段匹配 `!busy \|\| keep` 与 `caseSnapshotLoaded` | 派生函数:快照未载入不出卡、载入出卡、busy 收卡、采用中留卡 | 调用 | +| `rectification-tiebreak-card-loss` · no tie-break entry | 切 `showSelectionCards` 段不含 `interviewQuestionBlocksAdoptOffer` | 派生函数:最新回复挂着未答采集题时交付卡照出,只把采用锁上 | 调用 | +| `rectification-timeline-20260909` · timeline reads parsed inferenceMarks | 切 `timelineView` 段:用 `candidateResult?.inferenceMarks`、不碰 receipt | 派生函数:时间轴与直接用 inferenceMarks 建轴完全相同、与只用候选分钟不同;只改 receipt 里的 inference_state,时间轴不变 | 调用 | +| `rectification-post-adopt-verify` · verified_idle closing line | 从第一处 `verified_idle` 起取 400 字,无缺题文案 / 重载标签 | 真渲染缺题块:整段就是一行 `role="status"` 收尾句 | 渲染 | +| `rectification-activity-receipt` · answer deltas keep running activity | 切 delta 分支与 frames 定义,匹配状态推导、replace 语义、「正在组织回答」 | 调用 `runRectificationChatTurn`(假 NDJSON 流):只有工具时 flush 出 thinking 空行;两段 delta(第二段 replace)后只提交一次 streaming 行,正文为 replace 后全文,活动标签「正在组织回答…」未被清掉 | 调用 | +| `application-billing-contract` · free turns bypass settlement | 切 reserve / complete / release 三段(release 以 `};\n\n try {` 收尾) | 子进程里调用 `createRectificationRunBilling`(feature-pricing 引 server-only):非 message 动作三步直接成功、不碰账务库;message 三步都先查 Case 级预留 | 调用 | +| `rectification-window-cluster-cap` / `rectification-declared-window` · declared window before the model | 源码顺序:报时段拦截早于 `const selectedModel`(及分类调用) | route.ts 先 `await replyToDeclaredBirthWindow(context)` 并 return,再解析模型、再进打字预检;直接调用:报时段落一条确定性 turn、流里给锁定回复 + run.completed,普通消息返回 null、不碰持久层 | 调用 + 装配顺序 | +| `rectification-answer-choice` · structured choice is non-model | 切 `if (isStructuredChoice)` 到 `const resolvedModel` | route.ts 在该处把选择题交给 `handleRectificationStructuredChoice` 并 return;整个处理模块含 `applyRectificationChoice(accounting`、不含 agent / 计费 | 源码跟随(范围变大) | +| `rectification-unwritten-evidence` · classifier expectedWrite reaches the runner | `expectedWrite,` 与 `let expectedWrite … = "none"` | 类型、初值 `expectedWrite: "none"`、预检赋值、传给 `runV9AgentTurn` 的 `expectedWrite: turnState.expectedWrite` 四处 | 源码跟随 | +| `rectification-turn-intent-classifier`、`rectification-collect-stall`(2 条)、`rectification-discriminator-followup-consistency`、`rectification-occupation-coverage-exit`、`rectification-spoken-collect` · typed fast path | 切 route.ts `if (action === "message")` 到 `const requestTime` | `rectificationTypedMessageFastPath()`:打字处理模块全文 + route.ts 该锚点起 + agent 轮模块到 `const requestTime` 止,同一批代码;其上的子切片顺序断言不变 | 静态合同重组 | +| `rectification-range-offer-deadend` · dual-exit constant gone | 三个源之一是 route.ts | 该源换成路由拼接源码 | 源码跟随 | +| `product-domain-registry` · product gates | route.ts 含 `isProductEnabled("rectification")` | 该闸在 `agent-route-request.ts`,另加 route.ts 必须先 `await resolveRectificationAgentRequest({` | 源码跟随 | + +没有删除任何测试;没有删除任何断言而不补等价或更强的断言。被拼接源码替代的 `const route = …` 在两三个测试里改写后已无人使用,按原样删掉了这行(连同它的 `readFileSync` import),没有留空转。 + +改写后做过变异检查:把流 flush 的状态推导改成恒 thinking、把 `!busy || keepSelectionCardsWhileBusy` 改成 `!busy`、把 release 的免费早退删掉,对应的新断言都转红,改回后转绿。 + +### 仍按源码切片、没有改写的(两个锚点仍在同一个新文件里,切出的是同一段代码) + +- `application-billing-contract` 第 2 条:reserve 段(在 `agent-route-billing.ts`)与错误码映射段(在 `agent-route-agent-turn.ts`)。 +- `rectification-spoken-collect` agent 轮 `beforeRun` / `afterRun` / `alreadyDelivered` 段、`rectification-collect-stall` 的 `afterRun` 段(都在 `agent-route-agent-turn.ts`,单锚点切到末尾时多出的是计费模块,只会让 `doesNotMatch` 更严)。 +- 若干 `className="composer-wrap"` 起的切片:结束锚点原本就不存在(切到文件尾),现在切到拼接尾,只含 `doesNotMatch`,更严。 +- agent-run 的顺序切片(`rectification-delivery-ui-simplify`、`rectification-year-focus-overlay`、`rectification-probe-replay-loss` 的 settlement 段、`rectification-skipped-health-deadend`、`rectification-answer-choice` 的 `RETRYABLE_ERROR_CODES` 段):锚点都在同一个 agent-run 阶段文件里。 + +这些切片是用脚本逐条比对「拆分前单文件切出的文本」与「拆分后拼接源码切出的文本」筛出来的:内容相同(只差缩进)或只多出不相关文件的,留着;其余都改写了。 + +## 增长合同 + +新增 `frontend/tests/rectification-growth-contract.test.ts`(8 条),与首页 `home-shell-growth-contract.test.ts` 同型: + +- 聊天组件:文件 ≤ 737 + 150;组件本体 ≤ 900;useState ≤ 12、useRef ≤ 9、effect ≤ 5、useCallback ≤ 6;8 个 `lib/rectification-chat-*.ts` 参数函数模块不得调用任何 React hook。 +- 路由:文件 ≤ 152 + 150;`POST` ≤ 250;route.ts 只声明 `POST` 一个函数;`POST` 必须委托给 5 个分支处理函数。 +- agent-run:文件 ≤ 135 + 150;`runV9AgentTurn` ≤ 300、不得再有内嵌函数、准备 → 重跑 → 收尾顺序不变。 + +## 其他 + +- `frontend/DESIGN.md` 一处文件指向更正:时间轴与盘面标题行共用的 `workingRectificationTime(result)` 现在由 `rectification-chat-view.ts` 的 `deriveRectificationChatView` 调用(仍是容器每次渲染一次),只改说明位置,不改 UI。 +- 本轮没有浏览器真机走查;行为靠全量测试与原样搬移保证,两条组件级渲染测试(`rectification-dup-question-20260926.test.tsx`、`rectification-latency-20260926.test.tsx`)和三条路由级子进程测试照常通过。 +- 没有推 `origin/staging`,没有推 `main`,没有改 workflow,没有装或升级依赖。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index d6172760..2c9a24cf 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -38,7 +38,7 @@ | `TASK-rectification-fewer-probes-card-20260926.md` | [PROGRESS](PROGRESS-rectification-fewer-probes-card-20260926.md) | **减少无效追问 + 卡片区间为主**:引导补件离线新增达标 0 → 上限 6→2、定向题问完门槛未达直接出卡;卡片区间为主标题、代表分钟副标题,第一二名差距 ≥5 个百分点才显示百分比,否则写「目前区分不开」。不放宽任何置信度。先做 | 已验收(真机清单欠) | `4e6e8d87` + 验收补改 `7203d94e`(区分不开时隐藏「最可能」),deploy-staging run 2933 已部署;回放真值 ±30/±60 19→20、宽度中位 ±1 分钟、提问 11.4→7.8;Skill 10.0.30;真机清单 `docs/testing/rectification-fewer-probes-card-20260926.md` 未走 | | `TASK-rectification-telemetry-20260926.md` | [PROGRESS](PROGRESS-rectification-telemetry-20260926.md) | **匿名聚合统计**:每会话一行只存数字 / 枚举(题数分类、宽度、差距、停止原因、门槛达标、耗时、版本),管理后台只看汇总、保留 180 天;动表须 test:db。排在 fewer-probes 后 | 已验收(后台登录走查欠) | `d0bfc1fc`,门禁 run 2938 含真实 PG 测试通过、migrate 2940、deploy 2941(`e801fcf5`);只存数字/枚举、不存用户 id;后台「校正统计」页 | | `TASK-rectification-offline-research-20260926.md` | `PROGRESS-rectification-offline-research-20260926.md` | **三项离线研究**:答错 1–2 题的容错、V1/V2 分盘配权正确重跑、改正「1 分钟≈1.1 天」(实测中位 3.8 天)并核实 `_representative_pairs` 推断。不改线上。结论(`docs/research/rectification_offline_research_2026_09_26.md`):R1 答错 1 题真值在区间 98–100%、头名降三到四成,答错 2 题 ±30/±60 挤出 7–10%(两道反答=8 分=淘汰线);R2 权重生效,V1/V2 在 ±30/±60 按定义恒等、±10 六题后指标不变 → `no_benefit`(已实测);R3 3.8 天/分钟复现,45 天闸≈8–34 分钟,`_representative_pairs` 推断被推翻(全配对题数不变,卡在逐月评估),另记 BUG-1048 `investigating`(闸门跨年豁免 + zip 错位)。三项均不建议立实现单 | 已验收 | `0f5442ce`(纯研究,不改线上);三项均不立实现单:答错 2 题 ±30 真值入区间 0.93、V1/V2 no_benefit、每分钟边界位移中位 3.82 天(勘误 5 份文档);新发现 BUG-1048 出题闸门两处漏洞待产品决定 | -| `TASK-rectification-code-split-20260926.md` | — | **代码拆分(只搬不改)**:聊天组件 2043 行 / 35 useState、`POST` 926 行、`runV9AgentTurn` 1047 行,269 处切源码测试;拆分 + 增长合同 + 切片测试改调用函数。排在 fewer-probes、telemetry 之后 | 待领取 | — | +| `TASK-rectification-code-split-20260926.md` | `PROGRESS-rectification-code-split-20260926.md` | **代码拆分(只搬不改)**:聊天组件 2043 行 / 35 useState、`POST` 926 行、`runV9AgentTurn` 1047 行,269 处切源码测试;拆分 + 增长合同 + 切片测试改调用函数。排在 fewer-probes、telemetry 之后 | 待验收(D2 四项全达:组件本体 630 行 / useState 12、POST 114、runV9AgentTurn 25;全量失败名单与基线逐条一致、测试名无缺失) | `3a66c39f` / `eb773f12` / `a00c40d4`(未推送) | | `TASK-rectification-dup-question-20260926.md` | `PROGRESS-rectification-dup-question-20260926.md` | **同一轮问题出现两次(BUG-1045,复发自 BUG-585,BUG-969 拼回题干、去重只在刷新路径)+ 同一道选择题连画两张(BUG-1046:提交失败不回滚本地已答 + 兜底问题块条件过宽;H2 漏收回合)**。先于 latency 单 | 已验收(Claude 09-26 直接执行:子代理复现 A 与 B-H1(选择题提交 409 / 网络错误不回滚本地已答);Claude 变基到含 BUG-1043/1044 的 staging 后独立复验 tsc/lint 0、全量 3981 条失败名单与基线逐条一致、四路由 ○、gzip 不变) | `e4c1c7a3`(已部署 `f1d16405`,health 一致) | | `TASK-rectification-latency-20260926.md` | `PROGRESS-rectification-latency-20260926.md` | **每轮等待过长(BUG-1047)**:一轮打字回答串行 5 次开思考的模型调用,分类在开流前且无超时。产品定:收尾两步不动、分类保持思考只加 10 秒超时、发出后立即出确定性进度句并按阶段更新;补埋点;服务端无口吻优化须 A/B 逐位一致。排在 dup-question 之后 | 已验收(Claude 09-26 直接执行:子代理实现 D1–D4 与 D5 两项;Claude 用 Node 22 独立复验 tsc/lint 0、全量 4002 条失败名单与 Node 22 基线逐条一致(24 条均为 Docker/DB)、四路由 ○、gzip 不变。D5 第 1 项(探针复用)输出逐字节一致但触发冻结打分身份,Claude 建议暂不做) | `86ff9a40`(已部署 `8a409434`,health 一致) | | `TASK-rectification-session-title-result-20260922.md` | `PROGRESS-rectification-session-title-result-20260922.md` | **校正会话标题改写结果(BUG-1001)**:BUG-988 把副标题改成最后活动时间后,标题里的日期成了重复;而 BUG-929 删掉 `uniquifySessionTitle` 之后,`resolveSessionTitle` 对校正入口只返回 `生时校正 · M月D日`,**同一天多条标题完全相同**(真机截图:9/17 三条同名、9/16 两条同名、9/14 一天 5 条);旧标题的钟点后缀是创建时间、副标题是最后活动时间,同一行两个对不上的时间。**产品 09-22 拍板**:D1 标题改为承载结果——有 `accepted_time`/`confirmed_time` 写 `生时校正 · HH:MM`,否则写 `candidate_range` 的 `生时校正 · HH:MM–HH:MM`(推翻 BUG-929 的日期口径,但「不得再加墙钟去重后缀」保留);D2 存量批量重算(推翻 BUG-929「旧标题不批量改」,先例 `20260916020000_rectification_session_title_repair.sql`);D3 今日节奏保留日期(我判定的例外);D4 只在结果变化时改 title,`open` 路径不动(对 BUG-699 边界的有限扩展)。**红线**:任何写标题的路径都不得 bump `updated_at`(否则 13 条历史会话集体跳顶、毁掉 BUG-988);标题算式必须只有一处实现(一个 SQL 函数,触发器与回填共用)——BUG-987/992 都栽在「两层规则各写一份」。T4 跨层对断不得让步。已 grep 出真正受影响的只有 7 个文件,Python 侧只查裸字符串、无需改。BUG 段 1001 起 | 已验收(Claude 09-25:单一 SQL 函数、不动 updated_at、手改名不覆盖、回填幂等) | `88fe67df`/`a94a1d67`(已部署) |