docs(tasks): rectification code split progress, changelog, index row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a00c40d4b8
commit
c1592c21ad
@@ -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' <log> | sed 's/^not ok [0-9]* - //; s/ # .*//' | sort # 失败名单
|
||||
grep -E "^(not )?ok [0-9]+ - " <log> | 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 段挂 `<RectificationQuestionGapNotices` 并传 `choiceNonce`;真渲染:persisted 选择题出 `is-embedded` 卡、4 个选项;派生函数:busy 收卡、采用中留卡、无「出过一次」记忆 | 渲染 + 调用 |
|
||||
| `rectification-surface-contract` · question gap live row | `{questionGap === "preparing"` 在 map 之后、saved 行之前 | 容器里缺题块组件在同两个锚点之间;真渲染 preparing 态出 live 行与准备文案 | 渲染 |
|
||||
| `rectification-spoken-collect` · choice options in the same message | 容器 map 段含 persisted 题卡与 `onSelect={submitChoice}` / `onStop={submitStop}` | 容器传 `submitChoice` / `submitStop` 给缺题块;缺题块里的题卡绑同样两个回调 | 源码跟随 |
|
||||
| `rectification-exhaustion-exit` · collect spoken stop | 切 `RectificationReadonlyRange` 源码:有 `role="status"`、无 onStop / button | 真渲染:整段输出是一行 `role="status"` 的 `<p>`,无按钮、无「先这样」 | 渲染 |
|
||||
| `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,没有装或升级依赖。
|
||||
Reference in New Issue
Block a user