Files
Jyotisha/docs/tasks/TASK-freeze-metric-change-20260915.md
T
Jesse_ChenandClaude Opus 5 e4788dfc00 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
2026-09-15 23:15:37 +00:00

9.9 KiB
Raw Blame History

TASK · 把两条增长冻结从「数行数」换成「数耦合」

  • 日期:2026-09-15
  • 基线 commitorigin/staging @ 6b3248bf
  • 执行分支:codex/freeze-metric-change-20260915
  • 落点:AGENTS.md §6、tests/test_api_server_growth_contract.py、新建 frontend/tests/home-shell-growth-contract.test.tsfrontend/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 方法 2258,219 行,占全文件 72%
模块级函数 1022,981 行)
do_POST / do_GET 路径分支 78do_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.tsxjyotish_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.tspage.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.2api server 口径)最先做——它直接决定 C1 那一单还要不要为 29 行余量拧巴。
  2. 5.1 次之。
  3. 5.3 必须和前两条同轮(规则和门禁不许分家,否则下一轮有人按旧 AGENTS 拒改)。
  4. 5.4 不得砍。

7. 开工前置命令

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

验收命令:

.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 第三条的三个「不得再手写第二套」条款