# PROGRESS — 多 Agent 协同任务台账

> 多 agent 共享的**唯一协同入口**。开工前读这里 + `memory/`；认领任务、尊重依赖、完成即更新。
> 完整任务定义见 `DEV_PLAN.md`；本台账只展开**当前阶段**，进入新阶段时再把该阶段任务拉进来。
> 协同规则见 `CLAUDE.md` →「多 Agent 协同」。

**状态图例**：⬜ 待办 · 🔵 进行中(注明 @skill + 日期) · ✅ 完成 · ⛔ 阻塞(注明等什么)

---

## 进行中：AI 对话（聊天记录）持久化 · develop 上新功能

> **背景**：[2026-07-09] 用户要求把作者↔AI 每轮往复（refine/rewrite/clarify/continue/工具箱生成器）**落库为聊天记录**，每章一个「AI 对话」抽屉展示。多 agent（4 学科起草→4 视角交叉评审→合稿）产出实施计划 `docs/design/ai-chat-history-plan.md`。**决策全 A**：clarify buffer-until-concluded / v1 只记 5 类交互 / append-only 不删。
> **契约与不变量纪律**：@db 建表(契约 C2 扩)→@backend 端点(C3 扩)→@frontend gen:api+接线→@qa E2E，逐级等上游契约「稳定」。守 #1/#3/#8：聊天记录是**旁路留痕**（同 `usage_ledger` 物种），前端**显式 append**、生成端点**保持只读不变**、**绝不**喂 `assemble`/prompt（计划 §8 有围栏 + code-review 守卫）。

| 任务 | 状态 | 负责 | 依赖 | 备注 |
|---|---|---|---|---|
| AC-1 `ai_messages` 表 + 迁移 | ✅ @db 2026-07-09 | @db | — | 计划 §2 canonical DDL；迁移 `ceabd3c2c00d`(down=`f6a7b8c9d0e1`)；C2 扩已「稳定」，AC-2 可动工 |
| AC-2 repo + append/list 端点 + schema + 契约 | ✅ @backend 2026-07-09 | @backend | AC-1 | `c675a74`；`SqlAiMessageRepo`(批 append seq/thread_id + newest-first union/分页) + `POST/GET /projects/{id}/ai-messages` + schemas；C3 扩已「稳定」→ AC-3 可动工（须 `pnpm gen:api`） |
| AC-3 gen:api + AiConversationDrawer + useAiConversation + 接线 5 处交互 | ⬜ | @frontend | AC-2 | 计划 §4；本章对话 + 项目级生成两段 |
| AC-4 E2E + repo/端点/hook 测试 | ⬜ | @qa | AC-3 | 计划 §6 |

---

## 已完成阶段：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 + React 补丁 | ✅ | @frontend | — | `f4c2c4e` next 15.1.3→**15.5.20**（registry backport 线，避 16.x 大迁移）、react/react-dom 19.0.0→**19.2.7**、eslint-config-next/@types 同步；`pnpm install` 重锁 + `pnpm build` 绿；`pnpm audit` next/react/react-dom 无 high/critical；新 `lib/security/dependencyFloors.ts` 版本下界守卫。**⚠️ 运维待办（用户执行）**：CVE 号无法对真库核验（升级后 `pnpm audit`/GitHub advisory 复核）+ 升级后轮换全部应用密钥 |
| CR-C2 🔴 多章链设 `recursion_limit` | ✅ | @backend | — | `ed0a679` **前提证伪**：langgraph 1.2.5 默认上限=10007（非 25）、count≤50→~200 步，**无实活崩溃**；仍加显式**顶层** `recursion_limit=count*4+10`（防御硬化）；测断言 config 键（行为「跑长链完成」测会 vacuous） |
| CR-H1 🟠 Kimi OAuth 轮询不占 DB 会话 | ✅ | @backend | — | `ca8fbc8` 轮询移出会话：`_poll_for_token`（无会话）+`_run_kimi_oauth_poll`，仅最终 persist 走 run_job 短会话；`job_runner` 未动（与 style_learn 共享） |
| CR-H2 🟠 修 `httpx.AsyncClient` 泄漏 | ✅ | @backend | — | `5dd336b`+`26f10d0` 请求级依赖改 `_http_client` 生成器（`async with`→`aclose`）；两处测 override 跟随改，堵住 E2E 误触真网 |
| CR-H3 🟠 Anthropic 结构化输出记真实 usage | ✅ | @llm | — | `cb8fddf` 改 `create_with_completion` 取 `raw.usage`，弃硬编码 0（修计费腐蚀） |
| CR-H4 🟠 `request_id` 贯通网关日志 | ✅ | @llm | — | `51fe9f4`+`e08b01a` LlmRequest 加可选 `request_id` + **条件透传**（仅非 None，否则覆盖 contextvars 反退化 sync/SSE 追踪）；`llm_call`+两处 `llm_provider_failed` 全锁测。契约 C1-扩 |
| CR-H5 🟠 `thinking` 字段：实现或删除 | ✅ | @llm | — | `afeac51` 删死字段 `LlmRequest.thinking`（YAGNI）；保 `Capabilities.thinking`（异名活字段）；回写 ARCH/contracts |
| CR-H6 🟠 流式路径加 per-provider 重试 | ✅ | @llm | — | `8a2b1b0` `_stream_with_retry` 对称 run：仅首 token 前重试，mid-stream 失败照抛（不变量 §4.5） |
| CR-H7 🟠 `TOOLBOX` 注册表不可变 | ✅ | @backend | — | `25d48b7` `Final[Mapping]=MappingProxyType(_TOOLBOX_BY_KEY)`（仿 SPECS） |
| CR-H8 🟠 `.env.example` 移除可用 Fernet key | ✅ | @devops | — | `baa41d3` 换不可用占位符（Fernet 拒绝→照抄即启动失败）+ 轮换提示。**⚠️ 真 key 仍在 git 历史，曾用该 key 的环境须轮换（用户）** |
| CR-H9 🟠 API 文本输入加 `max_length` | ✅ | @backend | — | `e1a24a2`+`d6b53d0` 全请求文本字段加上限（api_key≤512/brief≤1e4/final_text≤2e5/segment≤2e4/directive≤2e4/instruction≤2e3…）+ **2 MiB body 中间件 413 兜底** + `PAYLOAD_TOO_LARGE`。契约 C3；`gen:api` 无漂移 |
| CR-H10 🟠 前端 `errorCode` 去重 | ✅ | @frontend | — | `5665cc9` `lib/style/{useRefine,useStyleLearn}` 统一 import 自 `generation/cards` |
| CR-H11 🟠 Drawer 关闭还原焦点 | ✅ | @frontend | — | `6e2b478` 行为本分支早已修；提取 `lib/a11y/useRestoreFocus` + renderHook 回归测试 + DRY（Drawer/ContextDrawer 共用） |
| CR-H12 🟠 RefineView 取消在途 refine | ✅ | @frontend | — | `5c02792` useRefine 暴露 `abort`（setState-free）+ AbortController + aborted 守卫弃迟到响应；RefineView cleanup 调用 + 真 deps |
| CR-M1 🟡 后端架构整改批 | ✅ 大部 | @backend | — | `c596a8d` outline volume 下推 DB WHERE（Protocol/Sql/fake 加 keyword `volume`）；`bf3dabe` 角色/世界观入库 N+1→`create_many`（世界观 add_all；角色一次 IN 查+一次 flush 保 upsert 幂等/批内去重；单条 create 薄封装）；`1392830` jobs 读端点裸 ORM→JobRepo；`0892ff6` projects/templates 列表加分页（`ww_api.pagination` limit∈[1,200]/offset≥0，越界 422，下推 LIMIT/OFFSET）。**item#4 provider_deps `_client`→已由 @llm 交付**（`832ab7d` `OpenAICompatAdapter.probe_connection()` 公开探测 + provider_deps 改调，删私有 `_client` 反手 + `type:ignore`/`noqa:SLF001`；isinstance 收窄兜非 OpenAI 兼容）。**item#6 assemble 并行→SKIP**：8 查询共享同一注入 AsyncSession，`gather` 会腐蚀（ledger 警告属实）；安全化须把 session 工厂穿透 assemble + 8 repo 各独立 session + 并发正确性测，对单用户几 ms 收益不值该回归风险 |
| CR-M2 🟡 网关/adapters 整改批 | ✅ 大部 | @llm | — | `32451a9` pricing 未知 model 发 `pricing_unknown_model` warning（成本仍 0、照常记账）；`9439b97` 三 adapter 瞬时错误消息经 `provider_error_summary(exc)` 只留类名+status（不泄 vendor 响应体）。**item `is_open()` CQS→SKIP**：冷却过读时半开清态是**故意的惰性转移**（唯一转半开点），拆 CQS 需两处调用方各先发命令、漏发则熔断永不复位（更差），风格分不值动熔断语义 |
| CR-M3 🟡 LangGraph 链健壮性 | ✅ 大部 | @llm | — | `ec91c8f` runner 改用公开 `graph.aget_state(config).interrupts` 判暂停（弃私有 `__interrupt__`；SpyGraph 测同步补 aget_state 透传）；`6a739ca` review 节点五审 `asyncio.gather` 并行扇出（复用主图 add-only 记账并发范式 T3.8，collect 仍一次落库；并发峰值测 + 五审落齐测）。**item `run_overdue_scan` 幂等→SKIP**：`scan_overdue` 本已幂等（单条 `UPDATE...RETURNING` + `status NOT IN [CLOSED,OVERDUE]` + commit-only-if-changed，原子无部分空洞），且下一章扫描 current+1 自愈漏标；再动会触碰验收事务边界，单用户零收益 |
| CR-M4 🟡 前端 key/a11y/cleanup 批 | ✅ | @frontend | — | `47d5558` ARIA（NavDrawer `aria-controls` + Drawer `id` + ForeshadowCard 语义化 `<dl>/<dt>/<dd>`）+ `125908e` ReviewReport `flashMark` setTimeout 卸载清理（ref 存句柄）。**核实为无需改/已具备**：index-key（三处均 replace-all/append-only 静态展示、无天然稳定 id，index 安全；卡名可编辑作 key 更糟）、`stop`（经查**非**服务端 SSE 帧、是客户端 AbortController 动作，加则处理不存在事件）、AppShell nav label（`<nav aria-label>` 已有）、ProjectWizard 步骤焦点（`stepHeadingRef`+首帧守卫 useEffect 已实现） |
| CR-M5 🟡 前端 TS 边界类型收紧 | ✅ | @frontend | — | `d80d41a` `ApiErrorEnvelope` 改由生成 schema（`components.schemas.ErrorEnvelope`）派生防漂移 + `9cf4915` `hasApplicableFix` 改对象参类型谓词、消除 `applyConflictFix` 两处 `as string` + `16f2eae` `parseJsonBody`（结构校验 HEAD 已具备，补非对象/非 JSON 守卫分支测试） |
| CR-L1 🟢 LOW 清理批 | ✅ | @backend·@llm·@frontend | — | **@backend**：`49e3cb6` SSE draft/rewrite/review 补 OpenAPI `text/event-stream` 声明（共享 `_SSE_RESPONSE`）；`print`→logging **无需改**（owned 目录唯一 `print(` 是 `export_openapi.py` CLI stdout，前端 gen:api 管道消费，改则断管道）。**@llm**：`c9ffada` 续写链前一章无已验收终稿发 `chain_prior_accepted_missing` warning。**@frontend**：`6414542` 去冗余 `as`/`.valueOf()` cast（`stream/sse.ts` idx.valueOf、`jobs/job.ts` `dict[error] as string`；保留 load-bearing 的 CSS-var 键/变量键强转） |
| CR-D1 🟡 抬高后端依赖下界（可选升级评估） | ✅ | @devops | — | 8 个 pyproject 后端运行时下界抬到 lockfile 主次版本（`langgraph>=1.2`/`anthropic>=0.111`/`openai>=2.43`/`pydantic>=2.13`/`fastapi>=0.137`/`cryptography>=49` 等，每条 ≤ 实锁）；`uv.lock` **纯元数据刷新**（仅 `requires-dist` specifier，零 pinned 版本变动）；`uv lock --check` 绿、pytest **971**、ruff 干净。root dev 工具下界 + 前端 vitest/tailwind 升级评估未纳入（超范围） |

> **[2026-07-08] CRITICAL + HIGH 全部 ✅ 落地**（14 项 + 4 项低危收尾），多 agent 并行开发（@llm/@backend/@devops/@frontend 四簇串行整合，各 TDD 红→绿、逐项补回归测试）。**门禁全绿**：后端 ruff/format 干净 · mypy **229** · alembic **无漂移** · pytest **934**；前端 lint/tsc 干净 · vitest **669** · build OK · coverage **95.41%**。**14 项对抗式复核 0 problem**（skeptic 逐条实证 test 非 vacuous）。落地含 3 处前提证伪（C2 非实活崩溃/H4 sync-SSE 已由 contextvars 贯通/H11 行为本分支已修）。
>
> **[2026-07-08] MEDIUM/LOW（CR-M1..M5 / L1 / D1）亦全部清理 ✅**（四簇串行整合）：明确取舍——高价值低风险项落地、风险>价值或前提已陈旧的子项**有据 SKIP/NOOP**（assemble 并行 gather / breaker is_open CQS / overdue-scan 已幂等 / index-key 安全 / `stop` 非服务端帧 / 已具备的 nav-label、wizard 焦点）。**门禁全绿**：后端 mypy **232** · alembic 无漂移 · pytest **971**；前端 vitest **681** · build · coverage **95.45%**。附带修复：一 agent 误提交的非源文件（`.coverage`/`AGENTS.md`/`example_skills`）已撤销还原为未跟踪（`1bda024`）。
>
> **维度小结**：后端 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。
>
> **[2026-07-08] CR-M1 + CR-L1 后端片 ✅ 落地**（@backend，6 提交 `c596a8d`/`bf3dabe`/`1392830`/`0892ff6`/`49e3cb6`，各行为改动先写红测再实现）。CR-M1 4/6 完成（volume 下推 · N+1→create_many · jobs→JobRepo · 列表分页），item#4（去 `_client`）与 item#6（assemble 并行）SKIP（理由见 CR-M1 行）；CR-L1 后端片完成（SSE OpenAPI；print 属误报）。**门禁全绿**：后端 ruff/format 干净 · mypy **230** · pytest **960 passed**（较基线 955 +5 新测：volume 下推 1 + 分页 2 + 批量入库 2）· 无模型改动故免 alembic。新增 5 测无回归，既有 upsert 幂等/冲突 gate + m5 真 pg E2E 全绿。
>
> **[请求 → @llm]** CR-M1 item#4：`apps/api/ww_api/services/provider_deps.py:91` 需去掉对 adapter 私有 `_client` 的探测（`adapter._client.models.list()`）。owning 类 `OpenAICompatAdapter`/`ProviderAdapter`（`packages/llm_gateway/ww_llm_gateway/adapters/`）在 @llm 域——请在 adapter 上加一个公开 `async probe_connection()`（内部最小请求验 Key，如 `models.list()`），@backend 改调该公开方法即可删私有属性访问 + `type: ignore`/`noqa: SLF001`。行为保持不变。

---

## 进行中：写作工作台重构（writing-first）· 分支 `feat/writing-first-workspace`

> **背景**：[2026-07-07] 用户对写作体验提 8 点反馈，产品主张升级为 **writing-first 工作台**——进来即写、所有工具/AI 动作从编辑器内按需调用并回填正文、元数据由内容渐进生成。多 agent 工作流（扎根代码→Web+GitHub 调研→三方设计→四镜头对抗评审→定稿）产出 DEV_PLAN（`scratchpad/writing-first-workspace-DEVPLAN.md`，**ephemeral 未入库**；灵感/取舍见 `memory/selectable-write-prompts.md`）。
> **已拍板决策**：进来即写=**延迟落库**（首次击键才建占位项目 + 列表过滤空稿）；可选提示词 MVP=**纯前端** directive→volatile（不改 `DraftStreamRequest` 契约、不动 `write_craft.md` 字节 → 金标准零回归）；预设 MVP=先只上内置；续写=多候选卡点选插入。
> **纪律**：全程 TDD 小步提交，回填全守 HITL（AI 绝不 auto-replace，点选/接受才落草稿）；`/refine`、`/rewrite` 只读不写库（不变量 #3）。**门禁全绿**：前端 vitest **678** / lint / tsc / build / coverage **95.36%**；后端 ruff / mypy **229** / pytest **955**（含 WFW-9 clarify E2E）/ alembic 无漂移。**WFW-0~9 全部交付**（WFW-5/6/7 多 agent 并行 → `3dd7d28`；WFW-8 整章重写跨栈 → `6ed9c89`+`542c2ab`；**WFW-9 三段全交付** M1 `1652ad9`+`e30c933` / M2 `4a09f27`..`8e29b33` / M3 `ea9a9d9`+`a703a90`，契约 C-Rewrite+C-Clarify 见 `memory/contracts.md`）。**未 push、未开 PR**（等用户示意）。

| 任务 | 状态 | 负责 | 依赖 | 反馈# | 备注 |
|---|---|---|---|---|---|
| WFW-0 进来即写（延迟落库） | ✅ | @frontend | — | #5 | `8389572` `/write`(DraftComposer) + `lib/start/useDeferredDraft`(懒建占位项目+种首章草稿,双重幂等,空进空出不落库) + 落地页 CTA「直接开始写」主/「先立项」辅 |
| WFW-1 润色 / 再沟通 | ✅ | @frontend | — | #1#2 | `2f0b841` `useRefine`(同步/refine,版本栈,再沟通=上一版产出+新意见再回炉) + `refineApply`(接受回填按内容重锚,原文变则提示重选) + RefinePanel 内联卡 + Editor 上报选区 |
| WFW-2 续写（多候选） | ✅ | @frontend | WFW-0 | #2 | `9195cf3` `useContinue`(continue 生成器 with_prior_chapter 读前文,多候选累加) + ContinuePanel 候选卡点选插入章末 + flush 后读最新草稿 |
| WFW-3 内联工具箱 + 金手指露出 | ✅ | @frontend | — | #4#8 | `04f4785` `useToolboxTools`(客户端拉描述符) + InlineToolbox(列非 legacy 生成器含金手指) + `GeneratorRunner` 加 `onInsertText`(文本产物插入正文) |
| WFW-4 AI 写作三槽（文风/剧情/自定义） | ✅ | @frontend | — | #3 | `44b0b8a` `directive.ts` 加 `PLOT_PRESETS` + `composeDirective` 合并;DirectivePanel 拆文风/剧情两组 chips + 自定义。纯前端零契约,`write_craft.md` 字节不变 |
| WFW-5 内容感知书名 | ✅ | @frontend | — | #6 | `3dd7d28` 纯前端零后端 MVP：`buildManuscriptExcerpt`(首尾摘录+硬上界) + GeneratorRunner「用当前正文」按钮把摘录填 brief，book-title 据正文反推。**后续更佳版**：后端富化 book-title.md + `with_manuscript` 策略(未做) |
| WFW-6 侧栏 IA 收敛（ContextDrawer） | ✅ | @frontend | — | #7 | `3dd7d28` 加法式 ContextDrawer 右侧速查抽屉 + 底栏「速查」按钮；设定库/大纲**完整只读速查**，伏笔/规则/文风**摘要+跳全宽页**(后续可补真实数据)。`CONTEXT_DRAWER_ENABLED` 一键回滚、旧侧栏/全宽页路由全保留 |
| WFW-7 首次 AI 写作前极轻 genre 采集 + 无大纲降级 | ✅ | @frontend | WFW-0 | — | `3dd7d28` 无题材首次写章前弹 GenrePicker(临时并入本次 directive 不落库) + chapters 空时「尚无大纲，将按设定自由生成」提示，杜绝无题材 slop |
| WFW-8 整章级「再沟通/重写」（对话式迭代） | ✅ | @backend·@llm·@frontend | — | 新#1 | `6ed9c89`(后端)+`542c2ab`(前端)。后端 SSE `POST .../rewrite`（`rewrite.md` 教条 + `rewrite_node` + `RewriteStreamRequest`，复用 assemble 记忆注入，只读不写库、工具非写节点、守 #3/#7/#9）；前端 `useChapterRewrite`(版本栈)+`ChapterRewritePanel`+底栏「整章重写」：给意见→流式重写整章→多轮迭代→接受某版才替换正文(HITL)。契约 C-Rewrite。用户反馈「整章不满意要跟 AI 交流」 |
| WFW-9 M1 AI 反问澄清（refine 侧，路线A 两阶段） | ✅ | @backend·@llm·@frontend | — | 新#2 | `1652ad9`(后端)+`e30c933`(前端)。多agent并行(后端整片‖ChoiceChips)+主线集成。非流式预检 `POST .../refine/clarify`→`ClarifyDecision`(结构化,analyst,只读,失败回退放行)+clarify_refine spec/教条(SPECS#24);既有 refine 未改。前端 `useClarify`+`ChoiceChips`+`clarify.ts`(foldClarifications/门控)+RefinePanel 门控接线:意见<10字或点「帮我理清方向」→AI反问选项→折答案进instruction→走既有refine。契约 C-Clarify。**M1 refine 侧完成；M2/M3 见下两行** |
| WFW-9 M2 AI 反问澄清（rewrite 侧，整章重写路径） | ✅ | @backend·@llm·@frontend | — | 新#2 | `4a09f27`+`6fd6375`(后端)+`9b32743`+`4b1088a`+`8e29b33`(前端)，多 agent 串行整合。新非流式预检 `POST .../rewrite/clarify`→`ClarifyDecision`(复用 M1 schema/`run_clarify`)+新 `clarify_rewrite` spec/教条(章级,SPECS 24→25,prompt_hashes +1)+`RewriteClarifyRequest{feedback,prior_draft?}`(仅取有界 3000 字摘录喂 analyst,不整章入 LLM);只读不写库、失败放行。前端 `useRewriteClarify`+`toVM/PROCEED` 提取入共享 `clarify.ts`(DRY)+`ChapterRewritePanel` 门控(镜像 RefinePanel):意见<10字或「帮我理清方向」→反问→折进 feedback→走既有 rewrite SSE。契约 C-Clarify 扩;gen:api 纳入新端点 |
| WFW-9 M3 澄清 UX 打磨 + E2E | ✅ | @frontend·@qa | — | — | `ea9a9d9`(a11y):refine/rewrite 两面板 AI 反问块加 `role=status` 向读屏动态播报(WCAG 4.1.3 Status Messages)。`a703a90`(E2E):`tests/test_wfw9_clarify_e2e.py` 7 用例真 pg+mock 网关零 token——refine/rewrite 含糊→反问 / 清晰→放行 / 失败开路(parsed=None→200 need_clarification=false,不 500) / 只读不写业务表但记 usage_ledger / 404。无产品 bug。**深度视觉/交互 QA(真起前端浏览)为人工验收事项** |
| WFW-9 M2 AI 反问澄清（rewrite 侧，路线A 两阶段） | ✅ 后端 | @llm·@backend | WFW-8·WFW-9 M1 | 新#2 | `4a09f27`(llm)+`6fd6375`(后端)。镜像 M1：非流式预检 `POST .../rewrite/clarify`←`RewriteClarifyRequest{feedback(必给),prior_draft(可选)}`→`ClarifyDecision`(复用同一 schema,analyst,只读,失败回退放行)+`clarify_rewrite_spec`/教条 `clarify_rewrite.md`(segment→整章,SPECS#25);既有 `.../rewrite` SSE 未改。端点只喂 prior_draft 开头 3000 字摘录(不整章入 LLM)、日志脱敏。契约 C-Clarify(扩)。门禁绿:后端 ruff/format·mypy229·pytest948·alembic 无漂移。**前端仍欠 `pnpm gen:api`+M2 UI 接线(仿 RefinePanel 门控 ChapterRewritePanel) + M3 UX/E2E 待办** |
| WFW-9 E2E（两阶段 AI 反问澄清，refine+rewrite） | ✅ | @qa | WFW-9 M1·M2 | 新#2 | `tests/test_wfw9_clarify_e2e.py` 7 用例真 pg + mock 网关（provider/adapter 层，同 M2/T6 范式）零 token：refine 含糊→反问1问 / refine 明确→放行、rewrite 含糊→反问 / rewrite 明确→放行、失败开放(网关产非 ClarifyDecision→仍200 need_clarification=false 不 500 且仍记账)、只读(seed chapter 字节一致+无 review/digest 行)但 usage_ledger 真落 analyst 一行、404 信封。响应绝不回灌原文摘录。无产品 bug。门禁绿:ruff/format·mypy 本文件净·pytest 955 passed |

---

## 已封板：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）。证实「加生成器 = 加一份声明」这条路。
> **执行波次**：A(@llm 脑洞 spec + IdeaListResult schema + brief_only context 策略 + 通用 run 路径) → B(@backend GeneratorTool/TOOLBOX 注册表 + 通用 toolbox/generate 端点 + response schema + 接线 build_gateway_for_tier) → C(@qa 脑洞 generate E2E：真 DB + mock 网关，断言 unknown key 404 / 无 provider 503 / 仅记 ledger 不写业务表)。
> **契约纪律**：B 改 OpenAPI → `memory/contracts.md` 记 C 扩 + `PROGRESS.md` 记一笔（前端 P2 才 `pnpm gen:api`）。守不变量 #2（只声明 tier）/#3（预览不写库）。**预计无 DB 迁移**（descriptor 在代码、复用既有表）。

| 任务 | 状态 | 负责 | 依赖 | 契约 | 备注 |
|---|---|---|---|---|---|
| T6.1 脑洞 spec + IdeaListResult schema + brief_only context + 通用 run 路径 | ✅ | @llm | — | 新 schema/spec | 完成：`schemas.py` 加 `Idea`/`IdeaListResult`；`specs.py` 加 `brainstorm_spec`(light, reads=[projects], writes=[])；`generation_node.py` 加 `build_brief_context` + **通用 `run_generator`**（据 `spec.output_schema` 校验 parsed，不绑定具体类型，任一声明式生成器共用）；两包 ruff/format/mypy 干净、新增 13 测 + 全包 252 passed 无回归。**@backend T6.2 可起：从 `ww_agents` 取 `brainstorm_spec`/`IdeaListResult`、从 `ww_core.orchestrator` 取 `run_generator`/`build_brief_context`** |
| T6.2 GeneratorTool 描述符 + TOOLBOX 注册表 | ✅ | @backend | T6.1 | C3扩稳定 | `packages/skills/ww_skills/toolbox.py`(描述符类型,frozen) + `toolbox_registry.py`(`TOOLBOX` 11 条 + `get_tool`)。25+7 测 |
| T6.3 通用端点 toolbox/generate(+ingest) | ✅ | @backend | T6.2 | C3扩稳定 | `routers/toolbox.py` 3 端点 + `schemas/toolbox.py` + `services/toolbox_context.py`(纯 dispatch) + `get_tier_gateway_builder` 注入缝。generate 预览仅 commit ledger；ingest 复用 continuity 409 + partition_writes 白名单。未知 key 404 / 无 provider 503 |
| T6.4 E2E（toolbox 全路径） | ✅ | @qa | T6.3 | — | `tests/test_t6_toolbox_e2e.py` 5 用例真 pg + mock 网关零 token：列表 11 key、brainstorm 预览仅 ledger 零业务写(#3)、glossary ingest 409→ack 放行真落 world_entities、unknown 404、无 provider 503、token 不泄漏。无端点 bug |
| T6.5 7 新生成器 spec+schema + with_outline_chapter | ✅ | @llm | — | 新 schema/spec | `schemas.py` +7 输出 schema、`specs.py` +7 spec(book-title/blurb/name/golden-finger/glossary/opening/fine-outline，只声明 tier #2)、`generation_node.py` +`build_outline_chapter_context`。309 passed |
| T6.6 工具箱落地页 + 声明驱动 Runner | ✅ | @frontend | T6.3 | — | `app/projects/[id]/toolbox/page.tsx`(RSC) + `components/toolbox/{ToolboxPage,ToolCard,GeneratorRunner}` + `lib/toolbox/*`(纯函数) + LeftNav「工具箱」+ ⌘K `nav-toolbox`/`action-gen-*`。legacy 3 跳现页。vitest 155→279/build OK |
| P2 收尾（限流/noopener/Committable） | ✅ | @backend+@frontend | — | — | 限流→`decisions.md` 记延后(单用户原型)；noopener 与 Committable 经查本分支早已修；`provider_deps.py` 残留 `type:ignore` 为 SDK 私有属性探活,load-bearing 保留 |

**T6 出口（DoD）✅ 达成**：通用执行器驱动全部 8 个新生成器 + 工具箱落地页声明驱动；后端门禁绿（ruff/format/mypy **195**/alembic **无漂移**/pytest **583**）；前端门禁绿（gen:api/lint/tsc/vitest **279**/build）；E2E mock 网关零 token 全路径走通。**实景 browse 复验（工具箱卡片栅格→脑洞预览→词条入库）待做。** 后续可选 P5（封面图像轨道 / 自定义 skill 运行期 schema / 收敛已有 3 竖井）。

---

## 已封板：Prompt 管理重构（方案A）+ 全量 prompt 重写 ✅
> **背景**：把 21 个内置 agent 的 `system_prompt` 散文从 `specs.py` Python 常量外置为 `.md` 文件，并统一内置/用户 skill 的只读解析入口。设计真源 `docs/design/prompt-management.md`；契约 `memory/contracts.md` C6-ext；决策/坑见 `memory/{decisions,gotchas}.md`（2026-06-24）。**多 agent 工作流交付**：TDD + 并行开发 + 交叉验证评审。

| 任务 | 状态 | 负责 | 备注 |
|---|---|---|---|
| 方案A 步骤1–3：`spec_model`/`prompt_loader`/`prompts/*.md`×21/`schema_catalog`/`SPECS`/`REVIEW_RESERVED_NAMES` | ✅ | @llm | 金标准 sha256 取运行时值；`SPECS[name] is *_spec` 同一实例；三集合 name 恒等 |
| 方案A 步骤4–5：`SpecResolver`（内置纯内存零 DB）+ SkillRegistry 入库守卫前移 + `toolbox_registry` 桥接 `SPECS` | ✅ | @backend | 守卫锚 `REVIEW_RESERVED_NAMES ∪ set(SPECS)`；删 12 生成器 `*_spec` 直接 import |
| 打包/CI：`.gitattributes eol=lf` + wheel `artifacts` 带 `prompts/*.md` + `agents-wheel-smoke` 冒烟；`structlog` 补声明 | ✅ | @devops | 裸装 `import ww_agents; assert SPECS` 通过；`.gitattributes` 须完整嵌套路径 |
| 集成回归：#12 编排器无回归 + #15 apps/api import-smoke（`*_spec is SPECS[name]`）+ #16 CI 冒烟 | ✅ | @qa | 内容无关，长青 |
| 全量重写 21 prompt 内容（任务对齐 + prompt 工程最佳实践，schema 契约/不变量保持） | ✅ | @llm/@qa | 激进重写；金标准 fixture 重生成；护栏全验（无占位/无 model 名/中文/≤1 尾 LF/四审只读） |

**出口（DoD）✅**：后端门禁绿 ruff/format clean · **mypy 209** · **pytest 744 passed**；缓存断点前块字节稳定（不变量 #9，金标准守）；零 schema 变更、前端零影响（无 `pnpm gen:api`）。已合并 `develop`（外置 wave `4f561da`、重写 wave `eb356c4`）。**后续可选**：编排器 + apps/api 3 路由切 `SpecResolver` 后删 `*_spec` 导出。

---

## 已封板：K1 · Kimi Code OAuth（订阅 plan 接入）✅

> **背景**：用户要求接入 Kimi Code 订阅 plan（device-flow OAuth）。**已知情接受 ToS/封号风险**——研究确认：用订阅 plan 调用需伪造官方客户端 `User-Agent` + `X-Msh-*` 头，Kimi ToS 视为违规、可能封停会员（见变更日志研究结论）。用户明确选择此路径（非合规的 API Key 路径）。与既有 API-key провайдер `kimi` **分离**，新增独立 provider `kimi-code` 以便切换。
> **集成契约（研究确认，实现时对照 `github.com/picassio/pi-kimi-coder` 校验）**：
> - device flow：`POST https://auth.kimi.com/api/oauth/device_authorization`（client_id `17e5f671-d194-4dfb-9706-5516cb48c098`，env `KIMI_CLIENT_ID` 可覆盖，scope `kimi-code`）→ 返回 user_code/verification_uri/device_code/interval；轮询 `POST https://auth.kimi.com/api/oauth/token`（grant device_code，处理 authorization_pending/slow_down）。refresh：同 token 端点 grant refresh_token。
> - 推理：base `https://api.kimi.com/coding/v1`（OpenAI 兼容），model `kimi-for-coding`，`Authorization: Bearer <access_token>` + **必需伪造头**：`User-Agent`(如 `KimiCLI/1.x`) + `X-Msh-Platform`/`X-Msh-Version`/`X-Msh-Device-Name`/`X-Msh-Device-Model`/`X-Msh-Os-Version`/`X-Msh-Device-Id`(持久 UUID)。缺头→403。
> - token：access ~15min / refresh ~30d → **建网关时按需刷新**（临近过期刷新并持久化）。存储：`provider_credentials` 加 `auth_type`(api_key|oauth) + `oauth_enc`(Fernet 加密 JSON `{access_token,refresh_token,expires_at}`)。
> **执行波次**：A(@db 加列+迁移 ‖ @llm Kimi coding 适配器) → B(@backend OAuth device 服务 + 端点 + store + `_build_provider_adapter` 接线) → C(@frontend 设置页连接/轮询/断开 + 路由档位) → D(@qa E2E device-flow mock + 头断言)。
> **测试纪律**：device flow 注入 fake httpx（零联网）；适配器断言伪造头集；token 刷新逻辑单测；E2E mock auth+coding 两端点。

| 任务 | 状态 | 负责 | 依赖 | 契约 | 备注 |
|---|---|---|---|---|---|
| K1.1 provider_credentials 加 auth_type + oauth_enc 列 | ✅ | @db | — | C2扩 | 完成：models 加 auth_type/oauth_enc + api_key_enc 改 nullable；迁移 `1f011c42bd4d`；alembic check 无漂移。⚠️ K1.3 @backend 须把 `StoredCredential.api_key_enc` 改 `bytes\|None`（见下方请求 + memory）|
| K1.2 Kimi coding 适配器（伪造头 + OAuth bearer） | ✅ | @llm | — | C1扩 | 完成：`adapters/kimi_code.py`(KimiCodeAdapter 子类化 OpenAICompatAdapter + 伪造头 + 稳定 device_id)；`build_adapter` 加 `kimi-code` 分支；UA 验证为 `KimiCLI/1.5`（pi-kimi-coder 仅设 UA，X-Msh-* 为契约附带，见 C1扩 校正）；11 测全绿 |
| K1.3 OAuth device 服务 + 端点 + store + 接线 | ✅ | @backend | K1.1,K1.2 | C3扩 | 完成：`services/kimi_oauth.py`(start/poll/refresh,注入 httpx Protocol)+3 端点(start 202→jobs 后台轮询/disconnect/status,复用 run_job)+`StoredCredential` 收口(api_key_enc→nullable+auth_type/oauth_enc，K1.1 的 2 mypy red 已消)+store `upsert_oauth_credential`/`delete_credential`+`_build_provider_adapter` OAuth 按需刷新(300s 缓冲)。**token 不进 job 结果/日志/响应**。全仓门禁绿(ruff/format/mypy **154**/alembic 无漂移/pytest **430**)。OpenAPI 加 3 端点→**@frontend K1.4 须 `pnpm gen:api`** |
| K1.4 设置页 Kimi Code 连接流 | ✅ | @frontend | K1.3 | — | 完成：`gen:api` 纳入 3 OAuth 端点；`KimiCodeOauth` 连接区（显 user_code+开授权页+轮询 job→已连接/断开）+ 可编辑档位路由（含 `kimi-code:kimi-for-coding`）；纯逻辑 `lib/settings/{kimiOauth,providers}` + 25 新 vitest；前端门禁全绿（gen:api/lint/typecheck/vitest 155/build）。运行时验真 auth.kimi.com 留 K1.5 |
| K1.5 K1 E2E | ✅ | @qa | K1.4 | — | `tests/test_k1_kimi_oauth_e2e.py`(4 用例,真 pg)：device-flow(pending×2→成功)→加密 token 真落 pg(无明文+回环)→建网关断言 KimiCodeAdapter coding base+伪造头+bearer→refresh-on-build 更新存量 token(DB 真源)→disconnect 清除；token 全程不进 job 结果/状态/start 响应。全仓门禁绿(ruff/format/mypy **154**/alembic 无漂移/pytest **434**)。**K1 完成，可封板** |

**K1 出口（DoD）✅ 全部达成**：作者在设置页用 Kimi Code OAuth 登录（device flow，显 user_code + 开授权页 + 轮询 job）→ token Fernet 加密入库（`oauth_enc`，全程不进 job 结果/日志/响应）→ 把档位路由到 `kimi-code:kimi-for-coding` → 建网关时构 `KimiCodeAdapter`（coding base + 伪造 UA/X-Msh-* 头 + bearer）→ token 临近过期建网关时自动刷新并回写。后端门禁绿（ruff/format 干净 / mypy **154** / alembic **无漂移** / pytest **434**）；前端门禁绿（gen:api/lint/tsc/vitest **155**/build）；E2E（`tests/test_k1_kimi_oauth_e2e.py` 4 用例）device-flow mock 零联网走通，DB 真源逐项断言（含 token 不泄漏 + refresh-on-build + disconnect 清除）。**K1 完成。** **⚠️ 仍需用户用真账号对真 auth.kimi.com/api.kimi.com 做一次联网冒烟（device-flow 需伪造官方客户端 UA → ToS 视为违规、可能封停会员，用户已知情自担）；E2E/单测仅验逻辑、不联网。**

---

## 状态：M1–M5 全部完成（含 M5 三项收尾余项已清理）

> **当前无活跃阶段。** Phase 0–5（M1–M5）已全部交付，M5 的三项非阻塞收尾余项（①Codex 读端点、②Anthropic/Gemini 真实适配器选择、③`packages/skills` 纳入 workspace）亦已清理。各阶段详情见下方「已归档阶段」+「变更日志」。
> 最终门禁（本次全绿）：后端 ruff/format 干净、mypy **144 文件**、alembic **无漂移**、pytest **400 passed**；前端 lint/typecheck 干净、vitest **130 passed**、build OK。
> **下一步若继续**：DEV_PLAN §4 的 P2/后续（向量检索 / 真任务队列 / 全书一致性扫描 / 社区市场），进入时把对应任务拉入本表。
> **仍开放的非阻塞备注**：@devops 复核根 `uv.lock`（@backend 改了 `packages/skills/pyproject.toml` deps 后已 `uv sync` 重建 ww-skills）；Anthropic/Gemini 真实回退已接线（`build_adapter` 工厂 + 按 provider 选适配器）但尚未对真 SDK 联网验证（单测/E2E 用 OpenAI 兼容 mock）。

---

## 已归档阶段

<details><summary>Phase 5 · 生成 + 多 provider + 扩展 (M5) — ✅ 全部完成（2026-06-19）</summary>

T5.1 worldbuilder + character-gen 节点（C6✅扩；群像防雷同注入已生成+已有 + 入库前 continuity 校验缝 `precheck_generated_cards`）@llm · T5.4 网关多 provider 韧性（C1✅扩；Anthropic/Gemini 适配器 + 回退链 + `CircuitBreaker` + tenacity 退避 + 能力协商/降级 `served_by.degraded`，无 OpenAPI 形变）@llm · T5.5 Skill 运行时 + 规则（C8 + C3✅扩；`SkillRegistry.load` 越权声明拒绝 + 表权限沙箱 filter_reads/partition_writes + `POST /rules`）@backend · T5.2 生成/入库 API（C3✅扩；world/characters generate 预览 + POST characters 入库双 gate：continuity 预检 409 + partition_writes 白名单 + schema→JSONB 形变 + GET rules/skills；`build_gateway_for_tier` 接线 chain_from_routing 回退）@backend · T5.3+T5.6 角色生成器 + 世界观设计器 + 设定库 Codex + 规则页 + 技能库 + 命令面板（⌘K）@frontend · T5.7 M5 E2E + 切 provider（真 Gateway deepseek 失败→openai 回退、DB ledger 记实际服务方、能力降级）@qa。
出口达成：后端门禁绿（ruff/format 干净 / mypy 142 / pytest 391 / alembic 无漂移，复用 T0.2 既有表零建表迁移）；前端门禁绿（gen:api / lint / tsc / vitest 125 / build）；E2E 真 DB + mock 多 provider 零 token 走通。**M5 完成 → M1–M5 全部交付。** 三非阻塞余项（①Codex 读端点、②Anthropic/Gemini 真实适配器选择、③`packages/skills` 纳入 workspace）随后于同日全部清理（见变更日志 follow-up #1–#4）。

</details>

<details><summary>Phase 4 · 文风 (M4) — ✅ 全部完成（2026-06-19）</summary>

T4.2 style-auditor 双轨 + 第四审（C6✅扩/C4✅扩；`style_extract_spec`(analyst,16 维指纹带证据,不进 review 图) + `style_drift_spec`(name="style",light,第四审并入 REVIEW_SPECS) + `refiner_spec`(writer 纯文本)，REVIEW_SPECS 四审齐 + collect/SSE 加 style）@llm · T4.1 jobs 长任务基建（C3.5✅；`JobRepo`/`SqlJobRepo` + `run_job` 独立 session + lifespan zombie reaper）@backend · T4.3 API style + refine（C3✅扩；`POST /style` 202+jobs BackgroundTask、`GET /style` 最新指纹、`POST /refine` 同步 diff 不写库）@backend · T4.4 文风页 + 漂移 + 回炉 + job 轮询（`useJobPoll` 状态机 + StyleUpload/FingerprintView + 审稿页第四审 StylePanel + RefineView diff 采纳合入）@frontend · T4.5 M4 E2E（学文风→202 job→后台跑完→指纹真落 pg version+1 + 第四审 SSE/`chapter_reviews.style` 真填 + 回炉不写库）@qa。
出口达成：后端门禁绿（ruff/format 干净 / mypy 115 / pytest 296 / alembic 无漂移，全程零建表迁移，复用 T0.2 预留的 `style_fingerprint`/`chapter_reviews.style`/`jobs`）；前端门禁绿（gen:api / lint / tsc / vitest 96 / build）；E2E 真 DB + mock 网关零 token 走通 学文风→第四审打分→回炉，DB 真源逐项断言。@docs 已把 `GET /style` 回写 ARCH §7.2/PRODUCT_SPEC §6。

</details>

<details><summary>Phase 3 · 伏笔 + 节奏 (M3) — ✅ 全部完成（2026-06-18）</summary>

T3.1 foreshadow Repository + 纯函数状态机（OPEN/PARTIAL/CLOSED/OVERDUE 全转移 + 到期判据 + `ForeshadowLedger*` 写侧 repo）@backend · T3.4 outliner Agent + 伏笔窗口（C6✅扩；`outliner_spec` + `OutlineResult`(含 foreshadow_windows) + `run_outline` 只读节点）@llm · T3.3 foreshadow-analyst + pace-checker 节点（C6✅扩/C4✅扩；并入 REVIEW_SPECS 三审齐 + collect 分列 + SSE foreshadow/pace 事件）@llm · T3.2 到期扫描 BackgroundTask + 登记/状态接口（验收后 `run_overdue_scan` 自建 session 置 OVERDUE + `POST/PATCH .../foreshadow`，重复/非法→VALIDATION 422）@backend · T3.5 API outline + foreshadow（C3✅扩；`GET .../foreshadow?status=` 看板 + `POST .../outline` upsert outline 表含窗口）@backend · T3.6 伏笔看板 + 大纲编辑器 + 节奏报告（四泳道 + beats+窗口徽标 + 审稿页节拍图▁▃▅）@frontend · T3.7 M3 E2E（伏笔埋设→进展→验收后扫描 OVERDUE + 大纲含窗口 + 三审 SSE，strict-xfail 钉住并发记账 bug）@qa · T3.8 [bugfix] 并行审记账并发安全（`SqlAlchemyLedgerSink.record` 改 add-only 去 `await flush`，解 AsyncSession 并发 flush 冲突，xfail 转 XPASS 后解钉）@llm。
出口达成：后端门禁绿（ruff / mypy 111 / pytest 228 passed（0 xfailed/0 failed）/ alembic 无漂移，`foreshadow` 表已 T0.2 建齐无新建表）；前端门禁绿（gen:api / lint / tsc / vitest 69 / build）；E2E 真 DB + mock 网关零 token 走通，DB 真源逐项断言。T3.7 暴露并发记账 bug → T3.8 修复 → 解钉 xfail。

</details>

<details><summary>Phase 2 · 一致性 + 验收 (M2) — ✅ 全部完成（2026-06-18）</summary>

T2.1 续审 AgentSpec+结构化输出契约+网关 instructor 接线（C6✅稳定，改 C1：`run()` 填 `parsed`）@llm · T2.2 LangGraph 并行审+collect 留痕+review SSE（C4✅扩）@llm · T2.3 摘要/审稿留痕 Repository+章节 version（@backend，表已 T0.2 建）· T2.4 验收事务+冲突 gate（单原子事务 + R2 事务外提炼 digest）@backend · T2.5 review API+历史（C3✅扩）@backend · T2.6 审稿报告页+冲突裁决+验收 gate @frontend · T2.7 M2 E2E（真 DB+多档位 mock 网关零 token）@qa。
出口达成：后端门禁绿（ruff / mypy 90 / pytest 114 / alembic 无漂移）；前端门禁绿（gen:api / lint / tsc / vitest 45 / build）；E2E 写→审→裁决→验收→摘要入库闭环、DB 真源逐项断言。设计决策 R1–R6 全兑现（见 `memory/decisions`）。

</details>

<details><summary>Phase 1 · 写章闭环骨架 (M1) — ✅ 全部完成（2026-06-18）</summary>

T1.1 LLM 网关接口+DeepSeek 适配器+usage_ledger 记账（C1✅稳定）@llm · T1.2 记忆 assemble 确定性选择（C5✅稳定）@backend · T1.3 LangGraph 写章节点+Postgres checkpointer+SSE（C4✅稳定）@llm · T1.4 立项+写章 draft(SSE)+PUT 自动保存（C3✅稳定）@backend · T1.5 AppShell+作品库+5 步立项向导 @frontend · T1.6 写作工作台（三栏+流式打字机+注入透明面板+自动保存）@frontend · T1.7 提供商凭据管理（Fernet 加密+测试连接）@backend · T1.8 设置页（档位路由+凭据行）@frontend · T1.9 M1 E2E（真实 DB+mock 网关零 token；暴露并修复 ledger 提交 bug）@qa。
出口达成：后端门禁绿（ruff / mypy 68 / pytest 57 / alembic 无漂移）；前端门禁绿（gen:api / lint / tsc / vitest 24 / build）；E2E 闭环走通。
M2 起可补的 M1 占位：本章助手/注入透明面板、向导主角/世界观字段、档位路由可写。

</details>

<details><summary>Phase 0 · 基建与栈修订 — ✅ 全部完成（2026-06-18）</summary>

T0.1 monorepo 骨架 ✅ @devops · T0.2 16 MVP 表迁移（无漂移，users stub）✅ @db · T0.3 FastAPI 骨架 + config + 错误信封(带 request_id) + OpenAPI + `GET /jobs/:id` + structlog ✅ @backend · T0.4 前端骨架 + 纸感 token + OpenAPI→TS gen:api ✅ @frontend · T0.5 CI（含 pg service + alembic 漂移校验）✅ @devops · T0.6 架构栈修订回写规格 ✅ @docs。
出口达成：backend 门禁绿、frontend 门禁绿、API 实跑 200 带 `x-request-id`。

</details>

---

## 变更日志（append-only，最新在最上）

> 格式：`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件`

- [2026-07-09] @backend — **✅ AC-2 repo + append/list 端点 + schema + 契约**（计划 §3，`c675a74`）。repo `packages/core/ww_core/domain/ai_message_repo.py`（`AiMessageRepo`/`SqlAiMessageRepo`/`AiMessageView`/`AiTurnRow`，经 `ww_core.domain` 导出）：`append`(一批多条 bubble → 批内 `seq`=enumerate 0 基位置、同 `thread_id`、meta 新 dict 不原地 mutate、`add_all`+一次 flush+refresh，**只 flush 不 commit**) + `list_for_chapter`(`created_at DESC, seq DESC` newest-first；`chapter_no` 非 None → 本章 ∪ 项目级 `IS NULL`，None → 仅项目级；可选 `kind`；`limit/offset`)。schemas `apps/api/ww_api/schemas/ai_messages.py`：批级 append 信封 `AiMessageAppendRequest{thread_id,chapter_no(ge=1)|null,kind,tool_key,messages}` + `AiMessageInput{role,content(min1,max200_000),meta(序列化≤50_000)}` + `AiMessageView` + `AiMessageListResponse{messages}`；`Literal` 为 kind/role 枚举权威（边界 422）。端点 `apps/api/ww_api/routers/ai_messages.py`（prefix `/projects`，tag `ai-messages`，已注册 main）：**`POST /projects/{id}/ai-messages`**(201，项目 404，校验 422，append 只 flush + 端点 `commit()`) + **`GET /projects/{id}/ai-messages?chapter_no=&kind=`+`PageDep`**(200，newest-first，只读)。**这是显式记录写入**——五个生成端点逐字节不变、只读（守 #3）；`ai_messages` 旁路侧记录，`assemble()`/agent 永不读（守 #1/#6）。注入缝 `get_ai_message_repo`。**TDD**：repo 真 pg 单测 9 passed（seq 0..n-1 / 同 thread_id / `created_at DESC,seq DESC` / union 含 NULL / 分页 / flush-only 跨 session 不可见）；端点集成 12 passed（round-trip / 本章∪项目级 / kind 过滤 / 404×2 / 422×5〔非法 role/kind、空 messages、空 content、超上限 content、超上限 meta〕 / commit 边界 / 响应 key 集合不泄漏）。**门禁全绿**：ruff/format 干净 · mypy **238 文件** · **`alembic check` 无漂移**（本任务不加模型）· `export_openapi` 成功（新端点+4 schema 已入表面）· pytest **992 passed**（971 基线 + 21）· 新模块 cov router 100%/schemas 100%/repo 97%（余 Protocol `...` 桩），全局 89%。契约 **C3 扩（AC-2）已登记「稳定」** → **@frontend AC-3 须 `cd apps/web && pnpm gen:api`** 后消费码生成名。
- [2026-07-09] @db — **✅ AC-1 `ai_messages` 表 + 迁移**（计划 §2 canonical DDL）。`packages/db/ww_db/models.py` 加 `AiMessage(UuidPk, CreatedAt, Base)`（补 `Uuid` 导入）：`project_id`(FK projects CASCADE, NOT NULL, indexed)/`chapter_no`(Integer nullable=项目级)/`thread_id`(Uuid NOT NULL)/`seq`(Integer NOT NULL, repo 批内 0 基定序)/`kind`(Text 无 CHECK)/`tool_key`(Text nullable)/`role`(Text + `ck_ai_messages_role` CHECK author|ai)/`content`(Text NOT NULL 全文)/`meta`(JSONB NOT NULL server_default `'{}'`)/`id`+`created_at`(mixin)；append-only 无 updated_at。复合索引 `ix_ai_messages_project_chapter=(project_id,chapter_no,created_at DESC)` 手写 `sa.text` DESC + FK 单列索引。迁移 `ceabd3c2c00d`（down=`f6a7b8c9d0e1`，**autogenerate 仅捕获 ai_messages 无杂散漂移**，DESC 表达式归一为 `sa.text`）。**T0.2 MVP schema 后第一张新业务表**——对话历史是 18 表都无的能力、正交于 review/job/手稿，属真新增（非重复建表）；是**旁路侧记录非手稿真源**，`assemble()`/agent 永不读（守 #1/#3/#6，围栏留 AC-4/P4 收口）。**门禁全绿**：ruff/format 干净 · mypy **233 文件** · `alembic upgrade head` + **`alembic check` 无漂移** + **downgrade→upgrade 往复干净** · pytest **971 passed**（基线不变，无代码用新表）。契约 **C2 扩（AC-1）已登记「稳定」** → AC-2 @backend 可动工。
- [2026-07-08] @devops — **✅ QA-CR CR-D1 抬高后端依赖下界至 lockfile 主次版本（保可复现）**。8 个 pyproject（`apps/api` + `packages/{core,agents,config,db,llm_gateway,shared,skills}`）的后端运行时依赖下界从过旧值抬到实锁 MAJOR.MINOR：`langgraph>=1.2`/`langgraph-checkpoint-postgres>=3.1`/`openai>=2.43`/`anthropic>=0.111`/`google-genai>=2.9`/`instructor>=1.15`/`tenacity>=9.1`/`pydantic>=2.13`/`pydantic-settings>=2.14`/`structlog>=26.1`/`asyncpg>=0.31`/`alembic>=1.18`/`fastapi>=0.137`/`uvicorn>=0.49`/`cryptography>=49`（每条 ≤ lockfile 实锁，同小版本线的 sqlalchemy/psycopg 留原样）。`uv.lock` 变动为**纯元数据刷新**——仅各 workspace 成员 `[package.metadata].requires-dist` 的 specifier 字符串随下界更新，**零 pinned `version=` 变动、零 re-resolve、零第三方版本 bump**。root dev 工具下界（pytest/ruff/mypy/httpx）+ 前端 vitest 3.x/tailwind 4.x 升级评估**明确不纳入**（非运行时可复现关键，超范围）。**门禁**：`uv lock --check` 绿（元数据刷新后一致）· `uv run ruff check .` 干净 · pytest **971 passed**（与基线一致，无回归）。无源码/测试改动。
- [2026-07-08] @llm — **✅ QA-CR MEDIUM/LOW 网关+链整改批（CR-M2/M3/L1@llm/M1.4，TDD 逐项提交）**。**完成 6**：`32451a9` pricing 未知 model 发 `pricing_unknown_model` warning（成本仍 0）；`9439b97` 三 adapter 瞬时错误消息经 `provider_error_summary(exc)` 只留类名+status（不泄 vendor 响应体）；`832ab7d` `OpenAICompatAdapter.probe_connection()` 公开探测 + provider_deps 弃私有 `_client` 反手（CR-M1.4）；`c9ffada` 续写链前一章无已验收终稿发 `chain_prior_accepted_missing` warning（CR-L1）；`ec91c8f` 链 runner 改用公开 `graph.aget_state(config).interrupts` 判暂停（弃私有 `__interrupt__`）；`6a739ca` review 节点五审 `asyncio.gather` 并行扇出（复用主图 add-only 记账并发范式 T3.8，collect 一次落库）。**SKIP 2**：CR-M2 `is_open()` CQS（惰性半开转移是唯一转半开点、拆分漏发命令会致熔断永不复位，风险>收益）；CR-M3 `run_overdue_scan` 幂等（`scan_overdue` 本已是原子 `UPDATE...RETURNING`+`NOT IN[CLOSED,OVERDUE]`+commit-only-if-changed，且下一章 current+1 扫描自愈漏标，再动会触碰验收事务边界）。**门禁全绿**：后端 ruff/format 干净 · mypy **232 文件** · pytest **971 passed**（+11 新测：pricing 2 / 错误脱敏 4 / probe 2 / 续写缺前文告警 2 / 并行审 1）· 无 DDL/无迁移。守不变量 #1/#3/#4（熔断/验收事务/add-only 记账 sink 未动）。
- [2026-07-08] @llm/@backend/@devops/@frontend — **✅ QA-CR CRITICAL + HIGH 整改批全部落地**（14 项 + 4 低危收尾）。多 agent 工作流：先 14 个只读 agent 逐项**扎根真实代码**产精确修复+测试规格（纠出 3 处台账前提漂移），再按 owner 四簇**串行整合**入单工作树（避免并发写树 + repo 级 `ruff format .` 竞态），各 TDD 红→绿、逐项补回归测试、逐 fix 小步提交，末尾 14 个 skeptic **对抗式复核**（实证每条 test 非 vacuous，0 problem）。**关键落地/纠偏**：C1 next 15.1.3→**15.5.20**+react **19.2.7**（`pnpm audit` 清、`dependencyFloors.ts` 下界守卫，**CVE 号无法对真库核验+密钥轮换为用户运维事项**）；C2 **前提证伪**（langgraph 默认上限 10007 非 25，无实活崩溃）仍加显式顶层 `recursion_limit`；H4 request_id **条件透传**（否则覆盖 contextvars 反退化）；H2 修请求级 httpx 泄漏时**揪出 E2E 会误触真网**（override 跟随修）；H9 全文本字段上限+2 MiB body 中间件 413 兜底（契约 C3，`gen:api` 无漂移）；H8 `.env.example` 换不可用占位符（**真 key 仍在 git 历史，曾用环境须轮换**）。**门禁全绿**：后端 mypy 229/pytest **934**/alembic 无漂移；前端 vitest **669**/build/coverage 95.41%。契约 C1-扩（LlmRequest.request_id）+ C3（H9 caps）。**MEDIUM/LOW 择批后清理。** 提交 `cb8fddf`→`dd6d2fc`（19 提交）。

- [2026-07-08] @backend — **✅ QA-CR 后端整改 5 项（TDD，逐 fix 提交）**。**CR-C2**：`chain_runner.run_chain_job` 给 `graph.ainvoke` 传显式 top-level `recursion_limit=count*NODES_PER_CHAPTER+HEADROOM`（防御硬化，命名常量）。**CR-H1**：Kimi OAuth 设备轮询移出 DB 会话——新增 `_poll_for_token`(纯 HTTP)/`_make_persist_work`/`_run_kimi_oauth_poll`，仅最终持久化经 `run_job` 开短会话（防连接池饥饿；删 `_make_poll_work`）。**CR-H2**：请求级 httpx 客户端改**生成器依赖** `_http_client`（`async with ... yield`，请求结束 aclose 修泄漏）；`_default_http_client` 保留供后台裸调；E2E + 端点测试 override 键跟随改 `_http_client`。**CR-H7**：冻结 `TOOLBOX` 为只读 `MappingProxyType`（`_TOOLBOX_BY_KEY` 私有底 dict + `Final[Mapping]`，仿 SPECS）。**CR-H9**：用户请求字符串字段补 `max_length`（providers/generation/projects/style/foreshadow/rules/templates，命名常量无魔数，样本列表长度+每条双上界）+ 新错误码 `ErrorCode.PAYLOAD_TOO_LARGE`(413) + 应用级 `body_size_limit_middleware`(2 MiB，按 Content-Length 拦截自建 413 信封)，`main.py` 注册。**门禁全绿**：ruff/format 干净 · mypy **229 文件**（0 错）· pytest **928**（+23：recursion_limit 1 / poll-no-session 1 / http-gen 1 / toolbox-readonly 1 / input-size 19[17 参数化 + samples 1 + body413 1]）· **无 DDL/无迁移**。记 contracts C3（CR-H9：新增 `maxLength` + `PAYLOAD_TOO_LARGE`）。→ **@frontend 须 `pnpm gen:api`**（请求字段现有 `maxLength`；本 cluster 未代跑）。守不变量 #3/#8/#9。

- [2026-07-06] @backend/@llm/@frontend — **✅ 灵感⑦ 群像按定位配比·缩减版 + §0 归属纠偏（develop 直提）**。角色群像生成支持「按定位配比」下发。**契约变更** `CharacterGenerateRequest` 加 `role_mix:dict[str,int]|None`——`model_validator(mode="after")` 定义与既有 `count` 的关系：在场时校验（非空、每项≥1、总和≤`MAX_GENERATE_COUNT`=12）并把 `count` 归一为各值之和（构造期归一化，忽略客户端 count）；缺省退回单一 `role`+`count`。**§0 归属纠偏**：`build_character_gen_context`+`run_character_gen`（`ww_core/orchestrator/generation_node.py`）确为 **@llm 域**——加末位默认 `role_mix=None` 关键字参，在场时注入「角色定位配比」块（保插入序、确定性、无时间戳，守 #6/#7 assemble 纯函数；覆盖单一 role 行）；`routers/generation.py` 穿 `role_mix=body.role_mix`。`character-gen.md` 加「按配比严格分配 role、各定位之和=总数」纪律 → **regen `prompt_hashes.json`**（仅 character-gen 哈希变，**不进 SPECS→不 bump 计数**）。**前端** `cards.ts` 加 `sanitizeRoleMix`（去空/去重/clamp≥1/按 12 截断）+`roleMixTotal`+`buildCharacterGenerateRequest` 第 4 参 roleMix；`useCharacterGen` `CharacterGenInput.roleMix` 穿参；新 `RoleMixEditor.tsx` + `CharacterGenerator` SegmentedControl 单一/配比双模。**TDD**：schema 校验 7 例（总和≤12/空 dict/非正值拒绝）+ context 注入 2 例 + run 穿参 1 例 + 前端 cards 构造/clamp 5 例 + hook renderHook 1 例。**门禁全绿**：后端 ruff/format 干净 · mypy **222** · pytest **858**（+11）· 无迁移（未动模型，未跑 alembic）；前端 gen:api/lint/typecheck 干净 · vitest **543**（+6）· coverage 95%（cards.ts 100%）· build OK。记 contracts C3。守不变量 #2/#6/#7/#9。**无 DB 迁移**。

- [2026-07-06] @llm/@db/@backend/@frontend — **✅ 灵感③ 人物塑造 advisory 审（develop 直提）**。写→审链新增第五审「人物塑造」：**advisory 单维——仅建议、不阻断验收**（不进 conflicts、不碰 `assert_conflicts_resolved`、**不入 `REVIEW_RESERVED_NAMES`**，D2）；只做人物塑造、不做氛围（氛围噪声大折进 style/pace）。**新 SPEC** `characterization_spec`（`tier="analyst"` 不写 model，`reads=("characters",)` 以人物卡 motive/appearance 为客观锚点，`writes=()` 只读，`prompts/characterization.md` **显式忽略文本长度与辞藻华丽度**、引用逐字片段+置信度对抗 LLM-judge 冗长/辞藻偏见）+ schema `CharacterizationReview{issues:[…character/aspect/where/quote/diagnosis/suggestion/confidence]}`（全字段默认值守解析韧性）+ 注册 `SCHEMA_CATALOG["characterization"]`；**计数三处 22→23**（`specs.py` assert / `test_prompt_loader.py` len / `schema_catalog.py` docstring）+ **regen `prompt_hashes.json`**（仅加一条，无其余漂移）。**DB** `chapter_reviews` 加 1 列 `characterization`（**JSONB nullable**，迁移 `f6a7b8c9d0e1`，nullable 免 backfill）。**契约变更** `ReviewHistoryItem`+`ReviewView` 加 `characterization`；接线链 `graph.py REVIEW_SPECS` 末位追加 + `chain/nodes.py ReviewRecordRepo` + `collect.py {CHARACTERIZATION/extract_characterization/ReviewRecorder/collect_reviews}` + `review_repo.py {Protocol/SqlReviewRepo/_to_view}` + `sse.py {EVENT_CHARACTERIZATION/characterization_event/_section_result_events}`（normalize 按 sorted 键自动纳入，字典序排最前不打乱既有四审断言）。**前端** `lib/review/{sse.ts,history.ts,useReviewStream.ts}` + 新 `CharacterizationPanel.tsx`（advisory 只展示、**无裁决 UI**、按置信度降序 + 低置信度折叠控注意力预算）+ `ReviewReport.tsx` 挂面板 + 双 seed。**门禁全绿**：后端 ruff/format 干净 · mypy **221** · pytest **848**（+核心 char 审 12 + agents spec 11 + api accept 2 等）· alembic 无漂移；前端 gen:api/lint/typecheck 干净 · vitest **536**（+10 含 `test_characterization_does_not_block_accept` 对应前端 reducer/normalize/parse）· coverage 95% · build OK。记 contracts C3+C4。守不变量 #2/#3/#9。依赖 ⑧（motive/appearance 已入 CharacterCard 契约，作客观锚点）。

- [2026-07-06] @backend/@llm/@frontend — **✅ 灵感⑤ AI 立项方案生成+预填（develop 直提）**。向导阶段（project 未建）一键推演结构化书级蓝图预填立项向导。**新 SPEC** `project_plan_spec`（`tier="analyst"` 不写 model，`reads=()`/`writes=()` 纯预览，`prompts/project-plan.md`）+ schema `ProjectPlanResult`（书名候选 list + 时空背景/叙事结构/故事核心/结局设计/基调，**全字段默认值守解析韧性**）+ 注册 `SCHEMA_CATALOG["project-plan"]`；**计数三处 21→22**（`specs.py` assert / `test_prompt_loader.py` len / `schema_catalog.py` docstring）+ **regen `prompt_hashes.json`**（仅加一条）。**新端点** `POST /skills/project-plan/generate`（**不带 project 前缀**绕开 toolbox 404）← `ProjectPlanGenerateRequest`（全字段 `max_length` CR-H9，`brief` 用 `str|None` 令 codegen 可选）→ `ProjectPlanView`；**种子门控**缺 genre/logline→422，无凭据→503。复用 `build_brief_context`+`run_generator`（`run_generator`/`_build_request` 的 `project_id` 放宽 `uuid.UUID|None`——向导无 project，ledger.project_id 本 nullable）；新缝 `get_project_plan_gateway`（analyst）。**零迁移**。**前端** `wizard.ts` 加 `mapPlanResultToWizardForm`（书名/结构/基调→对应字段；时空背景/结局设计→折进 premise **不静默丢**；结局取向/视角预设枚举不臆造）+ `applyPlanPatch`（**仅回填空字段、保护已编辑**）+ `canGeneratePlan`/`toPlanSeedRequest` + `useProjectPlan` hook（renderHook 测试）+ `ProjectWizard.tsx` 第 2 步 `PlanAssistant`（生成→预览→按字段回填）。**门禁全绿**：后端 ruff/format 干净 · mypy **218** · pytest **814**（+新端点 6 + agents spec 8 测）· alembic 无漂移（零 ORM 变更）；前端 gen:api/lint/typecheck 干净 · vitest **526**（+28）· coverage 95%（wizard/generation 100%）· build OK。记 contracts C3 新端点。守不变量 #2/#3/#9。依赖 ④（已交付）。
- [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 竖井。
- [2026-06-20] UX计划 B0(可控版)+F1(控件) — **✅ 本章注入透明 可控版（分支 `feat/ux-b0-controllable-injection`，提交 `0d473e7` 后端 + `6449c31` 前端，未合 develop）**。在 B0 读端点之上加作者微调，守不变量 #6「确定性选择」的作者兜底。**后端**(@backend/@db)：`PUT /projects/{id}/chapters/{no}/injection` ← `InjectionOverrideRequest{pinned,excluded,recent_n(1..20→422)}` → `InjectionResponse`（新增回显 `pinned`/`excluded`，`selected[].reasons` 可含新值 **`author_pin`**）；`select_relevant_entities` 加 `pinned/excluded` frozenset 入参（pin 强制纳入加 author_pin / excluded 强制剔除且**优先于 pin**）；`assemble` 加 `override` 关键字参（recent_n 覆盖回看章数）；**GET injection + PUT + draft 流式端点同读同一覆盖**（看到的=写章用的）。持久化：**新表 `chapter_injection`**（迁移 `ad2c4c663daf`，唯一 `(project_id,chapter_no)`；复用 outline 行污染 beats 已否决）+ 新 `domain/injection_repo.py`（`InjectionOverride`/`EntityRef`/`SqlInjectionOverrideRepo`，upsert 只 flush 端点 commit）。**前端**(@frontend)：`gen:api` 纳入 PUT + 新 schema；`lib/workbench/injection.ts` override 纯函数（`currentOverride`/`withPinToggled`/`withExcluded`/`withRestored`/`withRecentN`/`isPinned`，钳 1..20）；`useInjection` 加 `saving` 态 + `togglePin/exclude/restore/setRecentN`（PUT 后以服务端确定结果为准、写失败可读文案不改本地态）；`ChapterAssistant` 每实体 📌置顶/✕排除 + 「近 N 章」步进器 + 「已排除」恢复区。**全仓门禁绿**：后端 ruff/format/mypy **163**/alembic 无漂移/pytest **476**；前端 lint/tsc/vitest **183**/build。记 contracts C3 再扩。**实景 browse 复验待做。** UX 计划余项：R1/R2/R3 审稿人体学 + Tier4。
- [2026-06-20] UX计划 M1+M2 — **✅ 移动端可用（纯前端，分支 `feat/ux-m1-m2-mobile` 提交 9b893a6）**。救活手机端（390px）写→审主路径。**M1 导航响应式抽屉**：抽出纯逻辑 `lib/nav/items.ts`（`GLOBAL_NAV_ITEMS`/`projectNavItems`/`isNavItemActive` + `ActiveNav` 类型，6 vitest），桌面静态侧栏与移动抽屉共用避免漂移；新增通用 `Drawer`（遮罩点击/Esc 关闭/打开聚焦面板，复用 `CommandPalette` a11y 模式，`lg:hidden`）+ `NavDrawer`（汉堡 `aria-label=打开导航` + 左抽屉）+ `NavItems`；`LeftNav` 改 `hidden lg:block`，`AppShell` 顶栏加汉堡。**M2 工具条不溢出 + 移动可达**：`Workbench` 工具条改 `flex-wrap`（390px 不再横切「尚未保存」）；`ChapterList`/`ChapterAssistant` 抽出 `ChapterListContent`/`AssistantContent`，`Workbench` 经左/右 `Drawer` 在 `<lg` 触达目录/助手。**实景 390px 复验（browse）**：汉堡显示 + 静态导航 `display:none`、抽屉开合（聚焦 + Esc 关闭）、工具条两行无横向溢出、助手抽屉真注入面板。前端门禁绿：lint/typecheck 干净 · vitest **177**(171→+6) · build OK。无后端/契约变更。文件：`lib/nav/{items,items.test}.ts`(新) · `components/{Drawer,NavDrawer,NavItems}.tsx`(新) · `components/{AppShell,LeftNav}.tsx` · `components/workbench/{Workbench,ChapterList,ChapterAssistant}.tsx`。**UX 计划余项**：B0 可控版(PUT override) + F1 控件 + R1/R2/R3 审稿人体学 + Tier4。
- [2026-06-20] UX计划 F3+F4 — **✅ 流式进度感 + 友好错误文案（纯前端）**。F4：新 `apps/web/lib/errors/messages.ts`（`friendlyError(code,fallback)`→中文文案 + 可选「去设置提供商」动作；覆盖后端 `ErrorCode` + 前端 `NETWORK/STREAM_FAILED`；未知码回退原 message；+4 vitest）；写作页 `Workbench` 与审稿页 `ReviewReport` 的错误块改用之，不再直接暴露 raw error code。F3：`Workbench` 工具条字数计数器流式时变「生成中… N 字」+ 朱砂 `motion-safe:animate-pulse`（尊重 prefers-reduced-motion），停后回「字数 N」；纯 `text.length`，无后端改动。前端门禁绿：lint/typecheck/vitest **171**/build。文件：`lib/errors/{messages,messages.test}.ts` · `components/workbench/Workbench.tsx` · `components/review/ReviewReport.tsx`。无契约变更。
- [2026-06-20] UX计划 B0(读)+F1 — **✅ 本章注入透明（读端点 + 右栏真面板）**。兑现差异化信任牌「看到的=写章用的」（不变量 #6）：把先前写作页右栏「M1 暂未接 / M2 开放」的过时假占位换成真数据。**后端**(@backend)：`GET /projects/{id}/chapters/{no}/injection` → `InjectionResponse{project_id,chapter_no,selected:[{kind,name,reasons[]}],recent_n}`，调既有 `assemble()` 回放确定性 `SelectionTrace`（**无 LLM、无 commit、无 DDL**）；项目不存在→404，无大纲→`selected:[]`。文件：`schemas/injection.py`(新) · `routers/projects.py`(+GET+2 import) · `tests/test_injection.py`(新,3 测)。**前端**(@frontend)：`gen:api` 纳入端点；纯逻辑 `lib/workbench/injection.ts`(理由/类型→中文徽标 + 4 vitest) + `useInjection` 读 hook；`ChapterAssistant` 改 client 组件列出选中实体 + 入选理由徽标（本章点名/主角常驻/近章出现/伏笔窗口）+ 空/载/错三态，「四审」段改指向审稿页；`Workbench` 传 `projectId/chapterNo`。**全仓门禁绿**：后端 ruff/format/mypy **159 Success**/alembic 无漂移/pytest **454 passed**；前端 lint/typecheck/vitest **167 passed**/build。记 contracts C3扩。**B0 余项（可控版：PUT override pin/排除/recent_n + selection 加参 + draft 同读 override + 持久化）+ F1 可控控件待后续。**
- [2026-06-20] @frontend — **✅ 接上两个新读端点：大纲页 / 写作工作台重访时回显已存内容**。`pnpm gen:api` 纳入 `GET .../outline` + `GET .../chapters/{no}/draft` + schema `DraftView`；`lib/api/types.ts` 加 `DraftView` 别名。新增两个 RSC 读 helper（仿 `fetchForeshadow`/`fetchProject` 服务端读、无 CORS）：`fetchOutline(projectId)`→`OutlineChapterView[]`（200 解包 chapters，**任何错误/空降级为 `[]`** 不阻塞进页），`fetchDraft(projectId,chapterNo)`→`DraftView|null`（复用 `getJsonOrNull`，**404→null 当空编辑器**，不抛）。**大纲页**(`outline/page.tsx`) 进页 `fetchOutline` 作 `initialChapters` 种入 `OutlineEditor`（`useOutline` 已据非空初值置 `ready`）→ 重访显已生成大纲；生成流照常覆盖所显章节。**写作工作台**(`write/page.tsx` + `Workbench`) 进页 `fetchDraft` 拿 `content` 作编辑器初值（`useState(initialText)`）→ 重访重载已写正文；404/新章 → `""` 空编辑器；**SSE 流式写 + AbortController + PUT 自动保存全不动**（流起始空、仅 streaming/done/aborted 变化才覆盖，初值不反扑活跃流）；导出 `WORKBENCH_CHAPTER_NO`(原 `M1_CHAPTER_NO`)供页面拉对应章草稿。新增 `lib/api/server.test.ts`(5 vitest，mock fetch 验解包/降级/404→null)。前端门禁全绿：gen:api / lint / typecheck / vitest **160**(155→+5) / build。文件：`lib/api/{types,server,server.test}.ts` · `app/projects/[id]/{outline,write}/page.tsx` · `components/workbench/Workbench.tsx`。消费 contracts C3扩（@backend 两读端点）。
- [2026-06-20] @backend — **✅ `GET /projects/{id}/chapters/{no}/draft` 草稿读端点（补缺口，镜像 GET /outline）**。写作工作台先前只有 `POST .../draft`(SSE 流写)+`PUT .../draft`(自动保存) 无读端点 → 重访工作台编辑器空白、看似「未保存」（其实 `chapters` 行在库）。补对称读侧：`GET /projects/{id}/chapters/{no}/draft` → 200 `DraftView{project_id,chapter_no,volume,status,version,content,length}`（**比 PUT 的 `DraftResponse` 多 `content`**——读侧需正文重建编辑器）；无草稿行/正文空白 → **404 `NOT_FOUND`**（工作台据此呈现空编辑器，仿其它缺资源端点）。**直接复用既有 `chapter_repo.get_draft`**（同续审 `_resolve_review_draft` 读缝，`ChapterDraftView` 已含全字段，无新 repo 方法/view）；只读不写库；多版本固定返草稿版次(`DRAFT_VERSION=1`)。文件：`schemas/projects.py`(+`DraftView`) · `routers/projects.py`(+GET endpoint+import) · `tests/test_projects.py`(+2：回灌正文 / 无草稿 404)。**全仓门禁绿**：ruff/format 干净、mypy **154 Success**、alembic **无漂移**、pytest **444 passed**（442→+2）。OpenAPI 新增 1 端点 → **@frontend 须 `cd apps/web && pnpm gen:api`** + 工作台初次加载拉已保存草稿回灌编辑器。记 contracts C3扩。
- [2026-06-20] @backend — **✅ `GET /projects/{id}/outline` 大纲读端点（补缺口，仿 Codex 读端点）**。大纲页先前只有 `POST .../outline`（生成+落库）无读端点 → 重访页面已落库大纲不回显、看似「未保存」。补对称读侧：`GET /projects/{id}/outline` → 200 `OutlineResponse{chapters:[OutlineChapterView{no,volume,beats:list[str],foreshadow_windows}]}`（与 POST **同形**，按 chapter_no 升序）；项目存在无大纲 → **200 空列表**（非 404）；项目不存在 → 404 `NOT_FOUND`。**复用 C5 读侧**：扩 `OutlineRepo`(domain) + `SqlOutlineRepo`(assemble 读侧) 加 `list_for_project`（仿 Character/WorldEntityRepo，order_by chapter_no，既有 `get` 不动）；新注入缝 `get_outline_read_repo`(仿 `get_rules_read_repo`)。beats DB JSONB `{"beats":[...]}` → 解包裸 `list[str]`（同 POST）。文件：`routers/outline.py`(+GET) · `services/project_deps.py`(+dep) · `domain/repositories.py`(+protocol method) · `memory/sql_repositories.py`(+impl) · `tests/{test_outline.py(+3),fakes_projects.py(+FakeOutlineReadRepo)}` + 补 2 既有 outline fake（test_memory/test_projects）满足扩展后的 protocol。**全仓门禁绿**：ruff/format 干净、mypy **154 Success**、alembic **无漂移**、pytest **442 passed**（439→+3）。OpenAPI 新增 1 端点 → **@frontend 须 `cd apps/web && pnpm gen:api`** + 大纲页初次加载拉已持久化大纲。记 contracts C3扩。

- [2026-06-19] @llm — **K1.2 修头校正：恢复 opencode 规范的完整 7 头（UA `KimiCLI/1.37.0`）**。先前误信 `picassio/pi-kimi-coder`（UA-only）把头集裁成仅 UA，是分歧/错误参考导致的误修——真源改为 `ooojustin/opencode-kimi`（`src/headers.ts`+`src/constants.ts`，1:1 镜像 kimi-cli v1.37.0，已重新拉取 master 核对）。`kimi_code_headers()` 现发 7 头：`User-Agent=KimiCLI/1.37.0` + `X-Msh-Platform=kimi_cli`（字面常量）+ `X-Msh-Version=1.37.0` + `X-Msh-Device-Name`（hostname ASCII 化）+ `X-Msh-Device-Model`（`kimi_device_model()`：macOS=`macOS {mac_ver} {machine}`）+ `X-Msh-Os-Version`（`platform.version()`）+ `X-Msh-Device-Id`（稳定 32 位无连字符 `uuid4().hex`：env `KIMI_DEVICE_ID` 优先→否则持久化 `~/.kimi/device_id`）。恢复 `kimi_device_id()`/`kimi_device_model()` 助手 + 重加 imports（os/uuid/socket/platform/pathlib）+ `__init__` 重导出。文件：`packages/llm_gateway/ww_llm_gateway/adapters/kimi_code.py` + `__init__.py` + 两测（断全 7 头键 + 固定字面值 + device-id 32 位小写 hex 稳定 + host 派生值非空 ASCII，env 注入保 CI 确定）。更 contracts C1扩（7 头 + opencode 真源）+ gotcha（pi-kimi-coder 分歧→回退）。门禁绿：ruff/format 干净、mypy 干净（27 文件）、pytest **60 passed**（packages/llm_gateway）。无 OpenAPI 形变。
- [2026-06-19] @backend — **K1.3 修正：device authorization 重新带 `scope=kimi-code`**（实测 401 后修）。K1.5 联网实测登录拿到真 JWT 但调 `api.kimi.com/coding/v1` 回 `401 Invalid Authentication`——根因 token 缺 coding entitlement（省略了 scope）。`start_device_authorization` 请求体加 `scope=kimi-code`（=`{"client_id":<id>,"scope":"kimi-code"}`，模块常量 `KIMI_CODE_SCOPE`）；scope 只放 device-auth，token 交换/刷新不带。文件：`apps/api/ww_api/services/kimi_oauth.py` + 单测 `test_kimi_oauth_service.py`（`test_start_device_authorization_parses_response_and_sends_scope` 断 `data["scope"]=="kimi-code"`）。修正 C3扩 contract + decision「省略 scope」表述 + 加 gotcha。无 OpenAPI 形变，前端无需 re-gen。门禁绿：ruff/format 干净、mypy 干净、pytest **101 passed**（apps/api）。
- [2026-06-19] @orchestrator — **🎉 K1 完成 + 独立复核（Kimi Code OAuth 订阅 plan 接入）**。波次 A(K1.1 @db 加列+迁移 ‖ K1.2 @llm KimiCodeAdapter)→B(K1.3 @backend OAuth device 服务+端点+store+接线)→C(K1.4 @frontend 连接流+可编辑路由)→D(K1.5 @qa E2E) 全绿，契约先行、零返工、无新 bug。研究先行（device flow at auth.kimi.com、client_id、伪造 UA/X-Msh-*、coding base、kimi-for-coding、access~15m/refresh~30d）+ 对照 pi-kimi-coder/ooojustin/lemon07r 校验（省略 scope 匹配 kimi-cli v1.41.0+）。最终独立复跑确认：后端 ruff/format 干净、mypy **154 文件**、alembic **无漂移**(首个新迁移=provider_credentials 加 auth_type/oauth_enc)、pytest **434 passed**；前端 vitest **155**/build。能力闭环：设置页 device-flow 登录→token Fernet 加密入库(`oauth_enc`，不泄漏)→路由 `kimi-code:kimi-for-coding`→网关构 KimiCodeAdapter(伪造头+bearer)→临过期自动刷新。**封号风险用户已知情接受**；与既有 API-key provider `kimi` 分离可切换。**唯一未尽**：对真 auth.kimi.com/api.kimi.com 的真账号联网冒烟（含封号风险，用户自担）——代码/E2E 仅验逻辑零联网。
- [2026-06-19] @qa — **K1.5 ✅ K1 E2E（Kimi Code OAuth device-flow 全 mock → 真 pg）**。新增 `tests/test_k1_kimi_oauth_e2e.py`（4 用例，零联网，真 pg）：① `POST .../oauth/start`→202 `{job_id,user_code,verification_uri,interval}`，fake httpx 跑 device-auth + token(pending×2→成功) 全程；后台 `run_job` 轮询跑在**真 e2e_sm session**（override `get_session_factory`→e2e_sm + monkeypatch 模块级 `kimi_oauth._default_http_client`→同一 scripted fake + `kimi_oauth.asyncio.sleep`→no-op；端点 device-auth 走 `_default_http_client` dep 同样指 fake）→ `GET /jobs/{id}` done，result `{connected:true,provider:"kimi-code"}`；**DB 真源**：`provider_credentials` 行 `auth_type="oauth"`/`oauth_enc` 非空/`api_key_enc` null，密文不含明文 token + 解密回环 access/refresh；`GET .../oauth/status` connected 无 token。② 存 oauth + `tier_routing` writer→`kimi-code:kimi-for-coding` → `build_gateway_for_tier` → `KimiCodeAdapter`，其 `_client` 带 coding base `https://api.kimi.com/coding/v1` + UA `KimiCLI/1.5` + 全 X-Msh-* + `api_key`(=bearer)=存量 access token。③ 临近过期 oauth 包 → `_build_provider_adapter` 触发刷新（fake `project_deps.kimi_refresh` + 字符串 target monkeypatch `project_deps.httpx.AsyncClient`）→ 适配器用新 token + `oauth_enc`(DB 真源)更新为新包。④ `POST .../oauth/disconnect`→凭据行清除（DB 真源无 oauth）。**token 全程负向断言：不进 job 结果/状态响应/start 响应/密文明文**。全仓门禁绿：ruff/format 干净、mypy **154 Success**、alembic 无漂移、pytest **434 passed**（430→+4）。memory 记 1 gotcha。**K1（Kimi Code OAuth 订阅 plan 接入）全 5 子任务 ✅，可封板。⚠️ 仍未对真 auth.kimi.com/api.kimi.com 联网验证（含封号风险，用户自担）。**
- [2026-06-19] @frontend — **K1.4 ✅ 设置页 Kimi Code OAuth 连接流 + 可编辑档位路由**。`pnpm gen:api` 纳入 K1.3 的 3 端点（OAuthStart/Disconnect/StatusResponse），`lib/api/types.ts` 加 3 别名。纯逻辑（node-env vitest）：`lib/settings/kimiOauth.ts`（`connectPhase` 状态机 idle/awaiting/connected/error + `toDeviceDisplay`/`authOpenUrl`/`toConnectionStatus`/`jobConnected`/`formatExpiresAt`，16 测）+ `lib/settings/providers.ts` 扩（`KNOWN_PROVIDERS` 加 `kimi-code`(auth:oauth, model:kimi-for-coding) + `API_KEY_PROVIDERS`/`TIERS`/`defaultModelFor` + 路由草稿纯函数 `toRoutingDrafts`/`applyProviderChange`/`draftsToRoutingInput`，9 测）。组件：`useKimiOauth` hook（**复用 `useJobPoll`**：connect=POST start→展示 user_code+`window.open` 授权页+轮询 job→done 拉 status；disconnect=POST→复位）+ `KimiCodeOauth.tsx`（连接区，user_code 用 `<output select-all tabIndex=0>` 可读可选可聚焦 + motion-safe）。`ProvidersSettings.tsx` 改：档位路由从只读→**可编辑**（per-tier select provider + model input + 保存 PUT），API-key 凭据行只列 `API_KEY_PROVIDERS`（OAuth 提供商有独立连接区，无 key 输入），挂入 `KimiCodeOauth`。`server.ts` 加 `fetchKimiOauthStatus`，`page.tsx` `Promise.all` 拉 providers+oauth 状态传 `kimiOauth` prop（进页即显连接态）。**job 结果无 token，只读 connected**。前端门禁全绿：gen:api / lint / typecheck / vitest **155 passed**（130→+25）/ build。memory 记 1 gotcha。**⚠️ 运行时对真 auth.kimi.com 设备授权 + 真 api.kimi.com 调用验证留 K1.5/手动（含封号风险，用户自担）。**
- [2026-06-19] @backend — **K1.3 ✅ Kimi Code OAuth device-flow 服务 + 端点 + store + 网关接线（C3 扩稳定）**。新增 `apps/api/ww_api/services/kimi_oauth.py`（`start_device_authorization`/`poll_token`/`refresh`，注入 `AsyncHttpClient` Protocol 零联网；`TokenSet`+`encrypt_oauth_bundle`/`decrypt_oauth_bundle` 复用 Fernet；`needs_refresh` 300s 缓冲；client_id env `KIMI_CLIENT_ID` 可覆盖、device authorization 省略 scope）+ `routers/kimi_oauth.py`（`POST .../oauth/start`→202 `{job_id,user_code,verification_uri,...}`+ BackgroundTask `run_job` 后台循环轮询→成功存加密 oauth_enc+job done(结果只 `{connected,provider}`)；`POST .../oauth/disconnect`→`{disconnected}`；`GET .../oauth/status`→`{connected,expires_at}` 无 token）+ `schemas/kimi_oauth.py`（3 schema）+ 注册 main.py。`services/credentials.py`：`StoredCredential` 加 `auth_type`/`oauth_enc`、`api_key_enc`→`bytes|None`（**K1.1 的 2 处 mypy red 消除**），`SqlCredentialStore` 读新列 + `upsert_oauth_credential`/`delete_credential`，`upsert_credential` 显式置 api_key 态。`services/project_deps.py`：`_build_provider_adapter` 对 `kimi-code`/`auth_type=oauth` 走 `_resolve_kimi_code_token`（解密→临近过期则经 httpx 刷新+持久化→access token 喂 `build_adapter("kimi-code",…)`）；`provider_deps.py` `_PROVIDER_BASE_URLS` 加 `kimi-code`。**token 全程不进 job 结果/日志/响应**（仅 Fernet 密文存 oauth_enc）。TDD 先红后绿：`tests/test_kimi_oauth_service.py`(11) + `test_kimi_oauth_endpoints.py`(5) + `test_kimi_code_wiring.py`(3)（fake httpx 零联网）。**全仓门禁绿**：ruff/format 干净、mypy **154 源文件 Success**、alembic **无漂移**、pytest **430 passed**（400→+30）。OpenAPI 新增 3 端点 + 3 schema → **@frontend K1.4 须 `pnpm gen:api`**。memory 记 C3 扩（K1.3）+ 1 decision + 2 gotcha。**⚠️ 真 auth.kimi.com/api.kimi.com 联网验证（含封号风险）留 K1.5，用户自担。**
- [2026-06-19] @llm — **K1.2 ✅ Kimi Code 订阅 plan 适配器（C1 扩稳定）**。`packages/llm_gateway/ww_llm_gateway/adapters/kimi_code.py`：`KimiCodeAdapter` **子类化 `OpenAICompatAdapter`**（coding 端点 OpenAI 兼容，复用 complete/stream/结构化/瞬时错误包装/capabilities），provider 固定 `kimi-code`（与 API-key `kimi` 分离）；`build_kimi_code_client(access_token, base_url?)` 构 `AsyncOpenAI(api_key=access_token, base_url="https://api.kimi.com/coding/v1", default_headers=kimi_code_headers())`——access_token 当 bearer，伪造头随客户端发。`kimi_code_headers()`：`User-Agent` + 全 `X-Msh-*`(Platform/Version/Device-Name/Device-Model/Os-Version/Device-Id)。`kimi_device_id()`：env `KIMI_DEVICE_ID` 优先，否则确定性 `uuid5` 缺省（**绝不每次随机**，稳定且来源可测/可配）。model `kimi-for-coding` **不硬编码**（经 `.complete(req, model)` 传入，路由关注点）。`factory.build_adapter` 加 `provider=="kimi-code"` 分支；包 `__init__` 导出全部符号。**契约校正（已对照 `github.com/picassio/pi-kimi-coder` `extensions/index.ts`）**：参考实现对 coding API **仅**设 `User-Agent: "KimiCLI/1.5"`（精确值，已采用）、**未**设 X-Msh-* 头——与 PROGRESS 契约「缺 X-Msh→403」不符；本实现以验证到的 UA 为准 + 附带 X-Msh-* 客户端标识头（真客户端会发、额外标识无害、最大化避免 403）；**真 api.kimi.com 联网验证头集是否足够留 K1.3/K1.5（含封号风险，用户自担）**。TDD 先红后绿；`tests/test_kimi_code_adapter.py`(8) + `tests/test_kimi_code_factory.py`(3)，不联网（断 base_url + default_headers + 稳定 device_id + 工厂分派）。门禁绿（隔离 `MYPY_CACHE_DIR=.mypy_cache_k12` 已清理）：ruff/format 干净、mypy **27 源文件 Success**、pytest **58 passed**（47→+11）。**@backend 行动（K1.3）**：`_build_provider_adapter` 对 provider `kimi-code` 调 `build_adapter("kimi-code", api_key=<刷新后的 access_token>, base_url=…)`；token 刷新/获取归 K1.3。**无 OpenAPI 形变**，前端无需 re-gen。memory 记 C1 扩（K1.2）契约 + 1 gotcha。
- [2026-06-19] @db — **K1.1 ✅ `provider_credentials` 加 OAuth 列（C2 扩稳定）**。`packages/db/ww_db/models.py` `ProviderCredential`：① 加 `auth_type: Mapped[str]`(`Text, nullable=False, server_default="'api_key'"`，值 api_key|oauth)；② 加 `oauth_enc: Mapped[bytes|None]`(`LargeBinary, nullable=True`，持 Fernet 加密 JSON `{access_token,refresh_token,expires_at}`)；③ `api_key_enc` 改 `nullable=True`(`Mapped[bytes|None]`，OAuth-only 行无 api_key、既有 api_key 行保值)；`UniqueConstraint(owner_id,project_id,provider)` 不变。**首个新迁移**（M1–M5 全零建表）`migrations/versions/1f011c42bd4d_provider_credentials_oauth_columns.py`（down_revision=`220ca2e3d53f`）：up=add_column auth_type/oauth_enc + alter api_key_enc→nullable，down 逆向；autogenerate 仅这 3 op 无 noise，手修模板重复 import + 改本仓 ruff 样式（见 gotcha）。`upgrade head` 已应用、`alembic check` **无漂移**。门禁：ruff/format 干净、pytest **400 passed**、alembic 无漂移；**mypy 余 2 处 `arg-type` 错（均在 @backend `apps/api/ww_api/services/credentials.py`，因 api_key_enc 改 nullable 打穿，K1.3 吸收，见下方 @backend 请求）**。memory 记 C2 扩 + 2 gotcha。
- **[请求 → @backend，K1.3]**：K1.1 把 `provider_credentials.api_key_enc` 改 nullable（`bytes|None`），打穿到你拥有的 `apps/api/ww_api/services/credentials.py` mypy 报 2 处 `arg-type`（`StoredCredential.api_key_enc: bytes` ↳ 现 `bytes|None`，dataclass line 28 + store line 75/97）。@db 不越权改 apps/api。K1.3 接 OAuth store 时把 `StoredCredential.api_key_enc` 改 `bytes | None`（并加 `auth_type`/`oauth_enc` 字段 + OAuth 读写路径），mypy 即全绿。oauth_enc 持 Fernet 加密 JSON `{access_token,refresh_token,expires_at}`（key=`settings.credential_enc_key`，同 api_key_enc）。

- [2026-06-19] @orchestrator — **🎉 M1–M5 全部交付 + M5 余项收尾完成**。五个里程碑（Phase 0–5）全部 ✅；M5 三项非阻塞余项 + 本次台账归档共四项 follow-up 已清：**#1** Codex 读端点（`GET /projects/:id/characters` + `GET .../world_entities`，@backend）+ 前端接线（CodexPage 拉全量真源，去「会话内回显」局限，@frontend）；**#2** `build_adapter` provider→适配器工厂 + anthropic mypy 修复（@llm），`build_gateway_for_tier` 按 provider 选适配器启用 Anthropic/Gemini 真实回退（@backend）；**#3** `packages/skills` 纳入 uv workspace + skill impl（registry/permissions）从 `ww_core.domain` 迁入 `ww_skills`（@backend，@devops 加 SDK）；**#4** 本次把 M5/M4/M3 三段折叠进「已归档阶段」、顶部改短状态行（@orchestrator）。最终门禁全绿：后端 ruff/format 干净、mypy **144 文件**、alembic **无漂移**、pytest **400 passed**；前端 lint/typecheck 干净、vitest **130 passed**、build OK。台账已归档，当前无活跃阶段。
- [2026-06-19] @frontend — **M5 follow-up #1 ✅ Codex 展示真源（解余项①）**（消费 @backend C3 扩 Codex 读端点，无契约变更）。先 `pnpm gen:api` 纳入 `GET /projects/:id/characters`（`CharacterListResponse{characters?}`）+ `GET .../world_entities`（`WorldEntityListResponse{world_entities?}`）+ `lib/api/types` 加 2 别名。`lib/api/server` 加 `fetchCharacters`/`fetchWorldEntities`（200 空列表非 404，仿 `fetchForeshadow`）；codex route（Server Component）`Promise.all` 拉 项目+人物+世界观全量 → 传 `CodexPage` 初始数据。`CodexPage` 人物 tab 渲染**跨会话持久化真源**（刷新不再空），本会话新入库卡经纯函数 `mergeCharacterCards`（按 name 去重、持久化优先、不重复）合并补显（generate→ingest 后仍即时见新行）；世界观 tab 渲染入库真源（本期无世界观入库端点，故只真源 + 生成器预览，无 session 合并）；时间线维持 P2 占位。纯逻辑 `lib/generation/cards.{mergeCharacterCards,mergeWorldEntities}` + 5 node-env 单测。前端门禁全绿：gen:api/lint/tsc/**vitest 130**（125→+5）/build。**Codex「会话内回显」局限已去除**（解 gotcha + PROGRESS 余项①）。memory 把 Codex 缺口 gotcha 标 RESOLVED。
- [2026-06-19] @backend — **M5 follow-up R1 + R3 ✅**（三 fix，与并行 @llm `llm_gateway` 改动 disjoint）。**R1-Codex 读端点**（解余项①）：加 `GET /projects/:id/characters`（`CharacterListResponse`）+ `GET /projects/:id/world_entities`（`WorldEntityListResponse`），复用 C5 读侧 `SqlCharacterRepo`/`SqlWorldEntityRepo`（经 `get_memory_repos`，不动 assemble）+ 反向 JSONB 解包（`{"items":[...]}`→list/`{"text":...}`→str/`{"rules":[...]}`→list），register main；C3 扩稳定。**R3-skill 迁包**（解余项③）：`skill_registry.py`+`skill_permissions.py` 从 `ww_core.domain` **迁入** `packages/skills/ww_skills/`（`ww_skills/__init__` 改直接导出，删门面）；importer（`generation.py`/`project_deps.py`/`test_generation.py`）改 `from ww_skills import …`；skill 单测移 `packages/skills/tests/`（13 测）；删 ww_core.domain 两模块+__init__ 导出；`packages/skills/pyproject.toml` deps 校准（去 `ww-core`、补 `ww-llm-gateway`+`sqlalchemy`），`uv sync` 重建。**R1#2-build_adapter 接线**（解余项②的 apps 侧；@llm 的 `build_adapter` 工厂 + @devops 的 anthropic/google-genai SDK 均已落地，import 实解非 transient）：`build_gateway_for_tier` 的 `_build_provider_adapter`（原 `_build_openai_compat_adapter`）改调 `ww_llm_gateway.build_adapter(provider, api_key=…, base_url=…)`——Anthropic/Gemini 传 base_url=None 走原生适配器，去掉直接 `AsyncOpenAI`/`OpenAICompatAdapter` import。门禁绿（隔离 `MYPY_CACHE_DIR=.mypy_cache_r1backend` 已清理）：ruff/format 净、mypy **101 文件 0 error**、pytest `packages/core packages/skills apps/api` **246 passed**（226→+20：+10 迁移 skill 测 +3 Codex 读端点测 + 其余）。两新端点入 OpenAPI（验证 get 存在）。memory 登记 C3 扩（Codex 读端点）+ C8 实现位置更新 + build_adapter 接线 + 3 decisions。**@frontend 行动**：`cd apps/web && pnpm gen:api` 纳入 `GET characters`/`GET world_entities`，CodexPage 初始列表拉全量去掉「会话内」局限（解 gotcha Codex 缺口）。**@devops 行动**：复核根 `uv.lock`（@backend 改了 `packages/skills/pyproject.toml` deps，已 `uv sync` 重建 ww-skills）。**M5 三非阻塞余项①②③ 至此全部清除。**
- [2026-06-19] @llm — **R2 follow-up #2 ✅ 真多 provider 适配器选择**（C1 扩 follow-up #2 稳定；接 @devops 已加 `anthropic`+`google-genai` SDK 之后）。① **修真 SDK 装上后的 mypy 报错**：`instructor.from_anthropic(self._client)` 重载只收 SDK 具体 `AsyncAnthropic|...`、`AnthropicClient` Protocol 不匹配 → adapter 内 `cast("AsyncAnthropic", self._client)`（`TYPE_CHECKING` import，零运行时硬依赖）选中 AsyncInstructor 重载。gemini 走 `response_schema` 非 instructor，**无**该类错。② **新增 provider→适配器工厂** `factory.build_adapter(provider, *, api_key, base_url=None) -> ProviderAdapter`（包 `__init__` 导出）：`anthropic`→`AnthropicAdapter`、`{gemini,google}`→`GeminiAdapter`、其余→`OpenAICompatAdapter(provider, AsyncOpenAI(...))`；从 api_key 构真实客户端（factory 处 `cast` 跨 Protocol read-only-attr 约束，adapter 仍声明 Protocol 形参、测试替身缝完好）。新增 `tests/test_build_adapter_factory.py` 6 测（按 provider 名断分派，不联网）。**门禁绿**（隔离 cache）：ruff/format 净、mypy **24 源文件 0 错**、pytest **47 passed**（41→+6）；全仓 `mypy packages apps` **144 文件 Success**（anthropic.py 报错确认消除、gemini 无同类错）。无 OpenAPI 形变（served_by 不出 API），前端无需 re-gen。**@backend 可在 `build_gateway_for_tier` 用 `build_adapter` 替换「一律 OpenAICompatAdapter」启用 anthropic/gemini 真实回退。** 记 1 gotcha（Protocol vs 真 SDK cast 边界）。
- [2026-06-19] @orchestrator — **🎉 M5 完成 + 独立复核 ✅**（Wave C：T5.3+T5.6 ✅ + Wave D：T5.7 ✅）。波次 A(T5.1‖T5.4‖T5.5)→B(T5.2)→C(T5.3+T5.6)→D(T5.7) 全绿，契约先行、零返工、无新 bug。最终独立复跑确认：后端 ruff/format 干净、mypy **142 文件**、**alembic 无漂移**（M5 复用 T0.2 既有 world_entities/characters/rules/skills 表，全程零建表迁移）、pytest **391 passed**；前端 tsc 干净、**vitest 125 passed**。**M5（生成+多provider+扩展）出口 DoD 全达成**：worldbuilder/character-gen 生成→预览→入库（continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变）；网关多 provider 回退/熔断/能力降级（真 Gateway 回退链，E2E 验主失败→fallback 服务 + 记账记实际 provider）；Skill registry + 表权限沙箱 + 规则页/技能库/命令面板(⌘K)。**M1–M5 全部交付。** 非阻塞余项：① Codex 读端点缺口（无 GET characters/world_entities，现以生成+会话内回显管理）；② Anthropic/Gemini 真实回退待 @devops 加 SDK + 按 provider 选适配器类（E2E 用 OpenAI 兼容 mock 验证回退）；③ `packages/skills` 纳入 uv workspace（现 impl 落 ww_core.domain + façade）。建议下一步：清理 PROGRESS 把 M3/M4/M5 折叠归档，或处理上述余项。
- [2026-06-19] @qa — **M5 Wave D：T5.7✅ M5 E2E + 切 provider**（真 pg + mock 多 provider，零 token；无契约变更）。`tests/test_m5_e2e.py` 3 用例：**① 群像生成→入库闭环**（mock 网关按 `req.output_schema` 路由 CharacterGenResult/WorldGenResult/ContinuityReview）：`POST /world/generate` 预览 + `POST /characters/generate` 预览 3 卡 → `POST /characters` 入库**双 gate**——precheck 有冲突且未 `acknowledge_conflicts` → **409 CONFLICT_UNRESOLVED**+`details.conflicts`（并断言此刻 characters 表无行，守不变量 #3）→ 带 `acknowledge_conflicts=true` 重发 201；**DB 真源**断言 characters 行真落 pg + **schema→JSONB 形变**（traits/speech_tics→`{"items":[...]}`、arc→`{"text":...}`、tags/relations 直落 list），`rejected_tables` 空（partition_writes 合法路径），4 条 ledger。**② 切 provider 回退**：HTTP 角色生成跑在**真 `Gateway`**（非 stub）deepseek(每次 `complete` 抛 `TransientProviderError` 耗尽重试)→openai 回退链上，端点仍 200；`served_by` 不出 API，故经 **DB 真源 `usage_ledger`** 断言实际服务方 = **openai(fallback)≠deepseek(primary)**（ARCH §4.5 记账记实际服务方）。**③ 能力降级**：`structured_output=False` 唯一 provider 直测 `Gateway.run` → `served_by.degraded=True`（§4.4）。坑：生成网关是**请求 scope 依赖**（`get_worldbuilder_gateway`/`get_character_gen_gateway`/`get_precheck_gateway`），直接 `app.dependency_overrides` 注假网关即可，**无需** monkeypatch `build_gateway_for_tier`（与 M4 学文风后台自建网关不同）。全仓门禁绿：ruff/format 净、mypy **142 文件**、**alembic 无漂移**、pytest **391 passed**（388→+3）。**未发现 REAL bug**。**M5 出口 DoD 全达成** → orchestrator 可封 M5。
- [2026-06-19] @frontend — **M5 Wave C：T5.3+T5.6✅ 生成 + 设定库 + 规则 + 技能 + 命令面板**（消费 C3 扩 T5.2 + C8/T5.5，无契约变更）。先 `pnpm gen:api` 纳入 5 端点（world/characters generate + characters ingest + GET rules + GET skills）+ `lib/api/types` 加 12 别名。**T5.3**：① **角色生成器**（UX §6.6）`components/generation/CharacterGenerator`：一句话 brief + 数量(1–12) + 定位 → `POST /characters/generate` 预览卡 → `CharacterCardItem`（勾选入库 + 就地编辑名/弧光，默认全选）→ `POST /characters` 入库；**409 CONFLICT_UNRESOLVED 裁决流**：`useCharacterGen` 捕 409 → `extractIngestConflicts` 收窄 `details.conflicts`(复用 ReviewConflict 形) → `ConflictAdjudication` 面板展示 continuity 冲突 → 作者「已知悉强制入库」= 带 `acknowledge_conflicts=true` 重发 / 「取消改卡」。② **世界观设计器** `WorldGenerator`：`POST /world/generate` 预览 type/name/rules（本期无入库端点，仅预览参考）。③ **设定库 Codex**（UX §6.5）`codex/CodexPage` + `app/projects/[id]/codex`：人物/世界观/时间线三 tab（时间线 P2 占位）；`?gen=character|world` 命令面板跳转直开对应 tab。**T5.6**：④ **规则页** `rules/RulesPage` + `app/.../rules`：四级规则列表(`groupByLevel`) + 新增(`useRules` 乐观追加+回滚，仿 useForeshadow)。⑤ **技能库** `skills/SkillsPage`（Server Component 纯读）+ `app/.../skills`：`GET /skills` 按 scope 分组只读注册表(name/tier/reads/writes)。⑥ **命令面板 ⌘K** `command/{CommandPalette,CommandPaletteMount}` + `lib/command/palette`：⌘K/Ctrl+K 开关、模糊过滤(`filterCommands`)、上下循环高亮(`moveHighlight`)、Esc/Enter，a11y(role=dialog/listbox/option + 自动聚焦 + prefers-reduced-motion)；挂 RootLayout 对全局生效，读 pathname 注入项目上下文。⑦ LeftNav `ActiveNav` 加 codex/rules/skills（替换原 disabled 设定库占位）。纯逻辑全单测（node-env）：`lib/{generation/cards,rules/rules,skills/skills,command/palette}.test`。前端门禁全绿：gen:api/lint/tsc/**vitest 125**(96→+29：cards 12 + rules 5 + skills 3 + palette 9)/build（codex/rules/skills 路由齐）。memory 记 2 gotcha + 1 gap（Codex 读端点缺口）。**M5 前端交付完成** → 下一步 **Wave D：T5.7(@qa) M5 E2E + 切 provider**。
- [2026-06-19] @orchestrator — **M5 Wave B 收口 ✅**（T5.2，C3 扩稳定）。5 端点入 OpenAPI：world/characters generate 预览 + characters 入库（双 gate：continuity 预检 409 + partition_writes 白名单 + schema→DB 形变）+ GET rules/skills；`build_gateway_for_tier` 接线回退链（OpenAI 兼容齐，Anthropic/Gemini 真实切换余量 deferred 给 T5.7 + @devops SDK）。全仓门禁绿：ruff/format 干净、mypy **142 文件**、**alembic 无漂移**、pytest **388 passed**（371→+17）。下一步 **Wave C：T5.3+T5.6(@frontend 单 agent 顺序做)**——先 `pnpm gen:api`，做 角色生成器 + 世界观设计器 + 设定库 Codex + 规则页 + 技能库 + 命令面板（⌘K），入库流处理 409 裁决重发。
- [2026-06-19] @backend — **M5 Wave B：T5.2✅ 生成/入库 API**（契约 **C3 扩稳定**；消费 Wave A 的 C6扩/C8/C1扩，与已合并 Wave A 无写冲突）。`routers/generation.py`（+ `skills_router`，已注册 main）落 5 端点：① `POST /world/generate`（worldbuilder writer 网关 `run_worldbuilder` → **预览**世界观，不入库，末尾 commit 落 ledger）；② `POST /characters/generate`（character-gen writer 网关，注入已有角色防雷同 → **预览**角色卡）；③ `POST /characters`（入库 **双 gate**：`precheck_generated_cards` continuity 预检 analyst 网关——有冲突且未 `acknowledge_conflicts` → **409 CONFLICT_UNRESOLVED**+冲突详情，作者裁决后放行；`partition_writes(character_gen_spec,...)` 白名单过滤越权写表；过 gate 写 `characters` 行，**schema list/str → DB JSONB dict 形变**）；④ `GET /rules`（规则列表，复用 C5 读侧）；⑤ `GET /skills`（技能库，经 registry）。新写侧 repo `ww_core.domain.{character_repo(traits/speech_tics→{"items"}/arc→{"text"}),world_entity_repo(rules→{"rules"})}`（只 flush，端点 commit）。**T5.4 follow-up 接线完成**：`build_gateway_for_tier` 据 DB `tier_routing.primary+fallback` 预备多 `OpenAICompatAdapter` + 注入 `chain_resolver=chain_from_routing(...)` 启用回退链；无路由行退回 `resolve_route`（单 provider，M1 兼容），无可用凭据→503。**仅 OpenAI 兼容 provider**（deepseek/kimi/qwen/glm/openai）——Anthropic/Gemini 真实回退需 @devops 加 SDK + provider_deps 据 provider 选适配器类（**余量 deferred 给 T5.7 切 provider**）。全仓门禁绿：ruff/format 净、mypy **142 文件**、**alembic 无漂移**（M5 复用 T0.2 既有表）、pytest **388 passed**（371→+17：13 生成/入库端点 + 4 网关回退接线 + 4 形变 helper）。新端点入 OpenAPI（验证 world/characters generate + POST characters + GET rules + GET skills 齐）。memory 登记 C3扩稳定 + 2 decisions（入库 gate / 形变）+ 2 gotchas。**@frontend(T5.3/T5.6) 行动**：先 `cd apps/web && pnpm gen:api` 纳入这 5 端点；入库流处理 409（展示冲突→裁决勾选→带 `acknowledge_conflicts` 重发）。下一步 **Wave C：T5.3+T5.6(@frontend)**（依赖 T5.2✅+T5.5✅，可起）。
- [2026-06-19] @orchestrator — **M5 Wave A 收口 ✅**（T5.1 + T5.4 + T5.5 三 agent 并行，disjoint 目录 + 隔离 mypy cache，零写冲突零返工）。worldbuilder/character-gen 双 spec + 节点 + 入库前 continuity 校验缝（C6扩）‖ 网关多 provider 韧性：Anthropic/Gemini 适配器 + 回退链 + 熔断 + 能力降级（C1扩，无 OpenAPI 形变）‖ Skill registry + 表权限白名单（filter_reads/partition_writes）+ `POST /rules`（C8 + C3扩）。全仓门禁绿：ruff/format 干净、mypy **135 文件**、**alembic 无漂移**（M5 复用 T0.2 已建的 world_entities/characters/rules/skills 表，无建表迁移）、pytest **371 passed**（296→+75）。**Wave B 衔接待办**（T5.2 @backend 须处理）：① 角色 schema `traits`/`speech_tics`(list)/`arc`(str) ↔ DB JSONB **dict** 形变，入库须转换；② 入库 gate 调 `precheck_generated_cards`(continuity 冲突拦截) + `partition_writes`(越权写丢弃审计)；③ 补 `GET /rules` + `GET /skills` 供 T5.6 前端；④ 接线 `build_gateway_for_tier`→`chain_from_routing` 启用回退链（T5.7 切 provider 依赖）。**@devops 待办**：加 `anthropic`/`google-genai` SDK 依赖（适配器懒 import，单测不需）；评估把 `packages/skills` 纳入 uv workspace（现 impl 落 ww_core.domain + ww_skills façade）。下一步 **Wave B：T5.2(@backend 生成/入库 API)**。
- [2026-06-19] @llm — **M5 Wave A：T5.4✅ 网关多 provider 韧性**（契约 **C1 扩稳定**，仅扩 `packages/llm_gateway/`，与并行 T5.1 @llm 无文件冲突）。① **新适配器**`adapters/anthropic.py`(`AnthropicAdapter`：原生结构化经 instructor.from_anthropic 懒构建、`cache_control` 缓存断点、thinking 能力)+`adapters/gemini.py`(`GeminiAdapter`：`response_mime_type=json+response_schema` 结构化、thinking)——**均经注入的客户端 Protocol，不硬 import anthropic/google-genai SDK，单测零联网零依赖**。② **回退链 + 重试 + 熔断**`gateway.py`：`Gateway` 加 `chain_resolver`/`max_retries`/`breaker`(保留 `resolver=` M1 兼容→自动包成单元素链)；沿链逐 provider→瞬时失败(tenacity 指数退避)重试 R 次→仍失败/熔断打开/无适配器→切下一个；首个成功者服务、非链首标 `served_by.fell_back`；链耗尽抛 `LLM_UNAVAILABLE`；记账记**实际服务方**(回退后不记主模型)；流式仅首块前可切(产出后中途失败上抛不重连)。`CircuitBreaker(threshold/reset_seconds/clock)` 连续失败超阈值短时熔断、成功清零、冷却半开。③ **能力协商/降级**：结构化请求按 `capabilities().structured_output` 重排链优先选支持者；链上无支持者→降级首个可用(适配器自走 instructor JSON 提示)标 `served_by.degraded`。④ **瞬时错误契约**`errors.py`：`TransientProviderError`(三适配器按异常类名+status_code 包装)+网关视 `AppError(RATE_LIMITED)` 可重试。⑤ **routing.py**：`resolve_chain`/`chain_from_routing`(据 DB tier_routing 去重保序)/`ChainResolver` 别名导出。⑥ `ServedBy` 加 `degraded`(向后兼容，默认 False)；`pyproject.toml` 加 `tenacity>=8.2`。**23 新测**(test_fallback_chain 10/test_capability_degradation 4/test_new_adapters 7/test_multi_provider_accounting 2)，包内 **41 passed**；全仓门禁绿：ruff/format 净、mypy **135 文件**、core+api **226 passed**(无回归)。**无 OpenAPI 形变**(served_by 不出 API，前端无需 re-gen)。memory 登记 C1 扩 + 2 gotcha。**后续待办**：apps/api `build_gateway_for_tier` 接线回退链(预备多适配器+传 chain_resolver=chain_from_routing 包装)、@devops 加 anthropic/google-genai SDK 依赖供真实接线。下一步 Wave B(T5.2 @backend，依赖 T5.1)。

- [2026-06-19] @backend — **M5 Wave A：T5.5✅ Skill 运行时 + 规则**（契约 **C8 + C3 扩稳定**；与并行 T5.1/T5.4 @llm 无目录/写冲突，隔离 `MYPY_CACHE_DIR=.mypy_cache_t55`）。① **Skill registry loader**（`packages/core/ww_core/domain/skill_registry.py`）：`SkillRecord`(frozen,skills 行声明快照) + `SkillRepo` Protocol + `SqlSkillRepo`（读 `skills` 表，裸 tier 非法→VALIDATION） + `SkillRegistry.load(repo)`（async，每条跑 `validate_declaration`，越权声明→AppError VALIDATION 加载中断；frozen `dict[name,AgentSpec]`）+ `get`/`names`/`list_scope`。复用 §5.1 网关机制（不重实现网关），不变量#2 只带 tier。② **表权限沙箱**（`skill_permissions.py`，纯函数/不可变，§5.6「声明式权限+apply 层白名单」非进程沙箱）：`KNOWN_TABLES`(10 创作表白名单) + `filter_reads`(注入只喂声明 reads 表) + `partition_writes`(写库只应用声明 writes，越权→rejected 供审计 log+丢弃) + `validate_declaration`(reads/writes 越界→VALIDATION)。守不变量#3（allowed 仍过验收 gate，不开写后门）。③ **`POST /projects/:id/rules`**（`routers/rules.py`，已注册 main）：`RuleCreateRequest{level:Literal[global/genre/style/project],content(min1)}`→**201** `RuleView{level,content}`，非法 level/空 content→FastAPI 422；写侧 `domain/rule_repo.py`(`RuleWriteRepo`/`SqlRuleWriteRepo`/`RuleWriteView`,只 flush,端点 commit)；喂 assemble 四级 `merge_rules`。④ 注入缝 `get_rule_write_repo`/`get_skill_registry`（`project_deps`）。门禁绿（限本任务范围）：ruff/format 净、`MYPY_CACHE_DIR=.mypy_cache_t55 mypy packages/skills packages/core apps/api` **0 error/94 文件**、pytest `packages/skills packages/core apps/api` **226 passed**（+18 新测：6 permissions + 4 registry + 2 rule_repo + 3 rules 端点 + 既有）。`POST /rules` 已入 OpenAPI（验证）。**实现位置决策**：registry/沙箱落 `ww_core.domain`（§5.6 强制点在编排/写库层 + `packages/skills` 当前非 uv workspace member，根 pyproject @devops 未接线）；`ww_skills` 留再导出门面，待 @devops 纳入 workspace 可迁移（见 decisions/gotchas）。**@frontend(T5.6) 行动**：先 `cd apps/web && pnpm gen:api` 纳入 `POST /rules`（规则页）；技能库经 `get_skill_registry`(若 T5.6 需 GET /skills 端点，请在此提请 @backend 补)。**@llm/T5.2 行动**：生成入库层用 `partition_writes` 守自定义 skill 产出白名单（仅声明 writes 表），仍过验收。

- [2026-06-19] @llm — **M5 Wave A 半程：T5.1✅**（C6 扩稳定，与并行 T5.4 @llm/T5.5 @backend 无目录/写冲突，隔离 MYPY_CACHE_DIR）。worldbuilder + character-gen 落地：① schema（`packages/agents/ww_agents/schemas.py`）`WorldEntityCard{type,name,rules:list[str]}`/`WorldGenResult{entities}` + `CharacterRelation{name,kind,note?}`/`CharacterCard{name,role,traits,backstory,arc,speech_tics,tags,relations}`/`CharacterGenResult{cards}`——**字段对齐 `world_entities`/`characters` DB 列**供 T5.2 ingest 直映。② spec（specs.py）`worldbuilder_spec`(writer,reads=projects,writes=world_entities,硬规则显式 prompt)、`character_gen_spec`(writer,reads=world_entities+characters,writes=characters,**system_prompt 注入「已生成卡+已有角色」差异化要求=M5-a 防雷同**)。③ 生成节点（`packages/core/ww_core/orchestrator/generation_node.py`，模块内自有 GatewayRun Protocol，不重复导出）`run_worldbuilder`/`run_character_gen`（`build_character_gen_context` 把已有+已生成卡序列化成 `name(role)：traits` 简表注入，空时占位不崩，独立生成、网关失败上抛、parsed 违约 ValueError，只读不写库）。④ **入库前 continuity 校验缝**（ARCH §6.5）`precheck_generated_cards(spec=continuity_spec,*,cards,world_context,characters_context,...)->list[Conflict]`——编排器追加的检查、**非 character-gen 直接互调**（守 §5.4/不变量#1），T5.2 入库端点调它做 gate。门禁绿（限本任务文件）：ruff/format 净、mypy 本任务 7 文件 0 error（`packages/agents packages/core` 余 3 error 全在并行 @backend T5.5 的 rule_repo/skill 测试）、pytest **246 passed**(+34 新测：22 spec 契约 + 12 节点/防雷同/校验缝)。memory 登记 **C6 扩稳定**。**T5.2(@backend) 行动**：`POST /world/generate`→run_worldbuilder、`/characters/generate`→run_character_gen(批量循环把已产卡传 generated_so_far)、`POST /characters` 入库前调 precheck_generated_cards gate；analyst/writer 网关复用 build_gateway_for_tier。下一步 **Wave B：T5.2**（依赖本任务✅，可起）。
- [2026-06-19] @docs — spec 一致性回写：把 T4.3 顺带新增的 `GET /projects/:id/style`（最新指纹 `{dimensions, evidence, version}`，无指纹 404）补进 ARCHITECTURE.md §7.2 端点表（紧邻 `POST /style` 行）+ PRODUCT_SPEC.md §6 端点清单（紧邻 `POST /style` 行），消除 code↔spec 漂移。`memory/decisions.md` 该决策的「待回写规格」flag 至此**已解决**。仅改两份 spec，未动代码/测试。
- [2026-06-19] @orchestrator — **M4 收官独立复核 ✅**。波次按计划走通 A(T4.2‖T4.1)→B(T4.3)→C(T4.4)→D(T4.5)，每波契约先行 + 全仓门禁收口、零返工、无新 bug。最终独立复跑后端门禁确认绿：ruff/format 干净、mypy **115 文件**、**alembic 无漂移**（M4 全程零建表迁移，全靠 T0.2 预留的 `style_fingerprint`/`chapter_reviews.style`/`jobs` 列表）、pytest **296 passed**；前端 gen:api/lint/tsc/vitest **96**/build 绿。**M4（文风）出口 DoD 全达成**：学文风(16 维指纹带证据,版本化,异步 jobs+zombie reaper) + 漂移打分作为第四审真持久化(review 四并行分支+SSE `style`+`chapter_reviews.style`) + 回炉同步 diff(不写库守不变量#3)。Phase 4 完成；M1–M4 全部交付。下一阶段 M5(生成+多 provider+Skill) 见 DEV_PLAN，进入时归档 M3/M4 并拉入新表。
- [2026-06-19] @qa — **🎉 M4 完成**（Wave D：T4.5✅ M4 E2E）。`tests/test_m4_e2e.py` 真 pg + 多档位 mock 网关（零 token）真跑通过 3 用例：① **学文风闭环**：`POST /style`→**202** `{job_id}`→BackgroundTask `run_job` 自建独立 session 跑 `run_style_extraction`(analyst 假适配器产 StyleFingerprintResult)→`SqlStyleFingerprintWriteRepo.append` 真落 pg→`GET /jobs/{id}` status=done(result.version/dims_count)→`GET /style` 最新指纹→`mode="update"` 学一次 version+1；DB 真源 `style_fingerprint` 2 行(version 1/2,dimensions/evidence)+ `jobs` 2 行 done。② **写章第四审**：学文风后 assemble stable_core 含指纹→`POST .../review` SSE 四审齐(continuity/foreshadow/pace/style)无 error + `section{name:"style"}` + `style{score:72,segments:[{idx:2,score:40,label:"机翻腔"}]}` 帧真发 + `GET .../reviews` 留痕 style(dict)；DB 真源 `chapter_reviews.style` 列真填。③ **回炉**：`POST .../refine`→200 `{original,refined}`,refined≠original(writer 据 output_schema is None 返改写串)+ 不写库(无 chapter/review 行,不变量#3)+ writer 记账落 usage_ledger。**无新 bug**。关键坑(记 memory/gotchas)：学文风 BackgroundTask 在独立 session 上**自造网关**(`build_gateway_for_tier` 从凭据建真 OpenAI 适配器)→E2E 无真凭据,须 monkeypatch `style.build_gateway_for_tier`→真 Gateway 包假适配器(ledger 绑入参 session);写侧 repo + `run_job` 默认 `SqlJobRepo` 保持真→指纹/job 真落 pg;override `get_session_factory`→`e2e_sm`(同测试 engine/loop)。最终全收口门禁绿：ruff/format 干净、mypy **115 文件**、pytest **296 passed**(293→+3)、**alembic 无漂移**(M4 无建表迁移)。**M4 出口 DoD 全达成 → Phase 4 可归档。**
- [2026-06-19] @frontend — **M4 Wave C：T4.4✅ 文风页 + 漂移 + 回炉 + job 轮询**（消费 C3 扩 + C4 扩，无契约变更）。先 `pnpm gen:api` 纳入 style/refine/GET-style + jobs 类型（`lib/api/types` 加 4 别名）。① **job 轮询**：新模式 `lib/jobs/job.ts`(纯状态机 `narrowJob`/`reducePoll`/`isTerminal`/`styleLearnResult`——`GET /jobs/{id}` 是松散 `{[k]:unknown}` 裸端点须安全收窄) + `useJobPoll.ts`(setTimeout 轮询，done/failed 终态停，卸载清理)。② **文风页** `app/projects/[id]/style/page.tsx`(RSC 取 project + `GET /style`→404 容忍返 null) + `components/style/`：`StyleUpload`(多段文本粘贴/选文件 `file.text()` 读成文本→空行切段→`POST /style`)、`useStyleLearn`(受理 202→`useJobPoll`→done 拉指纹)、`FingerprintView`(16 维行+可展开证据)。③ **审稿页第四审**：`lib/review/sse.ts` 加 `StyleEvent`/`StyleDriftReport`/`reduceReview` case "style"(替换式仿 pace)/`ReviewStreamState.style`/seed；`history`-style 收窄 `normalizeStyleDrift` 落 `lib/style/style.ts`；`StylePanel`(◔ 整体相似度% + 漂移段一键回炉)接进 ReviewReport(seed 扩四态)。④ **回炉** `RefineView` + `useRefine`(`POST /refine`→新旧 diff→采纳：把 refined 替换 finalText 中原段，经既有 draft 路径合入，不另写库守不变量#3；503 优雅提示)。LeftNav 开「文风」入口(`ActiveNav` 加 "style")。前端门禁全绿：gen:api/lint/tsc/**vitest 96**(+27：job 12 + style 10 + style-sse 5)/build(含 `/projects/[id]/style` 路由)。memory 记 1 gotcha。下一步 **Wave D：T4.5(@qa) M4 E2E**。
- [2026-06-19] @orchestrator — **M4 Wave B 完成 ✅**（T4.3，C3 扩稳定）。style/refine 三端点入 OpenAPI：`POST /style`(202+jobs BackgroundTask 跑 run_style_extraction→StyleFingerprintWriteRepo.append)、`GET /style`(最新指纹含证据/版本)、`POST /refine`(writer 网关同步返 {original,refined} 不写库)。全仓门禁绿：ruff/format 干净、mypy **115 文件**、**alembic 无漂移**、pytest **293 passed**(+13)。`GET /style` 顺带补入（回写 ARCH §7.2/PRODUCT_SPEC §6 留 @docs）。下一步 **Wave C：T4.4(@frontend)**——先 `pnpm gen:api` 纳入 style/refine/GET-style + jobs 类型，再做文风页(上传/指纹/证据) + `lib/jobs/useJobPoll` + 审稿页第四审 StylePanel + 回炉 diff。
- [2026-06-19] @backend — **M4 Wave B：T4.3✅ API style + refine**（契约 **C3 扩稳定**）。三端点入 OpenAPI（独立 `routers/style.py`，仿 outline.py，已注册）：① `POST /projects/:id/style` → **202** `{job_id}`（学文风走 jobs，M4-c）：`job_repo.create("style_learn")`+commit → `background_tasks.add_task(run_job, session_factory, job.id, work)`；`work` 在 `run_job` **自建独立 session** 上自造 analyst 网关+写侧 repo → `run_style_extraction` → 拆 `dimensions_json`/`evidence_json` → `append`(version+1) → `{version,dims_count}` 落 `jobs.result`；无凭据 → dep 解析阶段 **503**（调度 job 前拦下，不凭空写注定失败的 job）。② `POST /projects/:id/chapters/:no/refine` → 同步 200 `{original,refined}`（writer 网关 `run(refiner_spec)`，`output_schema=None`→`resp.text`；不写库守不变量#3；末尾 commit 让 usage_ledger 落库）。③ **顺带补 `GET /projects/:id/style`** → 最新指纹 `{dimensions,evidence,version}`（UX §6.9；ARCH §7.2 原缺读端点——决策见 decisions，**待 @docs 回写 ARCH §7.2/PRODUCT_SPEC §6**）；不动 C5 读侧 `SqlStyleRepo`（assemble 行为不变）。新建写侧 `domain/style_repo.py`(`StyleFingerprintWriteRepo`/`Sql*`，`append` 只 flush + `latest` 含证据/版本)；`project_deps` 加 `get_style_write_repo`/`get_style_extract_gateway`(analyst)/`get_refine_gateway`(writer)。全仓门禁绿：ruff/format 干净、mypy **115 文件**、**alembic 无漂移**(M4 无建表)、pytest **293 passed**(280→+13)。memory 登记 C3 扩 + 2 decisions + 1 gotcha。**@frontend 行动**：T4.4 前 `cd apps/web && pnpm gen:api` 纳入 style/refine/GET style + jobs 轮询类型。下一步 **Wave C：T4.4(@frontend)**。
- [2026-06-19] @orchestrator — **M4 Wave A 收口 ✅**（T4.2 + T4.1 全绿）。全仓门禁收口：ruff/format 干净、mypy **111 文件**、**alembic 无漂移**（M4 无建表迁移）、pytest **280 passed**（M3 基线 228 → +52：T4.2 37 + T4.1 15）。契约稳定：C6扩/C4扩（StyleFingerprintResult/StyleDriftReview/四审齐/SSE style 事件）、C3.5（JobRepo/run_job）。下一步 **Wave B：T4.3(@backend)**——单 @backend agent 顺序做 style/refine 两端点：新建 `StyleFingerprintWriteRepo` + `routers/style.py`(`POST /style` 202 调 `job_repo.create`+`run_job`(work=`run_style_extraction`→`StyleFingerprintWriteRepo.append`)；`POST /refine` 同步 `refiner_spec` writer 网关返 {original,refined})，C3 扩；评估顺带 `GET /projects/:id/style`。
- [2026-06-19] @backend — **M4 Wave A：T4.1✅ jobs 长任务基建**（契约 **C3.5 稳定**，不碰 style 业务，与并行 T4.2 @llm 无目录/写冲突）。`packages/core/ww_core/domain/job_repo.py`：`JobRepo` Protocol + `SqlJobRepo`(create/set_running/set_progress 夹取/complete/fail/get/reap_zombies) + frozen `JobView`——状态写**只 flush**(提交归 `run_job`/端点)，唯 `reap_zombies` 启动期独立调用故**自 commit**。`apps/api/ww_api/services/job_runner.py`：通用 `run_job(session_factory, job_id, work)` **自建独立 session**(仿 `run_overdue_scan`)→set_running→work→complete/异常→新 session fail，异常吞不冒泡；最小 `JobLifecycleRepo` Protocol + 可注入 `repo_factory` 缝。`get_job_repo` 加进 `project_deps`；`main.py` lifespan 在 `seed_stub_user` 后跑 reaper(M4-d，幂等自 commit)。门禁绿(限本任务文件)：ruff/format 干净、mypy 本任务文件 0 error(`packages/core apps/api` 余 7 error 全在 @llm 在做的 `test_style_*`)、pytest **15 新测**(13 job_repo+2 job_runner)，`packages/core` **130 passed** / `apps/api` **50 passed**。memory 登记 C3.5。**T4.3 行动**：`POST /style` 调 `job_repo.create` + `background_tasks.add_task(run_job, session_factory, job.id, work=…)`。
- [2026-06-19] @llm — **M4 Wave A 半程：T4.2✅**（C6 扩 + C4 扩稳定）。style-auditor 双轨落地：①提取轨 `style_extract_spec`(analyst,独立生成仿 outliner,**不进 review 图**)+`StyleFingerprintResult{dimensions:[StyleDimension{name,value,evidence}]}`(16 维带证据)+`orchestrator/style_extract_node.py`(`build_style_extract_request`/`run_style_extraction`,模块内自有 GatewayRun Protocol,网关失败上抛/parsed 违约抛 ValueError,只读不写库)；②漂移轨第四审 `style_drift_spec`(name="style",light,只读)+`StyleDriftReview{score=100,segments:[StyleDriftSegment{idx,score,label?}]}`(默认值即无指纹降级态)→`REVIEW_SPECS` 四审齐；collect 加 `STYLE`/`extract_style`→record `style=` 列；sse 加 `EVENT_STYLE`/`style_event`+`_section_result_events` STYLE 分支(`style{score,segments}`,字典序 continuity<foreshadow<pace<style 天然纳入)；③回炉 `refiner_spec`(writer,output_schema=None 纯文本,非持久)。门禁绿：ruff/format 净、mypy **47 文件**(隔离 cache 避与 @backend T4.1 竞争)、packages/agents+core pytest **197 passed**(+37 新测：双轨 spec/schema 契约 + 四审图跑通 len(calls)==4 + collect 落 style 列 + SSE style 事件 + 无指纹降级落库)。memory 登记 C6扩/C4扩。**T4.1(@backend) 已并行完成**→ Wave B：T4.3(@backend style/refine 端点) 可起，调本任务 `run_style_extraction`/`refiner_spec`。
- [2026-06-19] @orchestrator — **进入 M4（文风）**：归档心智上 M3，Phase 4 五任务拉入台账。探查确认免重复建表：`style_fingerprint` 表/`chapter_reviews.style` 列/`jobs` 表+`GET /jobs/:id`/读侧 `SqlStyleRepo.latest` 均已在前期建齐 → **M4 无新建表迁移**；`assemble` 已把指纹纳入 `stable_core` → 第四审天然在 review_context 拿指纹对照、无需额外注入。已定决策：style-auditor 双轨拆 `style_extract_spec`(analyst,独立生成,仿 outliner)+`style_drift_spec`(light,第四审并入 `REVIEW_SPECS`)；学文风走 jobs+BackgroundTask(独立 session,仿 run_overdue_scan)+启动 zombie reaper；回炉 refine 同步返回 {original,refined} 不写库（守不变量#3）。启动 **Wave A：T4.2(@llm·agents+orchestrator) ‖ T4.1(@backend·jobs 基建)**（不同目录,隔离 MYPY_CACHE_DIR）。
- [2026-06-18] @orchestrator — **🎉 M3 完成**（T3.8✅ 修复 + @qa 解钉 xfail）。T3.8 把 `SqlAlchemyLedgerSink.record` 改 add-only（去并行路径里唯一的 `await flush()`，靠端点/事务 commit 持久化），并发回归测试 RED 已验旧实现抛 `Session is already flushing`；@qa 移除 T3.7 case3 strict-xfail、收紧为严格正向断言（三审 section 齐 + foreshadow/pace 真发事件 + `chapter_reviews.foreshadow_sug`/`pace` 列真填 + `beat_map` 精确断言）。最终全收口门禁绿：ruff/format 干净、mypy **111 文件**、pytest **228 passed**(0 xfailed/0 failed)、**alembic 无漂移**；前端 lint/tsc/vitest 69 绿。**M3 出口 DoD 全达成。** 下一阶段 M4（文风）。
- [2026-06-18] @orchestrator — **M3 Wave E：T3.7✅ E2E 交付 + 暴露并发 bug**。`tests/test_m3_e2e.py` 真 pg 跑通：① 伏笔 埋设→进展→验收后 BackgroundTask 扫描置 OVERDUE→看板过滤(DB 真源)✓ ② 大纲 `POST .../outline`→outline 表行+窗口+幂等 upsert+记账✓。③ 三审 SSE 用例 **strict-xfail 钉住真 bug**：三审同 LangGraph superstep 并行 + 共用请求 session 的 `SqlAlchemyLedgerSink.record`(add+`await flush`)→`AsyncSession` 非并发安全→第二/三审 flush 撞「Session is already flushing」→被 `run_review` 失败隔离静默吞成 `incomplete`→**foreshadow/pace 静默丢失**(SSE 无事件、`chapter_reviews` 列空)。M2 单审未触发。基线 **222 passed + 1 xfailed**。开 **T3.8(@llm)** 修复 → 修后 @qa 解钉 xfail → 才算 M3 达成。
- [2026-06-18] @orchestrator — **M3 Wave D 完成**（T3.6✅）。前端三块落地：伏笔四泳道看板（`/foreshadow`，OVERDUE 琥珀 + 登记/转移乐观更新）、大纲编辑器（`/outline`，逐章 beats + 伏笔窗口徽标 + 接近回收提示）、审稿页扩消费 `foreshadow`/`pace` SSE（伏笔建议区 + 节奏节拍图 ▁▃▅，三审齐显，文风第四审留 M4）。`pnpm gen:api` 纳入 M3 端点（看板/登记/转移/大纲），松散 JSONB 字段安全收窄不手写。前端门禁绿：gen:api/lint/tsc/**vitest 69(+24)**/build。memory +1 decision +2 gotchas。下一步 **Wave E：T3.7 M3 E2E**(@qa 真 DB + mock 网关零 token)。
- [2026-06-18] @orchestrator — **M3 Wave C 完成**（T3.5✅，C3 扩稳定）。`GET .../foreshadow?status=` 看板（续 foreshadow.router，四泳道）+ `POST .../outline`（独立 outline.py：`run_outline` analyst 网关 → 逐章 upsert `outline` 表含伏笔窗口 → 端点末尾 commit）。outline 写侧 repo 加 `Write` 前缀避撞 C5 读侧。全仓门禁绿：mypy **102 文件**、pytest **220 passed**(205→+15)。memory 登记 C3 扩 + 1 decision + 1 gotcha。下一步 **Wave D：T3.6**(@frontend 伏笔看板 + 大纲编辑器 + 节奏节拍图 + 审稿页消费 foreshadow/pace SSE，先 `pnpm gen:api`)。
- [2026-06-18] @orchestrator — **M3 Wave B 完成**（T3.2✅）。验收后到期扫描挂 BackgroundTask（`run_overdue_scan` 自建独立 session 置 OVERDUE，接 §5.5 `TODO(M3)`）+ 伏笔登记/状态端点（`POST/PATCH .../foreshadow`，重复 code/非法转移→`VALIDATION` 422，未动 shared 错误码）。全仓门禁绿：mypy **95 文件**、pytest **205 passed**(196→+9)。memory 登记 C3 扩 + 1 decision + 2 gotchas。下一步 **Wave C：T3.5**(@backend `GET .../foreshadow?status=` 看板续接 foreshadow.router + `POST .../outline` 独立 outline.py 调 run_outline)。
- [2026-06-18] @orchestrator — **M3 Wave B 半程**（T3.3✅，C4/C6 扩稳定）。foreshadow-analyst + pace-checker 并入 `build_review_graph`（`REVIEW_SPECS` 三审齐，文风第四审留 M4）；collect 一次 record 分列落 `conflicts`/`foreshadow_sug`(扁平+kind 标记)/`pace`(整 dict)；`normalize_review` 加 `foreshadow`/`pace` SSE 事件（向后兼容）。全仓门禁绿：ruff/format 干净、mypy **91 文件**、pytest **196 passed**(169→+27)。memory 登记 C4/C6 扩 + 2 gotchas。**Wave B 顺序执行**：T3.3 改完 orchestrator → 现启 **T3.2(@backend apps/api)**，对最终 orchestrator 构建、无编译竞争。
- [2026-06-18] @orchestrator — **M3 Wave A 完成**（T3.1✅ + T3.4✅）。两 agent 并行（@backend core/domain ‖ @llm agents+orchestrator，隔离 MYPY_CACHE_DIR 避缓存竞争）。T3.1：伏笔纯函数状态机(OPEN/PARTIAL/CLOSED/OVERDUE 全转移+到期判据)+写侧账本 repo(`ForeshadowLedger*`，加 Ledger 前缀避撞 C5 读侧)。T3.4：outliner spec + `OutlineResult` schema(含 `foreshadow_windows`)+`run_outline` 节点(只读不写库，C6 扩稳定)。全仓门禁绿：ruff/format 干净、mypy **90 文件**、pytest **169 passed**(114→+37+18)。memory 登记 C6 扩 + 2 decisions + 2 gotchas。下一步 **Wave B：T3.2(@backend 到期扫描+登记接口) ‖ T3.3(@llm foreshadow/pace 第三审并入 review_specs)**。
- [2026-06-18] @orchestrator — **进入 M3**：归档 Phase 2(M2) 至已归档；Phase 3 七任务拉入台账。沿用 M2 节奏：契约先行 + 分目录并行 + 全仓门禁收口。核对：`foreshadow` 表已在 T0.2 建齐 → **M3 无新建表迁移**；T3.1「Repository+状态机」据实记 @backend（落 `core/ww_core/domain/`，同 T2.3 先例）；T3.3 复用 M2 `build_review_graph` 的 `review_specs` 扩展点加 foreshadow/pace 两审（文风第四审留 M4）。启动 **Wave A：T3.1(@backend·core/domain) ‖ T3.4(@llm·packages/agents+orchestrator)**（不同目录，无写冲突）。
- [2026-06-18] @orchestrator — **🎉 M2 完成**（Wave E：T2.7✅）。`tests/test_m2_e2e.py` 真实 pg + 多档位 mock 网关（零 token）真跑通过 2 用例：主用例串 立项→写章 SSE→自动保存→**审稿 SSE(section/conflict)**→GET 历史→**残缺裁决触发 409 `CONFLICT_UNRESOLVED`+missing_indices(gate 事务前拦截未写库)**→全量裁决+终稿验收(200, accepted v2)→**DB 真源逐项断言**：chapters draft v1 留存 + accepted v2(content==final_text)、`chapter_digests.facts` 追溯终稿标记(证 #4 digest 从终稿非草稿)、`chapter_reviews.decisions` 写回、`usage_ledger` 三档位(writer/analyst/light)各落一条且事务已 commit；补充用例验证零冲突直通。**无新 bug**（M1 曾暴露的 ledger 提交 bug 在 M2 三档位无复发——review 流末 + accept 事务末均 commit）。最终全收口门禁绿：ruff/format 干净、mypy **90 文件**、pytest **114 passed**、**alembic 无漂移**（M2 全程零建表迁移）。M2 出口 DoD 全达成。下一阶段 **M3（伏笔+节奏）**。
- [2026-06-18] @orchestrator — **Wave D 完成**（T2.6✅）。审稿报告页落地：`app/projects/[id]/review`（RSC 取历史）+ `components/review/*`（报告卡/就地标注/AcceptPanel）+ `lib/review/*`（SSE reducer/裁决完整性/请求体组装，纯逻辑单测）。`pnpm gen:api` 纳入 review/accept/reviews 三端点类型（不手写）；SSE 沿用 `useDraftStream` 的 fetch+ReadableStream 范式；五类冲突就地朱砂锚点(R6 段级降级)+裁决(采纳/忽略/手改)+**未决禁验收 gate**+409 missing_indices 高亮+「本次将更新」清单。前端门禁绿：gen:api/lint/tsc/**vitest 45(+21)**/build。memory +1 decision +2 gotchas。下一步 **Wave E：T2.7 M2 E2E**(@qa 真实 DB + mock 网关零 token)。
- [2026-06-18] @orchestrator — **Wave C 完成**（T2.4✅ + T2.5✅，C3 扩稳定，单 @backend agent 顺序做）。三端点入 OpenAPI：`POST .../review`(SSE section/conflict/done) + `GET .../reviews`(新→旧) + `POST .../accept`。验收：冲突 gate（裁决 `conflict_index` 须覆盖 review.conflicts 全下标，缺判→409 `CONFLICT_UNRESOLVED`+missing_indices）→ 终稿 digest 提炼在事务外(R2，light 档)→ 单原子事务 promote(R4)+digest.append(#4)+set_decisions、末尾一次 commit、失败全回滚(R3)。`build_gateway_for_tier` 统一多档位网关注入。全仓门禁绿：ruff/format 干净、mypy **83 文件**、pytest **112 passed**（+9）。memory 登记 C3 扩 + 1 decision + 2 gotchas。下一步 **Wave D：T2.6**(@frontend 审稿页+裁决+验收 gate，先 `pnpm gen:api`)。
- [2026-06-18] @orchestrator — **Wave B 完成**（T2.2✅，C4 扩稳定）。审稿子图落地：`build_review_graph`(START→continuity 并行审→collect→END，可扩四审) + `collect_reviews` 落 `chapter_reviews` 留痕 + `normalize_review`(SSE 加 `section`/`conflict`)；M1 `build_write_graph` 不动。划清边界：本任务到 collect+归一缝为止，`interrupt`/accept 事务接线交 T2.4（对齐 R3：accept 从领域表重读、checkpoint 仅控制流）。全仓门禁绿：ruff/format 干净、mypy **80 文件**、pytest **103 passed**。memory 登记 C4 扩 + 3 gotchas（reducer/add_node 重载/失败隔离两层）。下一步 **Wave C：T2.4+T2.5**（单 @backend agent 顺序做：验收事务+冲突 gate / review API+历史，避免 apps/api 路由+OpenAPI 写冲突）。
- [2026-06-18] @orchestrator — **Wave A 完成**（T2.1✅ C6 稳定 + T2.3✅）。两个 fresh agent 并行（@llm packages/agents+llm_gateway ‖ @backend core/domain，目录不相交无冲突）。**补上 M1 遗留网关缺口**：`Gateway.run()` 经 instructor 接通 `output_schema→LlmResponse.parsed`（改 C1，见 contracts 变更日志）。T2.3 写侧三 repo（章节 accepted 晋升/digest append/审稿留痕+裁决）只 flush 不 commit、交 T2.4 事务。全仓 compose 门禁绿：ruff 干净、format 干净、mypy **77 文件**、pytest **89 passed**。memory 已登记 C6+C1 变更+2 decisions+3 gotchas。下一步 **Wave B：T2.2**(@llm 并行审图+collect 留痕+review SSE，依赖 C6+C4)。
- [2026-06-18] @orchestrator — **进入 M2**：归档 Phase 1(M1) 至已归档；Phase 2 七任务拉入台账。计划核对两处偏差并据实修正：(1) **T2.3「Repository 逻辑」落 `packages/core/ww_core/domain/`（@backend owned）而非 `packages/db`**——DB 全 MVP 表 + 唯一约束 `(project_id,chapter_no,version)` 已在 T0.2(@db) 一次建齐、M2 无新建表迁移，故 T2.3 由 @backend 承接 Repository 逻辑；(2) **网关结构化输出缺口**：`Gateway.run()` 当前不消费 `output_schema`/不填 `LlmResponse.parsed`（instructor 未接线），续审依赖此，归入 T2.1(@llm)。启动 **Wave A：T2.1(@llm·packages/agents+llm_gateway) ‖ T2.3(@backend·core/domain)**（不同目录，无写冲突）。
- [2026-06-18] @orchestrator — **🎉 M1 完成**（Wave E：T1.9 E2E✅）。真实 DB + mock 网关 E2E 走通 立项→写章 SSE→自动保存→DB 真源断言（projects/chapters/usage_ledger）。**E2E 暴露并修复一个生产 bug**：draft SSE 端点未提交注入 ledger 的请求 session（`SqlAlchemyLedgerSink` 按设计只 flush、不 commit——写库事务归编排层），导致真实写章的 `usage_ledger` 行被回滚丢失（违背「成本账本从第一天」）。修复：draft 端点流耗尽后 `await session.commit()`；E2E 改用请求 session 做 ledger、去掉手动 commit 以回归校验该提交。最终全门禁绿：ruff / mypy 68 / pytest **57** / alembic 无漂移；前端 gen:api/lint/tsc/vitest 24/build 绿。
- [2026-06-18] @orchestrator — **Wave D 完成**（T1.5+T1.6+T1.8✅，单前端 agent）。`pnpm gen:api` 重生成客户端含全 M1 端点；路由 `/`(作品库)、`/projects/new`(5 步向导)、`/projects/[id]/write`(工作台 SSE 打字机+停+自动保存)、`/settings/providers`(脱敏+测试连接+能力徽标)。前端门禁全绿：gen:api/lint/tsc/**test 24**/build。M1 占位（无 API 支撑、未造假端点）：单章导航、本章助手/注入透明面板(M2)、向导主角/世界观字段折叠进 premise、档位路由只读。下一步 Wave E：T1.9 E2E(@qa, mock 网关)。
- [2026-06-18] @orchestrator — **Wave C 完成**（T1.4✅ 立项+写章 API，**C3 稳定**：M1 端点全落）。记忆→网关→编排器串成 HTTP：5 端点入 OpenAPI，写章 SSE(token/done/error) + 幂等自动保存，lifespan 幂等 seed stub user（修 owner_id FK 缺口）。网关从凭据构建、测试经 dependency_overrides 注 mock。全仓 compose 绿：ruff、mypy **68 文件**、pytest **56 passed**。后端/编排全部完成。下一步 Wave D：单个前端 agent 做 T1.5+T1.6+T1.8（同一 Next 应用合并做，先 `pnpm gen:api`），避免 apps/web 写冲突。
- [2026-06-18] @orchestrator — **Wave B 完成**（T1.3✅ 编排器/C4稳定 + T1.7✅ 凭据/C3部分）。两个 fresh agent 并行（各 ~90–98k tokens，比 fork 省）。全仓 compose 门禁绿：ruff、mypy **61 文件**、pytest **48 passed**。修了跨包测试碰撞（apps/api `fakes.py`→`fakes_providers.py`；root mypy 排除 `conftest.py` 解决多 rootless conftest 同名）。⚠️ **Wave C 前置**：DB 无 stub user，而 projects/usage_ledger/provider_credentials.owner_id 均 FK→users.id；T1.4 须先幂等 seed stub user(`uuid(int=1)`)。下一步 Wave C：T1.4(@backend 立项+写章 SSE+自动保存，依赖 C4)。
- [2026-06-18] @orchestrator — **Wave A 完成**（T1.1✅ + T1.2✅，契约 C1/C5 稳定）。全仓 compose 门禁绿：ruff 干净、mypy 45 文件、pytest **27 passed**（9 基线 +10 网关 +8 记忆）。T1.1 网关由 orchestrator 内联实现（原 fork 因 stream idle timeout 早夭、零产出）；T1.2 记忆由 fork 完成。下一步 Wave B：T1.3(@llm 写章节点+SSE，依赖 C1+C5) + T1.7(@backend 凭据，依赖 C1)。
- [2026-06-18] @orchestrator — **进入 M1**：把 Phase 1 九任务拉入台账并归档 Phase 0；新增 workspace 成员 `packages/llm_gateway`(ww-llm-gateway) + `packages/core`(ww-core)，装 openai/instructor/langgraph/langgraph-checkpoint-postgres/cryptography/tenacity，根 `pyproject.toml` members+mypy_path 已更，`uv sync` 通过，基线门禁全绿(ruff/mypy 27 文件/pytest 9 passed)。记录 decision：`assemble` 返回中性文本以解耦 T1.1‖T1.2。启动 Wave A（T1.1 @llm + T1.2 @backend）。
- [2026-06-18] @devops/@db/@backend/@frontend — **Phase 0 全部完成**：monorepo(uv workspace + pnpm)、16 MVP 表迁移(无漂移)、FastAPI(structlog+request_id+错误信封+/jobs/:id)、Next.js(纸感 token + OpenAPI→TS gen:api)、CI。门禁全绿，API 实跑 200。下一步 M1/T1.1(网关接口)。
- [2026-06-18] @docs — DEV_PLAN 与规格对齐修订：T0.3 加可观测性底座(structlog+request_id)、T1.1 加 `usage_ledger` 记账(从第一天)、T0.2/T2.3 统一建表口径(全 MVP 表一次建齐, T2.3 仅 Repository)、T4.2 把文风漂移并入第四审、新增 `PUT .../draft` 自动保存端点并回写 ARCH §7.2 + contracts C3。
- [2026-06-17] @docs — 四份规格 + CLAUDE.md + 协同系统（PROGRESS/memory）就绪；可进 Phase 0 / T0.1。
- [2026-06-19] @llm — **K1.2 修头**：Kimi Code 适配器 `kimi_code_headers()` 改为**仅** `{"User-Agent": "KimiCLI/1.5"}`，删全部 X-Msh-* 头 + `kimi_device_id`/`KIMI_DEVICE_ID`/确定性 device-id 机制（K1.5 联网实测：有效 token 下 X-Msh-* 疑似触发 `401 Invalid Authentication`；对齐 pi-kimi-coder = UA-only）。改 `adapters/kimi_code.py` + `__init__` 导出 + 两测（adapter 6 测/factory 3 测，断 default_headers 仅 UA、无 X-Msh-*）。更 contracts C1扩（头集 UA-only）+ gotcha。门禁绿：ruff/format/mypy/pytest（56 passed）。
- [2026-06-19] @backend — **bugfix 空写章 prompt**：`assemble`（C5）从不含项目 premise/logline/theme/title 且无写章指令 → 全新项目（仅 premise）产空 `stable_core`+`volatile`，写章触发 LLM `400 message must not be empty`。修复：`MemoryRepos` 加第 8 repo `project:ProjectSpecRepo`（`SqlProjectSpecRepo` 按 project_id 读 `projects`）；`assemble` 把「作品蓝本」放入 `stable_core` 首段（书级 spec→缓存前缀，守 #9）+ `volatile` 始终含「请创作第 N 章的正文。」。改 `domain/repositories.py`(+ProjectSpecView/Repo) · `memory/sql_repositories.py`(+SqlProjectSpecRepo, sql_memory_repos 装配) · `memory/assemble.py`(_build_spec_section + 注入)；测试 `test_memory.py`(+bare-project/chapter-directive 两测+fake) · `test_projects.py`/`test_generation.py`(补 project= fake)。无 DDL/迁移（只读既有列）。门禁全绿：ruff/format/mypy(154 文件)/alembic check(无漂移)/pytest **438 passed**。更 contracts C5 + gotcha。消费方注意：手搓 `MemoryRepos(...)` 须补 `project=`。
- [2026-06-20] @frontend — **UX R1+R2 完成**（审稿裁决人体学，纯前端无后端改）。R1：冲突卡内联命中段预览——新纯函数 `lib/review/snippet.ts`（从 `where`「第 N 段」解析 1-based 段号 → 取终稿对应段截断预览，无段号/越界/空段→null 回退「跳转」），`ConflictCard` 加 `snippet` prop 渲染朱砂引用块。R2：冲突按 type 分组 + 严重度排序（设定违例>能力不符>时间线倒错>地理矛盾>性格漂移，未知殿后）+ 组计数徽标 + 「跳到下一条未裁决」（按显示顺序环回）+ 主次裁决按钮（采纳改法=朱砂主、忽略/手改=次级）——新纯函数 `lib/review/grouping.ts`（`groupConflicts`/`displayOrder`/`nextUnresolvedInOrder`，**分组只改显示顺序、每条携原始 index，守 conflict_index 覆盖 range 不变量**），`ReviewReport` 改分组渲染。TDD：snippet 8 测 + grouping 10 测。前端门禁绿：lint/tsc/**vitest 201**(+18)/build。分支 `feat/ux-r1-r2-review-ergonomics`。下一步（按 plan）：R3 验收前清单 → Tier4。
- [2026-06-20] @frontend — **UX R3 完成**（验收前清单 HITL gate，UX §7.4，纯前端无后端改，续 `feat/ux-r1-r2-review-ergonomics`）。新纯函数 `lib/review/accept-preview.ts`（`buildAcceptPreview`：据现有 client state 列「本次验收将更新」——晋升版次 + 提炼章节摘要 + 写回裁决(有冲突时) + 验收后伏笔到期扫描(可能置 OVERDUE) + 伏笔建议待登记提醒(有建议时)）。**只列后端 accept 事务确实做的事**（核对 `projects.py` accept 端点：promote+digest+decisions+overdue 扫描；**不**声称改人物 latest_state/自动落库伏笔建议）；带「以上为预期，最终以验收回执为准」免责。`AcceptPanel` 加 `foreshadowCount` prop + 未阻断时渲染清单，`ReviewReport` 传 `foreshadow.length`。TDD：4 测。前端门禁绿：lint/tsc/**vitest 205**(+4)/build。下一步（按 plan）：Tier4（T4-a 顶部 AI 工具条 / T4-b 本章指令框+风格预设）。
- [2026-06-22] @backend — **✅ T6.2+T6.3 创作工具箱通用端点稳定**（通用生成器框架后端落地，分支 `feat/t6-creation-toolbox`）。新 `ww_skills.TOOLBOX`(11 条：legacy 3 + 新 8) + `get_tool`（`toolbox_registry.py`，descriptor 在代码无迁移）。3 新端点（`routers/toolbox.py`，已注册）：`GET /skills/toolbox`（描述符列表，legacy 携 `legacy_route`：`codex?gen=world|character` / `/outline`）、`POST .../skills/{tool_key}/generate`（按 `spec.tier` 经新注入缝 `get_tier_gateway_builder` 建网关 → `run_generator` 结构化预览，**不写库**仅落 ledger；未知/legacy→404、项目不存在→404、无凭据→503）、`POST .../skills/{tool_key}/ingest`（仅 `ingest!=None` 工具；world_entities 走 **continuity 预检 409 gate**(`acknowledge_conflicts` 放行) + `partition_writes` 白名单 + schema→JSONB 写库，outline 细纲按 idx 拼 beats upsert；不可入库→422）。PURE context 派发 `services/toolbox_context.build_toolbox_context`（4 策略，缺章/大纲→空节拍不报错）。response/request schema `schemas/toolbox.py`（concrete 子模型，非裸 dict）。守不变量 #2/#3/#9。TDD：注册表 7 + context 8 + 端点 11 = **+26 测**。**全仓门禁绿**：ruff/format 干净、mypy **183 Success**（隔离 cache）、alembic **无漂移**（无新迁移）、pytest **578 passed**。OpenAPI 新增 3 端点 → **@frontend 须 `cd apps/web && pnpm gen:api`**（T6 前端前置）。记 contracts C3 扩（T6.2/T6.3）。
- [2026-06-20] @frontend — **Tier4 完成**（T4-a 全局 AI 工具条 + T4-b 本章指令直通生成，分支 `feat/ux-tier4-ai-toolbar`，未并 develop）。T4-a：新 `lib/nav/ai-tools.ts`(aiToolItems) + `components/AiToolbar.tsx` 服务端组件，AppShell 顶栏下仅项目页渲染（写本章/审稿/大纲/设定库/工具箱常驻一排，写本章=朱砂主动作），用 `--chrome` CSS 变量统一 chrome 高度（项目页 7rem=header4+toolbar3，余 4rem），6 个固定高度页改用 `calc(100vh-var(--chrome,4rem))` 适配。T4-b（全链）：后端 `assemble/_build_volatile` 加 `directive` 形参（「本章指令」段领衔 volatile，**绝不入 stable_core**，守缓存前缀不变量 #9），`stream_draft` 加可选 body `DraftStreamRequest{directive}` 透传、`directive_len` 入 log 不记原文、不持久化无迁移，重生成 TS 客户端；前端新 `lib/workbench/directive.ts`(STYLE_PRESETS + composeDirective 纯函数)、`useDraftStream.start(p,no,directive?)` 非空时发 JSON body、Workbench 加可折叠「本章指令」textarea + 风格预设 chips。TDD：ai-tools 4 测 + directive 4 测 + assemble directive 测 + draft 端点 directive/向后兼容 2 测。门禁全绿：后端 ruff/format/mypy(163 文件)/**pytest 479 passed**；前端 lint/tsc/**vitest 213**/build。契约记 contracts C3 扩（T4-b）。
- [2026-06-28] @backend/@frontend — **UI Phase G 后端依赖补齐**：`ProjectResponse` 增加 `updated_at` / `pending_review_count`，`chapters` 加 `updated_at` 迁移 `8c1d2e3f4a5b`；`SqlProjectRepo` 聚合项目自身、章节、审稿时间作为最近编辑，并按草稿是否晚于最近 accepted/review 统计待审稿章数。前端 `pnpm gen:api` 后项目库默认按最近编辑排序，新增“待审稿”筛选，作品卡显示最近编辑与待审稿徽标。契约记 `memory/contracts.md` C3 扩，UI 计划记 `docs/design/ui-improvement-plan.md` 0.16。
- [2026-06-29] @frontend/@qa — **UI review follow-up 修复完成**：补 `lib/review/chapterNav.ts` 使新增章导航测试可运行；`ReviewSectionPanel` 改为本地 open 状态，避免用户折叠后重渲染强制弹开；`ReviewReport.hasReport` 纳入 clean review；新增 `tests/test_project_metadata_e2e.py` 用真实 DB 覆盖项目最近编辑/待审稿统计口径。验证：前端 lint/typecheck/vitest **490 passed**/build；后端 ruff/format/mypy、pytest **763 passed**；浏览器抽样 review/settings 无 console error/横向溢出。
- [2026-07-06] @frontend — **✅ 灵感⑥ 关系图谱（缩减版）+ D4 完成**（纯前端，无后端契约/迁移/gen:api）。新增 `lib/characters/graphLayout.ts`（纯逻辑：`roleStyle` role→分组配色关键词映射、`buildGraphNodes`/`buildGraphEdges`(kind→标签，`DEFAULT_RELATION_LABEL` 命名常量)、`countDanglingRelations`、`layoutGraph`(@dagrejs/dagre 一次性确定性初始坐标、中心→左上、不可变)、`buildRelationshipGraph` 端到端）——**仅用现有 `CharacterCardView.relations={name,kind,note}`**：边按 name-join、kind 作中文标签、节点按 role 上色，无 closeness/阵营（数据不存在，不硬造）；悬空边（引用未入库角色）跳过并计数。新增 `components/characters/RelationshipGraph.tsx`（`'use client'`，React Flow @xyflow/react；CodexPage 经 `dynamic(ssr:false)` 挂载于人物 tab，显式容器高度 h-[28rem] + `import '@xyflow/react/dist/style.css'` + prefers-reduced-motion 关 fitView 动画 + 纸感 CSS 变量配色 + 悬空计数提示）。依赖：`@xyflow/react@^12.11.2`+`@dagrejs/dagre@^3.0.0`（见 contracts.md 2026-07-06）。TDD：graphLayout **+22 测**（role 映射/节点去空重名/kind 标签+默认/悬空跳过/自指跳过/无向去重/边 id 唯一/悬空计数/布局确定性+不可变/端到端）；组件走人工/E2E。前端门禁全绿：lint 干净 · typecheck 干净 · **vitest 565 passed** · **coverage lib 95.4%（graphLayout.ts 97.6%）** · **build OK**（codex 路由 7.58 kB，React Flow 懒加载独立 chunk）。注：`pnpm build` 门禁与运行中的 `next dev`(3001) 共享 `.next` → 触发 dev 服务临时 500（stale chunk），非代码缺陷、dev watcher 下次重编自愈，未重启服务。
- [2026-07-08] @frontend — **CR-M4/M5/L1 前端评审收尾（MEDIUM/LOW 打磨，仅 apps/web，无后端契约/gen:api）**。DONE：①a11y——`NavDrawer` 汉堡按钮补 `aria-controls`（新增 `Drawer` 可选 `id` prop 指向面板，默认 `nav-drawer`）、`ForeshadowCard` 的 `<dl>` 裸 `<div>` 行改真 `<dt>/<dd>` 结构（可视文案不变，importance 用 sr-only 标签）；②`ReviewReport.flashMark` 的 `setTimeout` 句柄入 ref + 卸载 useEffect 清理（防定时器泄漏，提 `FLASH_DURATION_MS` 常量）；③`useAccept` 手写 `ApiErrorEnvelope` 改由生成 schema 派生 `components["schemas"]["ErrorEnvelope"]`（端点专属 details 就地收窄，行为不变）；④`applyFix.hasApplicableFix` 重写为对象入参**类型谓词** `patch is ApplicablePatch`，消除 `applyConflictFix` 里两处 `as string`（含收窄单测）；⑤`server.parseJsonBody` 已具边界校验，补两条守卫分支测试（非对象体/非 JSON 体）；⑥去冗余 cast：`stream/sse.ts` `idx.valueOf()`→`idx`、`jobs/job.ts` `dict["error"] as string`（typeof 守卫后 TS 自动收窄）。SKIP/NOOP（前提已过时/本就安全）：`ProjectWizard` 换步聚焦标题已实现；`AppShell`/`LeftNav` 两处 `<nav>` 均已带 `aria-label`；`KNOWN_EVENTS` 加 `stop`——`stop` 是客户端 abort 动作非服务端 SSE 帧（对照 contracts.md 草稿 token/done/error、续审 section/conflict/foreshadow/pace/style/characterization/done/error 均无 stop），加入会为不存在的事件建处理；`CharacterGenerator`/`GeneratorRunner`/`ForeshadowCard.progress` 的 index-key——三处均为整表替换/追加式/静态展示列表（无逐项重排或删除，`CharacterCardItem`/`PreviewRow` 为无内部状态受控组件，选择/编辑均按 index 且随新预览整体重置），index 作 key 安全且无天然稳定 id，加合成 UUID 需重做 index 选择/编辑态属过度改造。门禁全绿：lint 干净 · typecheck 干净 · **vitest 681 passed**（+3）· **coverage lib 95.45%**（exit 0）· **build OK**。
