diff --git a/PROGRESS.md b/PROGRESS.md index c309ea2..5d5968f 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -8,7 +8,42 @@ --- -## 当前阶段:T6 · 创作工具箱(通用生成器框架)— P1 框架地基 +## 当前阶段:QA-CR · 全项目 Code Review 整改批 + +> **背景**:[2026-06-26] 对全仓做了一次**七维度 Code Review**(后端 Python 规范 / LangGraph 最佳实践 / 后端 FastAPI 架构 / 前端编程规范 / 前端架构 / 应用安全 / 依赖时效),6 个专科 reviewer 并行 + 依赖自查。绿门禁下仍揪出 **2 CRITICAL + 13 HIGH + 一批 MEDIUM/LOW**——再次证伪「测试过=没问题」。架构本身成熟、不变量纪律强(provider 中立 / HITL accept 单写 / checkpoint 仅控制流 / SecretStr 全覆盖);**确认无虞**:SSRF(`base_url` 仅来自硬编码 `_PROVIDER_BASE_URLS`,无用户 URL 入网)/ SQL 注入(全 ORM 参数化)/ XSS(无 `dangerouslySetInnerHTML`/`eval`)/ 错误信封不泄栈密钥 / Kimi 用 RFC 8628 device flow 无 CSRF 面。 +> **整改 DoD**:CRITICAL + HIGH 全部 ✅ 且各自补回归测试;MEDIUM 择批清理;门禁全绿(后端 ruff/format/mypy/pytest + alembic 无漂移;前端 lint/tsc/vitest/build)。**守 §Definition of Done**:每项 TDD 先红后绿、完成即小步 commit。 +> **落地顺序**:① 本周 CR-C1/C2 + 两资源泄漏(H1/H2)+ Fernet key(H8);② 合并前 计费两项(H3)+ request_id(H4)+ max_length(H9)+ 前端三 HIGH(H10/H11/H12);③ MEDIUM/LOW 技术债批。 +> **依赖纪律**:CR-C1(升 Next)须 `pnpm install` 重锁 + `pnpm build` 验证、**升级后轮换全部应用密钥**(与 CR-H8 Fernet key 联动)。CR-H4/H9 改 schema → 若动 OpenAPI 形参须 `pnpm gen:api` + `memory/contracts.md` 记一笔。无 DB 迁移预期。 + +| 任务 | 状态 | 负责 | 依赖 | 备注 | +|---|---|---|---|---| +| CR-C1 🔴 升级 Next.js 修 RCE(CVE-2025-66478)+ React 补丁 + 轮换密钥 | ⬜ | @frontend | — | `apps/web/package.json` next **15.1.3→15.1.9**(或 15.5.7/16.x),react/react-dom 19.0.0→补丁版(上游 CVE-2025-55182);App Router + RSC 应用正受影响。`pnpm install` 重锁 + `pnpm build` 验;**升级后轮换全部应用密钥**(联动 CR-H8)。Next15 LTS 2026-10-21 EOL,规划 16.x 迁移 | +| CR-C2 🔴 多章链设 `recursion_limit` | ⬜ | @backend | — | `services/chain_runner.py:210` 默认 25 supersteps、每章 4 节点→超 ~6 章即 `GraphRecursionError` 整 job 失败,「批量产一卷(K章)」在目标规模不可用。`config` 加 `recursion_limit=(last-start+1)*4+10`;补超默认上限的 mock-gateway 回归测试 | +| CR-H1 🟠 Kimi OAuth 轮询不占 DB 会话 | ⬜ | @backend | — | `services/job_runner.py:75`+`routers/kimi_oauth.py:85` 轮询全程(最坏 >200s)占连接池连接、饿死其他请求;轮询循环无需 DB,仅最终 `upsert_oauth_credential` 开短会话 | +| CR-H2 🟠 修 `httpx.AsyncClient` 泄漏 | ⬜ | @backend | — | `routers/kimi_oauth.py:73` `Depends(_default_http_client)` 非 generator 依赖、永不 `aclose()`;改 `async def _http_client(): async with httpx.AsyncClient(...) as http: yield http` | +| CR-H3 🟠 Anthropic 结构化输出记真实 usage | ⬜ | @llm | — | `adapters/anthropic.py:161` 硬编码 `usage=0`→`usage_ledger` 全记 0 成本、腐蚀计费(违 ARCH §4.8);用 `create_with_completion` 取原始响应 usage | +| CR-H4 🟠 `request_id` 贯通网关日志 | ⬜ | @llm | — | `gateway.py:183` 所有 `llm_call`/`llm_provider_failed` 无 request_id;`LlmRequest` 加 `request_id` 字段并从 API 层透传(违 ARCH §9.3 端到端追踪不变量) | +| CR-H5 🟠 `thinking` 字段:实现或删除 | ⬜ | @llm | — | `types.py:41` 声明但所有 adapter 忽略,`thinking=True` 静默无效、无报错;按 YAGNI 删除或实现转发 + 测试 | +| CR-H6 🟠 流式路径加 per-provider 重试 | ⬜ | @llm | — | `gateway.py:268` 流式仅 1 次尝试,首 token 前一次瞬时 429 烧光 fallback 链(与非流式 `_complete_with_retry` 不对称);用 `_retrying()` 包裹首 token 前尝试或显式文档化 | +| CR-H7 🟠 `TOOLBOX` 注册表不可变 | ⬜ | @backend | — | `packages/skills/ww_skills/toolbox_registry.py:70` 裸 dict 可变;仿同库 `SPECS` 用 `Final[Mapping]=MappingProxyType(...)`(违不可变不变量) | +| CR-H8 🟠 `.env.example` 移除可用 Fernet key | ⬜ | @devops | — | 已确认提交真实可用 key(注释「可直接用于本地」),换非功能占位符 + 突出轮换提示;联动 CR-C1 轮换 | +| CR-H9 🟠 API 文本输入加 `max_length` | ⬜ | @backend | — | `schemas/{providers,generation,projects}.py` `api_key`/`brief`/`final_text`/`segment` 仅 `min_length=1`(DoS/成本失控);加上限(api_key≤512、brief≤1e4、final_text≤2e5)+ ASGI 请求体大小限 | +| CR-H10 🟠 前端 `errorCode` 去重 | ⬜ | @frontend | — | `lib/style/{useRefine,useStyleLearn}.ts` 各自重复定义,统一 import 自 `lib/generation/cards.ts:132`(DRY 漂移隐患) | +| CR-H11 🟠 Drawer 关闭还原焦点 | ⬜ | @frontend | — | `components/Drawer.tsx:41` 关闭后焦点掉到 body(违 WCAG 2.4.3);传 `triggerRef`、关闭时 `.focus()` | +| CR-H12 🟠 RefineView 取消在途 refine | ⬜ | @frontend | — | `components/style/RefineView.tsx:33` 无 AbortController,段切换时旧响应可覆盖新响应;`useRefine` 暴露 abort 在 effect cleanup 调用,去 blanket exhaustive-deps disable | +| CR-M1 🟡 后端架构整改批 | ⬜ | @backend | — | `outline.py:162` volume 下推 DB 过滤;`generation.py:326`/`toolbox.py:431` N+1 INSERT→`bulk_create`;`jobs.py:28` 裸 ORM→repo;`provider_deps.py:91` 去私有 `_client`(加 `probe_connection()`);列表端点加分页。⚠️`assemble.py:155` 8 串行查询:**AsyncSession 不支持同一 session 并发,`gather` 须每查询独立 session**,不可照搬 | +| CR-M2 🟡 网关/adapters 整改批 | ⬜ | @llm | — | `gateway.py:90` `is_open()` 副作用违 CQS;`pricing.py` 未知 model 静默记 0 → 加 warning;adapter `str(exc)` 入异常消息可能带 vendor 响应体→只留类名+status | +| CR-M3 🟡 LangGraph 链健壮性 | ⬜ | @llm | — | `chain_runner.py:124` accept commit 后 `run_overdue_scan` 非幂等且失败无法 resume→部分进度空洞(幂等化或纳入事务);`chain_runner.py:159` 改用公开 `get_state().next` 替私有 `__interrupt__`;`chain/nodes.py:213` 链内 4 审串行→并行/复用 review 子图 | +| CR-M4 🟡 前端 key/a11y/cleanup 批 | ⬜ | @frontend | — | 可编辑列表去 index-key(`CharacterGenerator.tsx:135`/`GeneratorRunner.tsx:178`/`ForeshadowCard.tsx:68`);SSE `KNOWN_EVENTS` 补 `stop`(核对 `memory/contracts.md`);ARIA 补全(AppShell nav label / NavDrawer aria-controls / ForeshadowCard `
` 结构);ProjectWizard 步骤焦点;`ReviewReport.tsx:301` setTimeout 卸载清理 | +| CR-M5 🟡 前端 TS 边界类型收紧 | ⬜ | @frontend | — | `lib/api/server.ts:34` `parseJsonBody` 加最小结构校验;`useAccept.ts:60` `ApiErrorEnvelope` 从生成 schema 派生;`applyFix.ts` `hasApplicableFix` 改类型谓词去 `as string` | +| CR-L1 🟢 LOW 清理批 | ⬜ | @backend·@llm·@frontend | — | `chain.py:256` 静默回退加 warning;SSE 端点补 OpenAPI `text/event-stream` 声明;冗余 `as`/`.valueOf()` cast 清理;测试脚本 `print`→logging | +| CR-D1 🟡 抬高后端依赖下界(可选升级评估) | ⬜ | @devops | — | `pyproject.toml` 下界过旧(`langgraph>=0.2.40`/`anthropic>=0.34`),抬到接近 lockfile 实锁版本保可复现;后端实锁版本均当前。可选评估 vitest 3.x / tailwind 4.x | + +> **维度小结**:后端 FastAPI 架构 0C/2H/7M·Python 规范 0C/4H/6M·LangGraph 1C/2H/5M·前端 React 0C/2H/9M·前端 TS 0C/1H/8L·安全 0C/2H/4M·依赖 1C。后端依赖全部当前大版本线;前端唯一关键缺口=Next RCE。 + +--- + +## 已封板:T6 · 创作工具箱(通用生成器框架)✅ > **背景**:对标竞品「创作工具箱」——把每个「XX 生成器」实现为声明式 skill(`AgentSpec` 的设计本意),用一条**通用执行路径**驱动。用户已定:① 走**通用 skill 框架**(非逐个硬编码)② 封面/图像本期跳过 ③ 文本生成器全做(上架已有 3 + 新建 8)。完整设计见 `~/.claude/plans/creation-toolbox-generators.md`。 > **P1 目标(DoD)**:`GeneratorTool` 描述符 + `TOOLBOX` 注册表 + 通用 `GET /skills/toolbox` & `POST /projects/{id}/skills/{tool_key}/generate` 端点 + **1 个最简生成器(脑洞)端到端打通**(mock 网关 E2E 零 token)。证实「加生成器 = 加一份声明」这条路。 @@ -126,6 +161,7 @@ T0.1 monorepo 骨架 ✅ @devops · T0.2 16 MVP 表迁移(无漂移,users st > 格式:`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件` +- [2026-06-26] @orchestrator — **全项目 Code Review(七维度)完成 → `QA-CR` 整改批立项**。6 个专科 reviewer 并行(后端 FastAPI 架构 / Python 规范 / LangGraph 最佳实践 / 前端 React 架构 / 前端 TS 类型安全 / 应用安全)+ 依赖时效自查;212 py + 168 tsx 全覆盖。绿门禁下仍揪出 **2 CRITICAL + 13 HIGH + 一批 MEDIUM/LOW**:**C1** Next.js 15.1.3 RCE(CVE-2025-66478「React2Shell」,App Router+RSC 受影响,升 15.1.9/15.5.7/16.x + 轮换密钥);**C2** 多章链缺 `recursion_limit`(默认 25 supersteps、每章 4 节点→超 ~6 章即崩,批量产卷不可用)。HIGH 含 Kimi OAuth 占 DB 会话 / httpx 泄漏 / Anthropic 计费记 0 / request_id 未贯通 / `.env.example` 提交可用 Fernet key / 文本输入无 `max_length` / 前端 errorCode 重复 / Drawer 焦点 / RefineView 未取消。**确认无虞**:SSRF(base_url 仅硬编码)/SQL 注入(全 ORM)/XSS(无 dangerouslySetInnerHTML)/错误信封不泄密/Kimi device-flow 无 CSRF。已登记 `QA-CR` 任务表(CR-C1/C2 + CR-H1..H12 + CR-M1..M5 + CR-L1 + CR-D1)按 owner 分派 @frontend/@backend/@llm/@devops,落地顺序见当前阶段 background。**本批为评审立项,尚无代码改动。** - [2026-06-23] @orchestrator — **🎉 多章工作流链(Chain Workflow)交付 + 多 agent 交叉评审 + CRITICAL 修复 + 合并 develop(merge `513bf71`)**。对标星月「一键多章」+ 差异化「每章过四审、遇冲突才停人」。设计契约 `docs/design/chain-workflow.md`(commit `5ee9799`)。**多 agent 逐学科建造**(契约先行、目录单写者):**C1 @llm**(`7f3eaab`) LangGraph **cyclic 链图** `orchestrator/chain/{state,nodes,graph}.py`(`build_chain_graph`:write→review→decide→accept 循环 + **interrupt-on-conflict** + resume;删死代码 `build_write_graph`,`write_node` 本体留用;7 单测 mock 网关+fake session+MemorySaver)·**C2 @backend**(`29349dc`) 服务/3 端点/schema/checkpointer 接线(见下条)·**C3 @db**(`548b7ab`) langgraph 检查点 4 表迁移 `d3e4f5a6b7c8`(`autocommit_block` 避开 `CREATE INDEX CONCURRENTLY` 与 alembic 事务死锁;`env.py` `include_object` 豁免这 4 表防漂移误报)·**C4 @qa**(`061792d`) E2E `tests/test_chain_workflow_e2e.py` 真 pg + mock 网关零 token(无冲突两章全自动 + 冲突→interrupt→awaiting→resume→accept)。**3 路对抗交叉评审**(python/fastapi/不变量)在**绿门禁下仍揪出 1 CRITICAL + 7 HIGH**——证伪"测试过=没问题":CRITICAL=链把**绑请求 session 的 gateway** 传进 BackgroundTask,后台跑时 session 已关→write/review 的 `usage_ledger.record()` 写进死 session **静默丢失**(链计费全丢,违 #1;E2E 清了 ledger 却没断言其存在故漏掉)。**修复**(`d1ea83b`):网关改 `chain_gateway_builder` 按节点 session 重建(仿 `digest_gateway_builder`/`style.py`);日志脱敏(`_classify_job_error`+`exc_type`,去 `str(exc)`,§5);resume 原子 claim `JobRepo.claim_awaiting_to_running`(防并发双跑同 thread_id,非 awaiting→409)+ 所有权校验(`JobView.project_id`,跨项目→404);`ChainResumeAccepted`(去 start/count=0 哨兵);删死导入 `extract_conflicts`;**补回归断言**(链 `usage_ledger` 必有行 + `chapter_reviews` 每章一行)。**出口(DoD)✅**:后端门禁绿 ruff/format 干净 · mypy **194 Success** · alembic **无漂移**(业务表零迁移,复用 `jobs.status=awaiting_input` + `result.awaiting_chapter`;唯一新 DDL=langgraph 检查点表)· pytest **608 passed**(+6 回归测)。首次真正启用 LangGraph checkpointer/interrupt。守不变量 #1/#3/#4/#5/#9。**剩余下一步(未做)**:① 前端链 UI(`pnpm gen:api` 纳入 run/resume + 发起页/进度轮询/awaiting 裁决续跑面板,复用 `ConflictAdjudication`);② §12 文档回写 `ARCHITECTURE.md §5.2`(补 cyclic 链图 + 更正现状:写章直连流式/单章 accept 为事务/链图首启 checkpointer);③ 未 push 远端。可选:续写/扩写节点入链;UGC 链市场需多租户(deferred)。 - [2026-06-23] @backend — **✅ Chain Workflow C2(多章链 服务+端点+schema+checkpointer 接线,分支 `feat/chain-workflow`)**。承 C1(@llm)链图(`build_chain_graph` §3.3),落地服务/端点壳:① **3 端点**`routers/chain.py`(`POST .../chains/{key}/run`→202 `ChainRunAccepted`;`POST .../chains/runs/{job_id}/resume`→202;`GET /jobs/{id}` 复用)。校验:count 1..50→422、未知 chain_key→404、项目不存在→404、resume job 不存在→404、resume 非 awaiting→**409 新码 `CONFLICT`**、无凭据→503。② **schema**`schemas/chain.py`(`ChainRunRequest/ChainRunAccepted/ChainResumeRequest`,`ConflictDecision` 复用)。③ **服务**`services/chain_runner.py`(`run_chain_job` 仿 `run_job` 壳自建独立 session 驱动链图:set_running→ainvoke→据 `__interrupt__` 置 `awaiting_input` 或 `done`/`failed`;`build_accept_op` 在 apps/api 装配验收事务闭包注入图节点,守 #3/#4;token 不入 result/日志)+ `services/chain_deps.py`(`get_checkpointer_factory`:运行时 `AsyncPostgresSaver` 上下文 / 测试 MemorySaver)。④ **零迁移**(§7):复用 `jobs`,新增 `status="awaiting_input"` + `JobRepo.set_awaiting`,awaiting 章经 `result.awaiting_chapter`;新错误码 `ErrorCode.CONFLICT`(409)。⑤ **新网关缝**`project_deps.build_chain_gateway`/`get_chain_gateway`(按请求 tier writer/analyst/light 分派——单档网关恒返该档会错路由 review/digest,见 gotcha)+ `get_digest_gateway_builder`。**单测**`apps/api/tests/test_chain.py` 12 用例(mock 网关 + MemorySaver + fake session/accept_op,无 DB/无网络/无真 LLM):run→202、resume→202、未知 key 404、count 越界(>50/0) 422、resume 非 awaiting 409、项目/ job 不存在 404、run_chain_job 无冲突→done+written 满、冲突→awaiting→resume→done、错误脱敏、accept_op 冲突缺判→CONFLICT_UNRESOLVED。**全仓门禁绿**:ruff/format 干净、mypy **193 Success**、alembic **无漂移**(无 ORM 变更)、pytest **600 passed**。守不变量 #1/#3/#4/#5/#9。记 contracts C-Chain 稳定 + 2 decision + 3 gotcha。**唯一新增 DDL(langgraph 检查点表)= C3 迁移;前端链 UI = 契约稳定后 @frontend follow-up(本期非目标)。** - [2026-06-22] @orchestrator — **🎉 T6 创作工具箱(通用生成器框架)全工具箱交付 + 独立复核(分支 `feat/t6-creation-toolbox`)**。多 agent 并行波次:Wave 0(@llm T6.5 ‖ @backend 描述符类型 ‖ P2 收尾)→ Wave A(@backend T6.2+T6.3 TOOLBOX+端点合龙,契约稳定点)→ Wave B(@frontend T6.6 ‖ @qa T6.4)→ Wave C 收口,目录单写者零写冲突、契约先行、零返工。**@llm T6.5**:`ww_agents` +7 输出 schema + 7 spec(book-title/blurb/name/golden-finger/glossary/opening/fine-outline,只声明 tier #2)+ `build_outline_chapter_context`(with_outline_chapter 策略)。**@frontend T6.6**:`gen:api` 纳入 3 端点;`app/projects/[id]/toolbox/page.tsx`(RSC) + `components/toolbox/{ToolboxPage,ToolCard,GeneratorRunner}` + `lib/toolbox/*`(声明驱动纯函数,按 `input_fields` 渲染表单、按 `output_kind` 渲染预览、可入库者复用 `ConflictAdjudication` 走 409→ack)+ LeftNav「工具箱」+ ⌘K `nav-toolbox`/`action-gen-*`;legacy 3 跳现页不回归。**@qa T6.4**:`tests/test_t6_toolbox_e2e.py` 5 用例真 pg + mock 网关零 token,**未发现端点 bug**。**P2 收尾**:限流→`decisions.md` 记延后(单用户原型);noopener/Committable 经查本分支早已修;`provider_deps.py` 残留 `type:ignore` 为 SDK 私有属性探活、load-bearing 保留。**最终独立复跑全仓门禁绿**:后端 ruff/format 干净、mypy **195 Success**、alembic **无漂移**(descriptor 在代码、复用既有表,零建表迁移)、pytest **583 passed**;前端 gen:api/lint/tsc/vitest **279**/build OK。守不变量 #2(只声明 tier)/#3(预览不写库、入库经验收 gate)/#9(system_prompt 进缓存前块)。**"加生成器=加一份声明" 经实证(8 个新生成器复用同一通用执行器 + 同一前端组件)。** **实景 browse 复验(工具箱卡片栅格→脑洞预览→词条入库)待做。** 后续可选 P5:封面图像轨道 / 自定义 skill 运行期 schema / 收敛已有 3 竖井。