docs(tasks): 换掉两条增长冻结口径 + page.tsx 状态下沉第一簇 + C1 提前
产品 2026-09-15 三项拍板,落成两份新单与两处既有单的修订: - 新增 TASK-freeze-metric-change-20260915(无 BUG 号,后面两单的前置): 两条冻结余量已用完(page.tsx 1951/1951 余 0;api server 11334/11363 余 29), 冻结从「逼新代码往外走」退化成拦路。实证:page.tsx 行数砍 59% 但 Home() 的 useState 从 56 涨到 66;api server 225 个类方法只有 12 处真碰 HTTP。 主门换成耦合指标,行数降为粗护栏;同时推翻 §6「参数式 hook 内部保持 0 个 React hook」——那正是状态搬不走的原因。 - 新增 TASK-home-state-lowering-20260915(无 BUG 号):先搬 rectification* 那 15 个 state 进已经是 dynamic 子树的校正面,Home() useState 66 → ≤53。 零行为变化;串行在 freeze-metric-change + C2 + R3 之后。 - 修订 TASK-consultation-external-evidence-cache-20260915:依赖反转,C1 排在 API server 拆解之前(它动模块级函数,拆解动类方法);补「不得新增类方法、 行数余量仅 29」的硬红线。 - 修订 TASK-api-server-decomposition-20260916:串行依赖加 C1 与 freeze-metric-change;__new__ 计数按 grep 的 4 计(原文 3 是文件数); 阶段 4 收尾口径改写;基线 11,314 → 11,334。 纯文档推送,不触发门禁、不发布镜像、不部署。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
co-authored by
Claude Opus 5
parent
58ccafb67c
commit
e4788dfc00
@@ -231,16 +231,18 @@
|
|||||||
| `TASK-rectification-title-repair-migration-20260915.md` | — | BUG-699 / 704 的数据修补写成了 Node 脚本(要 `SCHEMA_DATABASE_URL`),但 `Migrate Staging Database` 只跑 `migrator` 应用 SQL 迁移、不执行任意脚本——产品没有任何按钮能修自己那批错名字的会话。脚本里本来就是纯 SQL,搬进一次性迁移即可复用现成按钮。生产停在 `7b620c7a`(无 `use-rectification-surface.ts`),where 自然匹配 0 行,是 no-op | 待领取 | `codex/rectification-title-repair-migration-20260915` |
|
| `TASK-rectification-title-repair-migration-20260915.md` | — | BUG-699 / 704 的数据修补写成了 Node 脚本(要 `SCHEMA_DATABASE_URL`),但 `Migrate Staging Database` 只跑 `migrator` 应用 SQL 迁移、不执行任意脚本——产品没有任何按钮能修自己那批错名字的会话。脚本里本来就是纯 SQL,搬进一次性迁移即可复用现成按钮。生产停在 `7b620c7a`(无 `use-rectification-surface.ts`),where 自然匹配 0 行,是 no-op | 待领取 | `codex/rectification-title-repair-migration-20260915` |
|
||||||
| `TASK-staging-dispatch-autofill-sha-20260915.md` | `PROGRESS-staging-dispatch-autofill-sha-20260915.md` | `Migrate Staging Database` 每次都要手抄 40 位 SHA,而那个值恰恰是「最新一个过门禁的 staging 提交」——机器能自己算,查询代码那一步里就有。改成留空自动解析、填了仍走原路径(回滚用),三条安全属性一条不丢。**产品 2026-09-15 明确授权修改该 workflow,执行方不得以 AGENTS.md §2.7 拒改**;生产两个按钮保持手填,那是护栏不是麻烦 | 待验收 | `codex/staging-dispatch-autofill-sha-20260915` |
|
| `TASK-staging-dispatch-autofill-sha-20260915.md` | `PROGRESS-staging-dispatch-autofill-sha-20260915.md` | `Migrate Staging Database` 每次都要手抄 40 位 SHA,而那个值恰恰是「最新一个过门禁的 staging 提交」——机器能自己算,查询代码那一步里就有。改成留空自动解析、填了仍走原路径(回滚用),三条安全属性一条不丢。**产品 2026-09-15 明确授权修改该 workflow,执行方不得以 AGENTS.md §2.7 拒改**;生产两个按钮保持手填,那是护栏不是麻烦 | 待验收 | `codex/staging-dispatch-autofill-sha-20260915` |
|
||||||
| `TASK-staging-auto-migrate-on-deploy-20260915.md` | `PROGRESS-staging-auto-migrate-on-deploy-20260915.md` | 门禁通过后自动先跑 staging 迁移再部署,不再手点(迁移幂等、无挂起时是 no-op,`db-migrate.mjs --check` 挂起返 3 可用于日志)。今天 `deploy-staging.yml` 完全不提迁移,忘点就让新代码跑在旧 schema 上且无人拦。**产品再次授权改 workflow,范围限 `backend-quality-gate.yml` 的 dispatch 段**;迁移失败必须阻断部署;回滚不自动迁移;生产完全不动。⚠️ 同轮必须把「迁移须对已部署代码向后兼容、破坏性变更拆两轮」写进 AGENTS.md §7.6 | 待验收 | `codex/staging-auto-migrate-on-deploy-20260915` |
|
| `TASK-staging-auto-migrate-on-deploy-20260915.md` | `PROGRESS-staging-auto-migrate-on-deploy-20260915.md` | 门禁通过后自动先跑 staging 迁移再部署,不再手点(迁移幂等、无挂起时是 no-op,`db-migrate.mjs --check` 挂起返 3 可用于日志)。今天 `deploy-staging.yml` 完全不提迁移,忘点就让新代码跑在旧 schema 上且无人拦。**产品再次授权改 workflow,范围限 `backend-quality-gate.yml` 的 dispatch 段**;迁移失败必须阻断部署;回滚不自动迁移;生产完全不动。⚠️ 同轮必须把「迁移须对已部署代码向后兼容、破坏性变更拆两轮」写进 AGENTS.md §7.6 | 待验收 | `codex/staging-auto-migrate-on-deploy-20260915` |
|
||||||
| `TASK-api-server-decomposition-20260916.md` | `PROGRESS-api-server-decomposition-20260916.md` | **重构单(串行在 qizheng 单之后)**:把业务逻辑搬出 `JyotishAPIHandler`。核心不是行数,是全仓 3 处靠 `JyotishAPIHandler.__new__` 伪造空壳 handler 借方法(`consultation_workflow_service` ×2、`capture_report_blocked_repairs_golden`、`local_accuracy_report`,MCP 也走这条),依赖方向反了、handler 没有 `headers`/`wfile` 随时可炸。四阶段:拆 `__new__` 后门 → 抽 ≥150 行业务方法 → `do_POST`/`do_GET` 改路由表 → 重新冻结行数 baseline(余量 300→50)。纯搬运不改行为,`test_api_server_security.py` 3841 行断言一条不许改。预计 11,314 → 约 9,230 行。BUG 段 710+ | 待领取 | — |
|
| `TASK-api-server-decomposition-20260916.md` | `PROGRESS-api-server-decomposition-20260916.md` | **重构单(串行在 qizheng 单之后;2026-09-15 又加两条前置:C1 外网缓存单先做、freeze-metric-change 先落地)**:把业务逻辑搬出 `JyotishAPIHandler`。核心不是行数,是 3 个文件 **4 处**靠 `JyotishAPIHandler.__new__` 伪造空壳 handler 借方法(`consultation_workflow_service` ×2、`capture_report_blocked_repairs_golden`、`local_accuracy_report`,MCP 也走这条),依赖方向反了、handler 没有 `headers`/`wfile` 随时可炸——实测佐证:**225 个类方法里只有 12 处真的碰 HTTP 上下文**。四阶段:拆 `__new__` 后门 → 抽 ≥150 行业务方法 → `do_POST`/`do_GET` 改路由表 → **阶段 4 已改写**:收尾不再是「行数 baseline + 余量 300→50」(那只是把问题推到三个月后),改成主门 `__new__` 计数必须为 0 + 类方法数不得增长,行数退为粗护栏保持 300 余量。纯搬运不改行为,`test_api_server_security.py` 3841 行断言一条不许改。预计 11,334 → 约 9,230 行。BUG 段 710+ | 待领取 | — |
|
||||||
| `TASK-chart-vedastro-decouple-20260915.md` | `PROGRESS-chart-vedastro-decouple-20260915.md` | **P0**:星盘页首屏那一发 `/api/chart` 没传 `skip_vedastro_main_entry_overview`,实测冷算 0.40–0.66 秒里约 0.36 秒是 VedAstro 空转(本机连 endpoint 都没配);生产 env 开着 network + fanout,等于首屏同步等 24 个外部请求 + 3 次领域扫描,而 `chart-view-mapper.ts` / `chart-view-contract.ts` 根本不读这份证据。星历页同端点传了标志,两页策略相反。BUG-718,**复发自 BUG-161**(前台请求不得同步串联可选外部证据)。串行在 chart-page-blocking-open 之后 | 待验收 | `codex/chart-vedastro-decouple-20260915` |
|
| `TASK-chart-vedastro-decouple-20260915.md` | `PROGRESS-chart-vedastro-decouple-20260915.md` | **P0**:星盘页首屏那一发 `/api/chart` 没传 `skip_vedastro_main_entry_overview`,实测冷算 0.40–0.66 秒里约 0.36 秒是 VedAstro 空转(本机连 endpoint 都没配);生产 env 开着 network + fanout,等于首屏同步等 24 个外部请求 + 3 次领域扫描,而 `chart-view-mapper.ts` / `chart-view-contract.ts` 根本不读这份证据。星历页同端点传了标志,两页策略相反。BUG-718,**复发自 BUG-161**(前台请求不得同步串联可选外部证据)。串行在 chart-page-blocking-open 之后 | 待验收 | `codex/chart-vedastro-decouple-20260915` |
|
||||||
| `TASK-vedastro-runtime-ops-20260915.md` | `PROGRESS-vedastro-runtime-ops-20260915.md` | 运行期真相单(与上单并行,文件不重叠;**不得改 `jyotish_api_server.py`**):官方 `vedastro==1.23.25` 其实是 REST 客户端(46 KB,全打 `api.vedastro.org`),且 import 时请求 pypi 并 `pip install --upgrade` 自升级——本机实测 pin 装完一 import 就变 1.23.26,`requirements.txt` 的锁在运行期是假的(BUG-719);无 key 时免费层排队是同步 sleep + 全局锁,24 个请求 ≈ 4.8 分钟堵住前台线程(BUG-720,定级依赖生产 key 是否配置)。生产 env 核对清单在 `docs/testing/vedastro-runtime-20260915.md`,**只能由产品负责人执行**。台账 ERR-107 / ERR-108 | 待验收 | `codex/vedastro-runtime-ops-20260915` |
|
| `TASK-vedastro-runtime-ops-20260915.md` | `PROGRESS-vedastro-runtime-ops-20260915.md` | 运行期真相单(与上单并行,文件不重叠;**不得改 `jyotish_api_server.py`**):官方 `vedastro==1.23.25` 其实是 REST 客户端(46 KB,全打 `api.vedastro.org`),且 import 时请求 pypi 并 `pip install --upgrade` 自升级——本机实测 pin 装完一 import 就变 1.23.26,`requirements.txt` 的锁在运行期是假的(BUG-719);无 key 时免费层排队是同步 sleep + 全局锁,24 个请求 ≈ 4.8 分钟堵住前台线程(BUG-720,定级依赖生产 key 是否配置)。生产 env 核对清单在 `docs/testing/vedastro-runtime-20260915.md`,**只能由产品负责人执行**。台账 ERR-107 / ERR-108 | 待验收 | `codex/vedastro-runtime-ops-20260915` |
|
||||||
| `TASK-rectification-engine-memoization-20260915.md` | — | **性能单(纯 Python,独占引擎三文件,可并行)**:一次重算 45% 的 CPU 是重复算同一份 Shadbala——`build_candidate_static_context` 每个候选分钟已算过一次却只留哈希、丢掉结果,`_candidate_row` 在「候选 × 事件 × 采样日期」最内层再算 36 遍(实测 2196 次 vs 应 61 次)。过境盘只依赖事件日期却按候选算 2196 次(应 36);鉴别探针一次请求算两遍;`_cached_rows` 是死代码。本机等价实验 3358 → 1604 ms(**快 53%**),`candidate_scores` 与 `decision_receipt` 逐字相同(唯一差异是计时字段)。**只做记忆化,不改算法**;year 精度采满 12 个月**产品 2026-09-15 决定不改、研究单也不立**。BUG 段 721 | 待领取 | — |
|
| `TASK-rectification-engine-memoization-20260915.md` | — | **性能单(纯 Python,独占引擎三文件,可并行)**:一次重算 45% 的 CPU 是重复算同一份 Shadbala——`build_candidate_static_context` 每个候选分钟已算过一次却只留哈希、丢掉结果,`_candidate_row` 在「候选 × 事件 × 采样日期」最内层再算 36 遍(实测 2196 次 vs 应 61 次)。过境盘只依赖事件日期却按候选算 2196 次(应 36);鉴别探针一次请求算两遍;`_cached_rows` 是死代码。本机等价实验 3358 → 1604 ms(**快 53%**),`candidate_scores` 与 `decision_receipt` 逐字相同(唯一差异是计时字段)。**只做记忆化,不改算法**;year 精度采满 12 个月**产品 2026-09-15 决定不改、研究单也不立**。BUG 段 721 | 待领取 | — |
|
||||||
| `TASK-rectification-failure-attribution-20260915.md` | — | **三处把系统故障说成别的东西(独占 `route.ts`)**:意图分类器两次异常返回的 `null` 与用户真的「说不清」共用一条分支,回一句「我不太确定这句是不是在回答上面的问题」,**用户这句里的经历直接丢弃且不写证据**(BUG-722,采集题分支早已改对、点选题分支没跟上);引擎 429(`ERR_COMPUTE_BUSY` + `Retry-After`)被压成 `engine_request_failed`,不重试不打日志,证据记下了但范围不动、模型照说「记下了」(BUG-723,**复发自 BUG-715**);attempt 210s × 2 = 420s > 路由 `maxDuration` 240s,重试必超预算(BUG-724,**复发自 BUG-059**,BUG-388 的防复发只写了单次尝试)。超时改成整轮一个预算,不砍 attempt 也不提 240。**产品 2026-09-15 决定:意图分类继续用会话选定的贵模型,不新增「工具模型」角色** | 待领取 | — |
|
| `TASK-rectification-failure-attribution-20260915.md` | — | **三处把系统故障说成别的东西(独占 `route.ts`)**:意图分类器两次异常返回的 `null` 与用户真的「说不清」共用一条分支,回一句「我不太确定这句是不是在回答上面的问题」,**用户这句里的经历直接丢弃且不写证据**(BUG-722,采集题分支早已改对、点选题分支没跟上);引擎 429(`ERR_COMPUTE_BUSY` + `Retry-After`)被压成 `engine_request_failed`,不重试不打日志,证据记下了但范围不动、模型照说「记下了」(BUG-723,**复发自 BUG-715**);attempt 210s × 2 = 420s > 路由 `maxDuration` 240s,重试必超预算(BUG-724,**复发自 BUG-059**,BUG-388 的防复发只写了单次尝试)。超时改成整轮一个预算,不砍 attempt 也不提 240。**产品 2026-09-15 决定:意图分类继续用会话选定的贵模型,不新增「工具模型」角色** | 待领取 | — |
|
||||||
| `TASK-rectification-settled-render-split-20260915.md` | — | **前端性能单(独占校正会话组件,可并行)**:`rectification-agentic-chat.tsx` 1973 行、`useMemo` 0 个、`memo` 0 个,`messages.map` 内联在组件体里且逐条新建时间轴数组与 choice card,`ChatMessageRow` 无 memo、结算态 Markdown 走没有缓存的 `renderProse`。流式每帧(~60/s)重渲整条会话并重跑每条已结算消息的 Markdown。BUG-473 在本文件只落地了 `stream-frame-buffer`,咨询面的 `SettledMessageList` + `HistoryMessageEntry` 拆分没有跟过来。**零行为变化**;验收必须有按帧驱动的渲染计数断言(照 `home-streaming-render-split.test.ts`)。BUG 段 725 | 待领取 | — |
|
| `TASK-rectification-settled-render-split-20260915.md` | — | **前端性能单(独占校正会话组件,可并行)**:`rectification-agentic-chat.tsx` 1973 行、`useMemo` 0 个、`memo` 0 个,`messages.map` 内联在组件体里且逐条新建时间轴数组与 choice card,`ChatMessageRow` 无 memo、结算态 Markdown 走没有缓存的 `renderProse`。流式每帧(~60/s)重渲整条会话并重跑每条已结算消息的 Markdown。BUG-473 在本文件只落地了 `stream-frame-buffer`,咨询面的 `SettledMessageList` + `HistoryMessageEntry` 拆分没有跟过来。**零行为变化**;验收必须有按帧驱动的渲染计数断言(照 `home-streaming-render-split.test.ts`)。BUG 段 725 | 待领取 | — |
|
||||||
| `TASK-rectification-request-dossier-cache-20260915.md` | — | **低风险单,串行在 failure-attribution 之后(同改 `route.ts`)**:一轮 Agent 对话实测取 3.44 次整份 Case 档案(点选题 2.07 次),全仓约 40 个调用点、请求内零缓存;档案是「最近 50 轮 turns + 全部 evidence + 合成收据」的大 jsonb。做法是包装 `accounting` 客户端做**写即失效**的请求作用域缓存(两个只读投影命中缓存,其余任何 RPC 先清空再转发),**零调用点改动**。不得做成「请求内只读一次」——档案在请求内会变。BUG 段 726 | 待领取 | — |
|
| `TASK-rectification-request-dossier-cache-20260915.md` | — | **低风险单,串行在 failure-attribution 之后(同改 `route.ts`)**:一轮 Agent 对话实测取 3.44 次整份 Case 档案(点选题 2.07 次),全仓约 40 个调用点、请求内零缓存;档案是「最近 50 轮 turns + 全部 evidence + 合成收据」的大 jsonb。做法是包装 `accounting` 客户端做**写即失效**的请求作用域缓存(两个只读投影命中缓存,其余任何 RPC 先清空再转发),**零调用点改动**。不得做成「请求内只读一次」——档案在请求内会变。BUG 段 726 | 待领取 | — |
|
||||||
| `TASK-consultation-external-evidence-cache-20260915.md` | — | **普通聊天性能单(Python,串行在 api-server-decomposition 之后)**:每轮每域同步等外网,cProfile 前三名全是 `api.vedastro.org` 的 HTTPS 往返(0.801 + 0.786 + 0.206 s),本地 swisseph 只有 0.022 s。三个护栏数字凑不齐:前台等 1.5 s、后台跑 8 s、线程池只有 2 个 worker,且超时**不 cancel** → 每 4 秒一轮就长期饱和,之后每轮白等再拿 `official_blocked`(BUG-727)。另 `western_evidence_packet` 122 KB 前端零读取点(BUG-728)。**产品定案**:按「出生数据+岁差+交点+UTC 日期」缓存(与引擎 `_official_snapshot_reference_date` 同键,否决自定 TTL),同日 0 等待 / 跨日先用旧的(≤7 天)后台刷新 / `daily_starlanguage` 要求当天 / 冷启动才走 1.5 s。**不许「干脆不调」——那会重开 BUG-301。** 另含 staging 单域耗时实测单(代码注释里的 21 s 与本机 0.5 s 差 40 倍,三域上限就是从它推的)。BUG 段 727–728 | 待领取 | — |
|
| `TASK-consultation-external-evidence-cache-20260915.md` | — | **普通聊天性能单(Python;2026-09-15 产品拍板改为**排在 api-server-decomposition **之前**)**:每轮每域同步等外网,cProfile 前三名全是 `api.vedastro.org` 的 HTTPS 往返(0.801 + 0.786 + 0.206 s),本地 swisseph 只有 0.022 s。三个护栏数字凑不齐:前台等 1.5 s、后台跑 8 s、线程池只有 2 个 worker,且超时**不 cancel** → 每 4 秒一轮就长期饱和,之后每轮白等再拿 `official_blocked`(BUG-727)。另 `western_evidence_packet` 122 KB 前端零读取点(BUG-728)。**产品定案**:按「出生数据+岁差+交点+UTC 日期」缓存(与引擎 `_official_snapshot_reference_date` 同键,否决自定 TTL),同日 0 等待 / 跨日先用旧的(≤7 天)后台刷新 / `daily_starlanguage` 要求当天 / 冷启动才走 1.5 s。**不许「干脆不调」——那会重开 BUG-301。** 另含 staging 单域耗时实测单(代码注释里的 21 s 与本机 0.5 s 差 40 倍,三域上限就是从它推的)。BUG 段 727–728 | 待领取 | — |
|
||||||
| `TASK-consultation-context-memory-20260915.md` | — | **记忆三缺口(TS,可并行)**:历史超预算时从最老整轮丢弃,`droppedCount` 算了却**全仓零读取点**,模型不知道少看了几轮——单条截断有「省略 N 字」标记,整轮丢弃没有(BUG-729,BUG-555 防复发只写了「头部截断」所以漏网);写摘要阈值写死 16,000,历史预算却是 `clamp((窗口−60k)×1.5, 4k, 40k)`,窗口 < **70,667** 时预算低于阈值 → 每轮静默丢(BUG-730,后台上架中等窗口模型即触发);写满时服务端存着摘要,`continueInNewChat` 只带问题不带摘要,而 `context_summary` 根本不在任何会话接口的列里(BUG-731)。**产品定案:静默继承**,且摘要文本永远不许由客户端提供(`chatSessionCreateSchema` 只收来源会话 uuid)。BUG 段 729–731 | 待领取 | — |
|
| `TASK-consultation-context-memory-20260915.md` | — | **记忆三缺口(TS,可并行)**:历史超预算时从最老整轮丢弃,`droppedCount` 算了却**全仓零读取点**,模型不知道少看了几轮——单条截断有「省略 N 字」标记,整轮丢弃没有(BUG-729,BUG-555 防复发只写了「头部截断」所以漏网);写摘要阈值写死 16,000,历史预算却是 `clamp((窗口−60k)×1.5, 4k, 40k)`,窗口 < **70,667** 时预算低于阈值 → 每轮静默丢(BUG-730,后台上架中等窗口模型即触发);写满时服务端存着摘要,`continueInNewChat` 只带问题不带摘要,而 `context_summary` 根本不在任何会话接口的列里(BUG-731)。**产品定案:静默继承**,且摘要文本永远不许由客户端提供(`chatSessionCreateSchema` 只收来源会话 uuid)。BUG 段 729–731 | 待领取 | — |
|
||||||
| `TASK-consultation-session-capacity-20260915.md` | — | **对话上限单(一份迁移,可并行;不碰 route.ts)**:`append_consultation_question` 的 200,000 字符额度里,`thinkingText`(≤4,000) + `thinkingSections`(实测 1,521/2,243/2,977) 占一半以上,而 `techniqueTruth`/`workflowReceipt`/`agentExecutionReceipt` 照样入库却不计入——同一条上限身兼二职且两职都没做好,约 **19 轮** 就「已写满」(200 条那档永远碰不到)。**产品定案:思考文本不计入**,额度只数用户读得到的正文(约 19 → 约 50 轮),另设一条按 `length(elem::text)` 把全部字段算全的物理上限(算式取 1,000,000,写进迁移注释)护住数据库行;两档都返回同一个 `session_full`。保留 advisory lock / 幂等 / 满员拒绝(BUG-464 防复发)。BUG 段 732 | 待领取 | — |
|
| `TASK-consultation-session-capacity-20260915.md` | — | **对话上限单(一份迁移,可并行;不碰 route.ts)**:`append_consultation_question` 的 200,000 字符额度里,`thinkingText`(≤4,000) + `thinkingSections`(实测 1,521/2,243/2,977) 占一半以上,而 `techniqueTruth`/`workflowReceipt`/`agentExecutionReceipt` 照样入库却不计入——同一条上限身兼二职且两职都没做好,约 **19 轮** 就「已写满」(200 条那档永远碰不到)。**产品定案:思考文本不计入**,额度只数用户读得到的正文(约 19 → 约 50 轮),另设一条按 `length(elem::text)` 把全部字段算全的物理上限(算式取 1,000,000,写进迁移注释)护住数据库行;两档都返回同一个 `session_full`。保留 advisory lock / 幂等 / 满员拒绝(BUG-464 防复发)。BUG 段 732 | 待领取 | — |
|
||||||
|
| `TASK-freeze-metric-change-20260915.md` | — | **规则单(后面两单的前置,无 BUG 号)**:两条增长冻结余量都用完(`page.tsx` 1,951/1,951 余 **0**;`jyotish_api_server.py` 11,334/11,363 余 **29**),冻结从「逼新代码往外走」退化成「拦路」。实证:`page.tsx` 行数砍 59% 但 `Home()` 的 `useState` 从 56 涨到 **66**(拆的是代码不是状态);api server **225 个类方法只有 12 处真碰 HTTP 上下文**,4 处 `__new__` 伪造空壳就是这么来的。**产品拍板换口径**:主门改成「`Home()` 的 useState/useRef 不得增长」与「类方法数 + `__new__` 计数不得增长」,行数降级为粗护栏;**同时推翻 §6「参数式 hook 内部保持 0 个 React hook」**(那正是状态搬不走的原因)。改 `AGENTS.md` §6 + 两个合同测试,不碰业务代码 | 待领取 | — |
|
||||||
|
| `TASK-home-state-lowering-20260915.md` | — | **page.tsx 状态下沉第一簇(串行在 freeze-metric-change + C2 + R3 之后)**:66 个 state 里 `rectification*` 占 **15** 个,而它们服务的 `<ConversationalBirthTimeRectification>` 本来就是 `dynamic()` 懒加载子树、挂着 24 个 props;`useRectificationSurface` 要解构约 56 个参数。把这簇搬进子树,`Home()` 的 useState 从 66 降到 ≤ 53。**零行为变化**;第一步必须先把 15 个逐个分类(只服务子树 / 外壳也要读)。产品否决了 Context Provider 与外部 store 两条路。不占 BUG 号 | 待领取 | — |
|
||||||
|
|
||||||
## 命名与归档
|
## 命名与归档
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
- 代码基线:`origin/staging` = **`2d7698ea`**(文档树 `6f74aa67`)。开工时以最新 `origin/staging` 为准。
|
- 代码基线:`origin/staging` = **`2d7698ea`**(文档树 `6f74aa67`)。开工时以最新 `origin/staging` 为准。
|
||||||
- **串行依赖**:本单必须等 `TASK-qizheng-native-chart-20260915` **合入 staging 之后**才开工。那一单独占 `scripts/jyotish_api_server.py` 的写权,并行等于自找冲突。
|
- **串行依赖**:本单必须等 `TASK-qizheng-native-chart-20260915` **合入 staging 之后**才开工。那一单独占 `scripts/jyotish_api_server.py` 的写权,并行等于自找冲突。
|
||||||
|
- **2026-09-15 新增串行依赖**:还要等 `TASK-consultation-external-evidence-cache-20260915`(C1)合入。产品拍板 C1 先做——它动的是模块级函数(`execute_consultation_workflow`、`_join_foreground_vedastro`、三个前台 VedAstro 常量),本单动的是 `JyotishAPIHandler` **类**,重叠不大;C1 的用户价值(每轮省掉一次外网等待)高于一次纯搬运。**本单要吸收 C1 的 diff**:开工时 `execute_consultation_workflow` 里会多出走缓存模块的分支,照常搬运即可,不得把它改回去。
|
||||||
|
- **2026-09-15 新增前置**:`TASK-freeze-metric-change-20260915` 决定了阶段 4 的收尾口径(见下方阶段 4 的改写),本单收尾前它必须已合入。
|
||||||
- 本单不改任何行为,不新增功能,不动前端。
|
- 本单不改任何行为,不新增功能,不动前端。
|
||||||
|
|
||||||
## 为什么要做:不是因为文件长,是因为它成了事实上的服务层
|
## 为什么要做:不是因为文件长,是因为它成了事实上的服务层
|
||||||
|
|
||||||
`scripts/jyotish_api_server.py` 现在 **11,314 行**,契约上限 11,363(`tests/test_api_server_growth_contract.py`:baseline 11,063 + 300 bugfix 余量)——**只剩 49 行**。
|
`scripts/jyotish_api_server.py` 现在 **11,334 行**(立单时 11,314,2026-09-15 实测已涨到 11,334),契约上限 11,363(`tests/test_api_server_growth_contract.py`:baseline 11,063 + 300 bugfix 余量)——**只剩 29 行**。
|
||||||
|
|
||||||
但真正的问题不是行数,是这个:
|
但真正的问题不是行数,是这个:
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ def build_runtime_evidence_helpers(chart):
|
|||||||
|
|
||||||
那个声称是「shared consultation workflow boundary for API and MCP callers」的模块,**反过来依赖单体文件**,并且靠 `__new__` 绕过 `BaseHTTPRequestHandler.__init__` 造一个空壳 handler,只为借用它身上的方法。
|
那个声称是「shared consultation workflow boundary for API and MCP callers」的模块,**反过来依赖单体文件**,并且靠 `__new__` 绕过 `BaseHTTPRequestHandler.__init__` 造一个空壳 handler,只为借用它身上的方法。
|
||||||
|
|
||||||
全仓一共 **3 处**这样做:
|
全仓 **3 个文件、4 处调用**这样做(下表第一行含两处;收尾断言按 `grep -c` 的 **4** 计,不要按文件数的 3 计):
|
||||||
|
|
||||||
| 位置 | 用途 |
|
| 位置 | 用途 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -141,17 +143,24 @@ def build_runtime_evidence_helpers(chart):
|
|||||||
- 404 行为、方法不允许、限流 429、异常映射全部不变。
|
- 404 行为、方法不允许、限流 429、异常映射全部不变。
|
||||||
- `do_POST` + `do_GET` 合计 ≤ 90 行。
|
- `do_POST` + `do_GET` 合计 ≤ 90 行。
|
||||||
|
|
||||||
### 阶段 4 · 重新冻结行数契约(必做,收尾)
|
### 阶段 4 · 收尾重新冻结(必做,收尾)—— 2026-09-15 改写
|
||||||
|
|
||||||
- 把 `tests/test_api_server_growth_contract.py` 的 `JYOTISH_API_SERVER_LINE_COUNT_BASELINE` 更新为**本单收尾时的实际行数**。
|
**原方案(行数 baseline 重设 + 余量 300→50)已作废。** 产品 2026-09-15 拍板换口径:余量收到 50 行只是把今天的问题推到三个月后,下一次 bugfix 又会立刻撞墙。新口径由 `TASK-freeze-metric-change-20260915` 落地,本单收尾时按它已经立好的尺子填新基线:
|
||||||
- 余量从 **300 改为 50**。
|
|
||||||
- 同步更新该文件顶部 docstring 里的日期与说明,以及 `AGENTS.md` §6 里引用的口径。
|
| 门 | 收尾时要达到 |
|
||||||
- **不得**在本单之外的任何轮次里调高 baseline。
|
| --- | --- |
|
||||||
|
| 主门 · `JyotishAPIHandler.__new__` 全仓计数 | **必须为 0**(今天 4)——这是「拆干净了」唯一不可伪造的证据 |
|
||||||
|
| 主门 · `JyotishAPIHandler` 类方法数 | 显著低于 225,并把新值写成不得增长的新基线 |
|
||||||
|
| 粗护栏 · 文件行数 | baseline 重设为收尾实际行数,余量保持 **300**(不再收到 50) |
|
||||||
|
|
||||||
|
- 同步更新 `tests/test_api_server_growth_contract.py` 顶部 docstring 的日期与说明。
|
||||||
|
- **不得**在本单之外的任何轮次里调高任何一条基线。
|
||||||
|
|
||||||
**验收标准**
|
**验收标准**
|
||||||
|
|
||||||
- `tests/test_api_server_growth_contract.py` 通过,且新 cap = 新实际行数 + 50。
|
- `grep -rn "JyotishAPIHandler.__new__" --include=*.py .`(排除 `skills/*/versions/**`)**零命中**,且该断言已经写进合同测试。
|
||||||
- `AGENTS.md` §6 的措辞与新口径一致。
|
- 类方法数新基线已写入合同测试,且人为加一个类方法能让它变红(贴反向验证)。
|
||||||
|
- `AGENTS.md` §6 的措辞与新口径一致(措辞由 freeze-metric-change 单先行落地,本单只填数)。
|
||||||
|
|
||||||
## 预期收益(按 AST 实测推算,允许 ±10%)
|
## 预期收益(按 AST 实测推算,允许 ±10%)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
- 基线 commit:`origin/staging` @ `6b3248bf`
|
- 基线 commit:`origin/staging` @ `6b3248bf`
|
||||||
- 执行分支:`codex/consultation-external-evidence-cache-20260915`
|
- 执行分支:`codex/consultation-external-evidence-cache-20260915`
|
||||||
- 主要落点:`scripts/jyotish_api_server.py`、`scripts/vedastro_service_adapter.py`、`scripts/vedastro_user_entrypoint.py`
|
- 主要落点:`scripts/jyotish_api_server.py`、`scripts/vedastro_service_adapter.py`、`scripts/vedastro_user_entrypoint.py`
|
||||||
- **串行在 `TASK-api-server-decomposition-20260916` 之后**:那一单独占 `scripts/jyotish_api_server.py`,本单必须以它合入后的 staging 为基线
|
- **依赖已于 2026-09-15 反转:本单排在 API server 拆解单之前。** 原文写的是「串行在 `TASK-api-server-decomposition-20260916` 之后」,产品拍板改为**本单先做**——理由见 §3.7,拆解单已同步改为串行在本单之后
|
||||||
- 与 context-memory 单、session-capacity 单无文件重叠,可并行
|
- 与 context-memory 单、session-capacity 单无文件重叠,可并行
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -69,6 +69,7 @@ cProfile 按 `tottime` 排前五名:
|
|||||||
4. **后台任务必须可取消、线程池必须可排队。** 现在「超时不 cancel + 只有 2 个 worker」的组合是本单必须解决的部分,不是顺带。
|
4. **后台任务必须可取消、线程池必须可排队。** 现在「超时不 cancel + 只有 2 个 worker」的组合是本单必须解决的部分,不是顺带。
|
||||||
5. **`western_evidence_packet` 按需返回,不删计算。** 西洋盘本身仍是 must-use 层之一(被读的是别的字段),本单只是不再把这个整包塞进每一轮的响应。
|
5. **`western_evidence_packet` 按需返回,不删计算。** 西洋盘本身仍是 must-use 层之一(被读的是别的字段),本单只是不再把这个整包塞进每一轮的响应。
|
||||||
6. **不动并发闸门(默认 2)、不动 `AGENT_TIMEOUT_MS`、不动 `maxDuration`。**
|
6. **不动并发闸门(默认 2)、不动 `AGENT_TIMEOUT_MS`、不动 `maxDuration`。**
|
||||||
|
7. **本单排在 API server 拆解之前**(2026-09-15 产品拍板,推翻本任务书首版的排序)。依据:本单要改的 `execute_consultation_workflow`、`_join_foreground_vedastro`、那三个常量**全是模块级函数**,而拆解单的核心是把业务逻辑搬出 `JyotishAPIHandler` **类**,两边动的是文件的不同部分;且本单的用户价值(每一轮省掉一次外网等待)远高于一次纯搬运。代价是拆解单将来要吸收本单的 diff,已在那一单里写明。
|
||||||
|
|
||||||
## 4. 硬红线
|
## 4. 硬红线
|
||||||
|
|
||||||
@@ -76,7 +77,8 @@ cProfile 按 `tottime` 排前五名:
|
|||||||
2. 赶不上外网时,不得把未交付的官方层标成 `executed`(BUG-301 防复发)。用旧缓存时,证据里必须能看出它是哪一天的。
|
2. 赶不上外网时,不得把未交付的官方层标成 `executed`(BUG-301 防复发)。用旧缓存时,证据里必须能看出它是哪一天的。
|
||||||
3. 缓存内容里不得落盘姓名、邮箱、用户 ID;出生资料派生值只能以哈希进键,不得明文写进缓存文件名(AGENTS §8)。
|
3. 缓存内容里不得落盘姓名、邮箱、用户 ID;出生资料派生值只能以哈希进键,不得明文写进缓存文件名(AGENTS §8)。
|
||||||
4. 前台不得再同步串联 overview + snapshot + range scan(BUG-301 防复发,现状已满足,不得回退)。
|
4. 前台不得再同步串联 overview + snapshot + range scan(BUG-301 防复发,现状已满足,不得回退)。
|
||||||
5. `scripts/jyotish_api_server.py` 不得增长(AGENTS §6):新逻辑进 `scripts/` 下的独立模块,主文件只做薄注册。本单又恰好排在拆分单之后,更不能把行数吃回去。
|
5. `scripts/jyotish_api_server.py` 不得增长(AGENTS §6):新逻辑进 `scripts/` 下的独立模块,主文件只做薄注册。**具体到数字**:开工时实测 11,334 行、上限 11,363,**余量只有 29 行**;`JyotishAPIHandler` 有 225 个方法,**本单不得新增任何类方法**。
|
||||||
|
若 `TASK-freeze-metric-change-20260915` 已经合入,主门改为「类方法数不得增长」,行数粗护栏放宽——**以开工当时生效的合同测试为准**,但「不新增类方法、新逻辑进独立模块」这条无论口径怎么换都成立。
|
||||||
6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
|
6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
|
||||||
|
|
||||||
## 5. 任务分解
|
## 5. 任务分解
|
||||||
@@ -137,8 +139,8 @@ cProfile 按 `tottime` 排前五名:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git fetch origin --prune
|
git fetch origin --prune
|
||||||
# 先确认 api-server-decomposition 单已合入 staging
|
# 本单不再等 api-server-decomposition;直接以 origin/staging 为基线
|
||||||
git log --oneline origin/staging | head -10
|
git log --oneline origin/staging | head -5
|
||||||
git worktree add -b codex/consultation-external-evidence-cache-20260915 \
|
git worktree add -b codex/consultation-external-evidence-cache-20260915 \
|
||||||
.worktrees/consultation-external-evidence-cache-20260915 origin/staging
|
.worktrees/consultation-external-evidence-cache-20260915 origin/staging
|
||||||
cd .worktrees/consultation-external-evidence-cache-20260915
|
cd .worktrees/consultation-external-evidence-cache-20260915
|
||||||
@@ -164,6 +166,7 @@ cd frontend && npx tsx --test tests/consultation-*.test.ts
|
|||||||
|
|
||||||
## 9. 不在本单范围
|
## 9. 不在本单范围
|
||||||
|
|
||||||
|
- `JyotishAPIHandler` 的拆解本身(本单只做薄注册,拆解排在本单之后)
|
||||||
- 三域上限 3 → N 的放宽(5.4 只负责量数据,放宽是产品决策)
|
- 三域上限 3 → N 的放宽(5.4 只负责量数据,放宽是产品决策)
|
||||||
- 并发闸门、`AGENT_TIMEOUT_MS`、`maxDuration`
|
- 并发闸门、`AGENT_TIMEOUT_MS`、`maxDuration`
|
||||||
- `reference_date` 缺省用 UTC 当天、而用户的「今天」是 UTC+8,早上八点前后会错开一天——**记为观察项,本单不修**
|
- `reference_date` 缺省用 UTC 当天、而用户的「今天」是 UTC+8,早上八点前后会错开一天——**记为观察项,本单不修**
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
# TASK · 把两条增长冻结从「数行数」换成「数耦合」
|
||||||
|
|
||||||
|
- 日期:2026-09-15
|
||||||
|
- 基线 commit:`origin/staging` @ `6b3248bf`
|
||||||
|
- 执行分支:`codex/freeze-metric-change-20260915`
|
||||||
|
- 落点:`AGENTS.md` §6、`tests/test_api_server_growth_contract.py`、新建 `frontend/tests/home-shell-growth-contract.test.ts`、`frontend/tests/chart-view-route.test.ts`(挪走一条断言)
|
||||||
|
- **这一单是后面两单的前置**:`TASK-home-state-lowering-20260915` 与 API server 拆解都需要新口径先生效,否则它们做的事会被旧门禁判红
|
||||||
|
- 与七条在飞分支无文件重叠(它们都不碰这两个合同测试,也都声明「`page.tsx` 一行不许动」)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 为什么要换
|
||||||
|
|
||||||
|
两条冻结的余量都用完了:
|
||||||
|
|
||||||
|
| 文件 | 当前 | 上限 | 余量 |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| `frontend/src/app/page.tsx` | 1,951 行 | 1,951 | **0** |
|
||||||
|
| `scripts/jyotish_api_server.py` | 11,334 行 | 11,363 | **29** |
|
||||||
|
|
||||||
|
冻结当初的用意是逼新代码往 hooks / lib / 独立模块里走,第一阶段确实起了作用(`page.tsx` 从 4,766 砍到 1,951)。但顶到线之后它的作用反转:**任何一轮正常改动只要需要在这两个文件里加几行接线就会被打红**,这一轮于是被迫去做一件与本次目标无关的搬运。上一轮的 `TASK-rectification-p0-fix-20260915` 就是这么来的——BUG-705 的十来行接线让 1,951 变 1,964,逻辑本身早就在 lib 里了,超的只是接线。
|
||||||
|
|
||||||
|
而行数从来不是维护成本的来源。两个文件各有一个能直接表达耦合的数字:
|
||||||
|
|
||||||
|
**`page.tsx`**
|
||||||
|
|
||||||
|
| 指标 | BUG-249 当时 | 今天 |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| 文件行数 | 4,766 | 1,951 |
|
||||||
|
| `useState` | 56 | **66** |
|
||||||
|
| `useEffect` | 18 | 22 |
|
||||||
|
| `useRef` | — | 41 |
|
||||||
|
| `useCallback` / `useMemo` | 0 / 0 | 0 / 0 |
|
||||||
|
|
||||||
|
**行数砍掉 59%,状态反而从 56 涨到 66。** 抽出去的 hook 是参数式的(§6 现有那句「参数式 hook 内部保持 0 个 React hook」),`useSessionManagement(params)` 开头要解构约 40 个参数——代码搬走了,状态所有权一个都没搬。
|
||||||
|
|
||||||
|
**`scripts/jyotish_api_server.py`**
|
||||||
|
|
||||||
|
| 项 | 数量 |
|
||||||
|
| --- | ---: |
|
||||||
|
| `JyotishAPIHandler` 方法 | **225**(8,219 行,占全文件 72%) |
|
||||||
|
| 模块级函数 | 102(2,981 行) |
|
||||||
|
| `do_POST` / `do_GET` 路径分支 | 78(`do_POST` 单个方法 263 行) |
|
||||||
|
| `self.headers` / `self.wfile` / `self.rfile` / `self.path` 全文件出现次数 | **12** |
|
||||||
|
| `JyotishAPIHandler.__new__` 伪造点 | **4** |
|
||||||
|
|
||||||
|
**225 个方法,只有 12 处真的碰到 HTTP 上下文。** 其余是披着 `self` 外衣的纯函数,这正是那 4 处 `__new__` 后门的成因。
|
||||||
|
|
||||||
|
## 2. 根因
|
||||||
|
|
||||||
|
冻结盯的是「文件有多大」,而维护成本来自「谁依赖谁」。行数是耦合的影子:影子被按住了,本体照长不误——`page.tsx` 的状态数、api server 的类方法数在冻结期内都是**增长**的。
|
||||||
|
|
||||||
|
## 3. 决策记录
|
||||||
|
|
||||||
|
产品 2026-09-15 拍板:**两条冻结的口径都要换**,并明确以下三点。
|
||||||
|
|
||||||
|
1. **主门换成耦合指标,行数降级为粗护栏。** 不是取消行数限制,而是把它 rebaseline 到有余量的位置,让它只拦住「整块新功能塞进来」这种明显情况;真正的门是下面两组数。
|
||||||
|
2. **推翻 `AGENTS.md` §6 现有的「参数式 hook 内部保持 0 个 React hook 的既定模式」。** 这条正是状态搬不走的原因:它要求抽出去的 hook 不持有 React 状态,于是状态只能留在 `Home()`。产品明确授权改掉它——**新口径下,抽出去的 hook 与子组件应当持有自己的状态**。执行方不得以「AGENTS 有这条」为由拒改;本节就是那条红线的推翻记录。
|
||||||
|
3. **`__new__` 计数这一轮只要求「不得增长」,不要求为 0。** 现在是 4 处,收到 0 是 API server 拆解单的验收标准,不是本单的。本单只负责把尺子立起来。
|
||||||
|
|
||||||
|
## 4. 硬红线
|
||||||
|
|
||||||
|
1. **本单不改任何业务代码。** 只改 `AGENTS.md` §6、两个合同测试,以及把一条断言从它现在寄居的文件挪到专用文件。`page.tsx` 与 `jyotish_api_server.py` 一行不许动。
|
||||||
|
2. **不得放宽既有的其它冻结条款。** §6 第三条(不得再手写第二个聊天输入框 / 第二套滚动跟随 / 第二套加载动画)原样保留。
|
||||||
|
3. 行数 rebaseline 的新基线必须取**开工当时的实测值**,并在测试注释里写明取值日期与 `wc -l` 的结果,不得抄本任务书里的数字(七条在飞分支合并后这些数会变)。
|
||||||
|
4. 新的耦合指标基线同理:`useState` / `useRef` / 类方法数 / `__new__` 计数都以开工当时实测为准,只许降不许升。
|
||||||
|
5. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
|
||||||
|
|
||||||
|
## 5. 任务分解
|
||||||
|
|
||||||
|
### 5.1 `page.tsx`:新建专用合同测试
|
||||||
|
|
||||||
|
现在这条断言寄居在 `frontend/tests/chart-view-route.test.ts` 的 `page.tsx does not grow to host the chart page` 里(`assert.ok((pageSource.match(/\n/g) ?? []).length <= 1951)`)——它和星盘页没有关系,只是当时顺手放在那儿。新建 `frontend/tests/home-shell-growth-contract.test.ts`,把增长约束集中过去:
|
||||||
|
|
||||||
|
| 门 | 断言 | 今天的值 |
|
||||||
|
| --- | --- | ---: |
|
||||||
|
| 主门 | `Home()` 里的 `useState` 数不得增长 | 66 |
|
||||||
|
| 主门 | `Home()` 里的 `useRef` 数不得增长(防止把 state 改写成 ref 绕过上面那条) | 41 |
|
||||||
|
| 粗护栏 | 文件行数 ≤ 实测基线 + 150 | 1,951 |
|
||||||
|
|
||||||
|
`chart-view-route.test.ts` 里保留与星盘页真正相关的那半条(`assert.doesNotMatch(pageSource, /chart-page|ChartPageView|\/api\/chart-view/)`),行数断言删除并注明搬到了哪里。
|
||||||
|
|
||||||
|
- 验收:新测试在当前代码上绿;人为在 `page.tsx` 加一个 `useState` 后必须红(执行方在进度记录里贴出这次反向验证,证明尺子会动,不是恒为真)。
|
||||||
|
- 验收:计数方式要能区分 `useState(` 与 `useState<Type>(`(今天 66 这个数就是按 `\buseState[<(]` 数出来的;只按 `useState(` 数会漏掉一半)。
|
||||||
|
- 验收:`npx tsx --test tests/chart-view-route.test.ts` 仍绿,且该文件不再包含行数断言。
|
||||||
|
|
||||||
|
### 5.2 `jyotish_api_server.py`:改 `tests/test_api_server_growth_contract.py`
|
||||||
|
|
||||||
|
现在是 `JYOTISH_API_SERVER_LINE_COUNT_BASELINE = 11063` + 300。改成:
|
||||||
|
|
||||||
|
| 门 | 断言 | 今天的值 |
|
||||||
|
| --- | --- | ---: |
|
||||||
|
| 主门 | `JyotishAPIHandler` 的方法数不得增长 | 225 |
|
||||||
|
| 主门 | 全仓 `JyotishAPIHandler.__new__` 出现次数不得增长 | 4 |
|
||||||
|
| 粗护栏 | 文件行数 ≤ 实测基线 + 300 | 11,334 |
|
||||||
|
|
||||||
|
方法数用缩进匹配(`^ (?:async )?def \w+`)即可,和本任务书 §1 那张表同一种数法。`__new__` 计数扫 `scripts/` 与 `tests/`,把命中文件列进断言失败信息,方便下次一眼看到是谁又开了后门。
|
||||||
|
|
||||||
|
- 验收:新断言在当前代码上绿;人为加一个类方法后必须红(同样贴反向验证)。
|
||||||
|
- 验收:该文件现有的另外三条断言(`AGENTS.md` 里必须出现 `must not grow` / `thinly registered` / 该测试必须在 `CORE_PYTEST_TARGETS` 与快速门里)一条不改仍绿。
|
||||||
|
- 验收:`.venv/bin/python -m pytest tests/test_api_server_growth_contract.py` 通过。
|
||||||
|
|
||||||
|
### 5.3 改 `AGENTS.md` §6
|
||||||
|
|
||||||
|
两条改写,逐字说明新口径:
|
||||||
|
|
||||||
|
- 第一条(api server):把「冻结时行数 + 300 行 bugfix 余量」换成「**类方法数不得增长、`__new__` 伪造点不得增长**,行数是粗护栏」;保留「新端点进独立模块、主文件只做薄注册」这句话——新口径正是在奖励它。
|
||||||
|
- 第二条(`page.tsx`):把「不得再增长」换成「**`Home()` 的 `useState` / `useRef` 数不得增长**」;**删掉「参数式 hook 内部保持 0 个 React hook 的既定模式」**,改成「抽出去的 hook 与子组件应当持有自己的状态;`page.tsx` 只做装配」。
|
||||||
|
|
||||||
|
同时在 §6 里点明这次换口径的理由一句话(行数是耦合的影子),免得下一轮有人以为是放水。
|
||||||
|
|
||||||
|
- 验收:`tests/test_api_server_growth_contract.py` 里那条「`AGENTS.md` 必须包含 must not grow / thinly registered」的断言仍绿(措辞改写时不要把这两个短语弄没了)。
|
||||||
|
- 验收:`frontend/AGENTS.md` 若有重复表述,同轮对齐。
|
||||||
|
|
||||||
|
### 5.4 记录
|
||||||
|
|
||||||
|
本单不产生 Bug 记录(改的是规则,不是缺陷),也不进 `CHANGELOG.md`(无用户可感知变化)。进度记录里必须写清楚:四组基线的实测值、取值日期、以及两次反向验证的结果。
|
||||||
|
|
||||||
|
## 6. 让步顺序
|
||||||
|
|
||||||
|
1. 5.2(api server 口径)最先做——它直接决定 C1 那一单还要不要为 29 行余量拧巴。
|
||||||
|
2. 5.1 次之。
|
||||||
|
3. 5.3 必须和前两条同轮(规则和门禁不许分家,否则下一轮有人按旧 AGENTS 拒改)。
|
||||||
|
4. 5.4 不得砍。
|
||||||
|
|
||||||
|
## 7. 开工前置命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch origin --prune
|
||||||
|
git worktree add -b codex/freeze-metric-change-20260915 \
|
||||||
|
.worktrees/freeze-metric-change-20260915 origin/staging
|
||||||
|
cd .worktrees/freeze-metric-change-20260915
|
||||||
|
git status -sb | head -1
|
||||||
|
# 取当时实测基线,不要抄任务书里的数
|
||||||
|
wc -l scripts/jyotish_api_server.py frontend/src/app/page.tsx
|
||||||
|
grep -cE '^ (async )?def ' scripts/jyotish_api_server.py
|
||||||
|
grep -rc 'JyotishAPIHandler.__new__' scripts/ tests/ | grep -v ':0'
|
||||||
|
grep -cE '\buseState[<(]' frontend/src/app/page.tsx
|
||||||
|
grep -cE '\buseRef[<(]' frontend/src/app/page.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
验收命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python -m pytest tests/test_api_server_growth_contract.py
|
||||||
|
.venv/bin/python scripts/run_quality_gate.py --profile quick
|
||||||
|
cd frontend && npx tsx --test tests/home-shell-growth-contract.test.ts tests/chart-view-route.test.ts
|
||||||
|
npx tsx --test tests/*.test.ts # 与基线逐条比对失败清单
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. BUG 编号起点
|
||||||
|
|
||||||
|
本单不占 BUG 号。基线 `6b3248bf` 上最大号 **BUG-720**,721–732 已被两轮审计七单预占。
|
||||||
|
|
||||||
|
## 9. 不在本单范围
|
||||||
|
|
||||||
|
- 真的去搬状态或搬方法(见 `TASK-home-state-lowering-20260915.md` 与 API server 拆解单)
|
||||||
|
- `__new__` 收到 0(拆解单的验收标准)
|
||||||
|
- §6 第三条的三个「不得再手写第二套」条款
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
# TASK · 把校正面的 15 个状态从首页搬下去(page.tsx 状态下沉 · 第一簇)
|
||||||
|
|
||||||
|
- 日期:2026-09-15
|
||||||
|
- 基线 commit:`origin/staging` @ `6b3248bf`(开工时以最新 `origin/staging` 为准,见 §7)
|
||||||
|
- 执行分支:`codex/home-state-lowering-20260915`
|
||||||
|
- 落点:`frontend/src/app/page.tsx`、`frontend/src/hooks/use-rectification-surface.ts`、`frontend/src/components/conversational-birth-time-rectification.tsx`(或新建的容器组件)
|
||||||
|
- **串行依赖(三条,缺一不可)**:
|
||||||
|
1. `TASK-freeze-metric-change-20260915` —— 本单要让抽出去的 hook 持有 React 状态,那正是现行 `AGENTS.md §6` 禁止的;必须等新口径生效
|
||||||
|
2. `TASK-consultation-context-memory-20260915`(C2)—— 它在改 `use-session-management.ts` / `use-consultation-run.ts`,与本单同一片状态层
|
||||||
|
3. `TASK-rectification-settled-render-split-20260915`(R3)—— 它在改 `rectification-agentic-chat.tsx`,与本单同一片子树
|
||||||
|
- 规模:一簇状态换个住处。**零行为变化、零文案变化。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 为什么是这一簇
|
||||||
|
|
||||||
|
`page.tsx` 现在 1,951 行,行数上限 1,951,**余量 0**。但行数不是病根:
|
||||||
|
|
||||||
|
| 指标 | BUG-249 当时 | 今天 |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| 文件行数 | 4,766 | 1,951 |
|
||||||
|
| `Home()` 的 `useState` | 56 | **66** |
|
||||||
|
| `useEffect` | 18 | 22 |
|
||||||
|
| `useRef` | — | 41 |
|
||||||
|
| `useCallback` / `useMemo` | 0 / 0 | 0 / 0 |
|
||||||
|
|
||||||
|
**行数砍掉 59%,状态反而从 56 涨到 66。** 前几轮拆的是代码不是状态:抽出去的 hook 是参数式的,`useSessionManagement(params)` 开头要解构约 40 个参数,`useRectificationSurface(params)` 约 56 个——状态所有权一个都没搬,每搬一次还要新增一批传参。
|
||||||
|
|
||||||
|
66 个 state 按归属分群:
|
||||||
|
|
||||||
|
| 群 | 个数 |
|
||||||
|
| --- | ---: |
|
||||||
|
| **`rectification*`** | **15** |
|
||||||
|
| `session*` | 10 |
|
||||||
|
| `profile*` | 6 |
|
||||||
|
| `synastry*` | 4 |
|
||||||
|
| `account*` | 4 |
|
||||||
|
| 其余分散 | 27 |
|
||||||
|
|
||||||
|
最大的一簇服务的是一个**已经是 `dynamic()` 懒加载的子树**:`<ConversationalBirthTimeRectification>` 挂着 **24 个 props**,而喂它们的 15 个 state 全住在 `Home()` 里。边界最清楚、收益最大,所以第一簇搬它。
|
||||||
|
|
||||||
|
那 15 个是:
|
||||||
|
|
||||||
|
```
|
||||||
|
rectificationSessionId rectificationShouldStartOpening
|
||||||
|
rectificationCaseId rectificationTurns
|
||||||
|
rectificationHeaderSlot rectificationSnapshot
|
||||||
|
rectificationPendingQuestion rectificationOpeningSessionId
|
||||||
|
rectificationLoading rectificationEntrySummary
|
||||||
|
rectificationMutationPending rectificationEntrySummarySettled
|
||||||
|
rectificationError
|
||||||
|
rectificationErrorSessionId
|
||||||
|
rectificationReadonly
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 根因
|
||||||
|
|
||||||
|
`AGENTS.md §6` 现行那句「参数式 hook 内部保持 0 个 React hook 的既定模式」把状态钉死在 `Home()` 里;同一节的行数冻结又不许 `page.tsx` 增长。两条合起来等于「不许再加状态」,而每一轮新功能都要加。`TASK-freeze-metric-change-20260915` 已经拿到产品授权推翻前半句,本单是第一个吃到新口径的轮次。
|
||||||
|
|
||||||
|
## 3. 决策记录
|
||||||
|
|
||||||
|
产品 2026-09-15 拍板:
|
||||||
|
|
||||||
|
1. **`page.tsx` 走「状态下沉」这条路**,不引外部 store、不铺全局 Context Provider。理由:不引新依赖、可以一簇一簇增量做,每搬一簇 `page.tsx` 就真降一截。
|
||||||
|
2. **第一簇搬校正面的 15 个。** 后续 `session*` / `profile*` / `synastry*` 各自另开单,本单不碰。
|
||||||
|
3. **抽出去的 hook 与子组件从此持有自己的状态**(推翻 §6 旧红线,措辞由 freeze-metric-change 单落地)。执行方不得以「AGENTS 说参数式 hook 里不能有 React hook」为由拒改。
|
||||||
|
4. **零行为变化。** 本单不修任何已知交互缺陷,发现了写进进度记录。
|
||||||
|
|
||||||
|
## 4. 硬红线
|
||||||
|
|
||||||
|
1. **不是 15 个都能搬。** 有几个外壳自己要读(例如 `rectificationSessionId` / `rectificationCaseId` 参与决定显示哪个界面、侧栏会话列表也要知道)。**第一步必须逐个分类**:「只服务子树 → 搬下去」「外壳也要读 → 留在外壳,但收敛成一个对象,不再是散装的多个 `useState`」。分类表写进进度记录,不许含糊。
|
||||||
|
2. **零行为变化**是唯一成败判据:进入校正面、退出、刷新、从首页卡片打开、打开历史校正、开场自动触发、只读态、报错态、换模型、采用后回首页——逐条与改前一致。
|
||||||
|
3. `next build` 后 `/` 仍须 `○ Static`;首屏 JS gzip 变化在 ±2 % 内(上次实测 584,413 B)。
|
||||||
|
4. 测试总数不得低于开工时 `origin/staging` 的实测;改任何既有断言必须写「原值 / 新值 / 原因」三栏(AGENTS §7.3)。
|
||||||
|
5. 不得新写第二个聊天输入框、第二套滚动跟随、第二套加载动画(§6 第三条原样有效)。
|
||||||
|
6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
|
||||||
|
7. **不得改数据库、不得改任何 API 路由。** 本单只动前端状态的住处。
|
||||||
|
|
||||||
|
## 5. 任务分解
|
||||||
|
|
||||||
|
### 5.1 先分类,再动手
|
||||||
|
|
||||||
|
把 15 个逐个归类并写进进度记录:
|
||||||
|
|
||||||
|
| 类别 | 处置 |
|
||||||
|
| --- | --- |
|
||||||
|
| 只服务校正子树 | 搬进子树(`useRectificationSurface` 变成真 hook,或由容器组件持有) |
|
||||||
|
| 外壳也要读 | 留在外壳,但合并成**一个**状态对象,散装 `useState` 数下降 |
|
||||||
|
|
||||||
|
- 验收:分类表在进度记录里,15 个一个不漏,每个写明依据(谁在读它)。
|
||||||
|
|
||||||
|
### 5.2 `useRectificationSurface` 变成真 hook
|
||||||
|
|
||||||
|
现在它要解构约 56 个参数。改成自己 `useState` / `useEffect` 持有第一类状态,对外只暴露子树真正需要的接口;`page.tsx` 侧的传参随之消失。
|
||||||
|
|
||||||
|
- 验收:`useRectificationSurface` 的参数个数显著下降,新值写进进度记录(改前约 56)。
|
||||||
|
- 验收:`<ConversationalBirthTimeRectification>` 的 props 个数下降,新值写进进度记录(改前 24)。
|
||||||
|
|
||||||
|
### 5.3 `Home()` 的状态计数必须真降
|
||||||
|
|
||||||
|
- 验收:`Home()` 的 `useState` 数从 **66** 降到 **≤ 53**(搬走至少 13 个;允许留 2 个在外壳,多留必须逐个说明理由)。
|
||||||
|
- 验收:`useRef` 数不得上升(改前 41)——不许把 state 改写成 ref 来凑数字。
|
||||||
|
- 验收:`frontend/tests/home-shell-growth-contract.test.ts`(由 freeze-metric-change 单建立)在本单收尾时更新为新基线,并贴一次反向验证。
|
||||||
|
|
||||||
|
### 5.4 行为等价证明
|
||||||
|
|
||||||
|
- 验收:改动前后各跑一次全量前端套件,失败清单逐条一致(无 Docker 时数据库套件照常红)。
|
||||||
|
- 验收:`npx tsx --test tests/rectification-*.test.ts` 全绿,断言零改动——如果必须改,按 §4.4 写三栏说明。
|
||||||
|
- 验收:`docs/testing/` 下留一份真人走查清单,覆盖 §4.2 那十条路径(本仓没有浏览器与登录态,这一项只能人工)。
|
||||||
|
|
||||||
|
### 5.5 记录
|
||||||
|
|
||||||
|
本单不产生 Bug 记录(不是缺陷,是结构改造),不进 `CHANGELOG.md`(无用户可感知变化)。若过程中确实改了任何可见样式,必须同提交更新 `frontend/DESIGN.md`(AGENTS §7.5)。
|
||||||
|
|
||||||
|
## 6. 让步顺序
|
||||||
|
|
||||||
|
1. 5.1(分类)**不得砍**——没有分类表就动手,一定会把外壳要读的状态搬下去然后再搬回来。
|
||||||
|
2. 5.2 + 5.3 是主体。
|
||||||
|
3. 5.3 的目标值可以让步(比如只搬到 ≤ 56),但让步幅度和原因必须写进进度记录,**不得静默降低**。
|
||||||
|
4. 5.4 不得砍。
|
||||||
|
5. 5.5 不得砍。
|
||||||
|
|
||||||
|
## 7. 开工前置命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch origin --prune
|
||||||
|
# 三条串行依赖必须都已合入 staging,逐个确认
|
||||||
|
git log --oneline origin/staging | head -20
|
||||||
|
git worktree add -b codex/home-state-lowering-20260915 \
|
||||||
|
.worktrees/home-state-lowering-20260915 origin/staging
|
||||||
|
cd .worktrees/home-state-lowering-20260915/frontend
|
||||||
|
git status -sb | head -1
|
||||||
|
npm ci
|
||||||
|
# 取当时实测基线,不要抄本任务书里的数字(七条在飞分支合并后会变)
|
||||||
|
grep -cE '\buseState[<(]' src/app/page.tsx
|
||||||
|
grep -cE '\buseRef[<(]' src/app/page.tsx
|
||||||
|
grep -oE 'const \[rectification[A-Za-z]+' src/app/page.tsx | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
验收命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./node_modules/.bin/tsc --noEmit
|
||||||
|
npm run lint # 0 error
|
||||||
|
npx tsx --test tests/rectification-*.test.ts tests/home-shell-growth-contract.test.ts
|
||||||
|
npx tsx --test tests/*.test.ts # 与基线逐条比对失败清单
|
||||||
|
npm run build # `/` 仍须 ○ Static,首屏 gzip ±2%
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. BUG 编号起点
|
||||||
|
|
||||||
|
本单不占 BUG 号。基线 `6b3248bf` 上最大号 **BUG-720**,721–732 已被两轮审计七单预占。
|
||||||
|
|
||||||
|
## 9. 不在本单范围
|
||||||
|
|
||||||
|
- `session*` / `profile*` / `synastry*` / `account*` 四簇(各自另开单)
|
||||||
|
- Context Provider 或外部 store(§3.1 已否决这一轮走这两条路)
|
||||||
|
- 校正面自身的任何交互缺陷(本单零行为变化)
|
||||||
|
- API server 拆解(另一条线)
|
||||||
Reference in New Issue
Block a user