后端: 加 pytest-cov + [tool.coverage] 配置, pytest --cov-fail-under=80 (基线~88%)。 前端: 加 @vitest/coverage-v8, vitest.config.ts 设 80% 阈值, 新增 test:coverage 脚本 (基线~93%)。 前端覆盖范围限定 lib/** 纯逻辑层; hooks(use*.ts)待 jsdom 测试栈接入后纳入。 CI backend/frontend job 改跑覆盖率门禁命令。CLAUDE.md 同步 DoD 与 Toolchain。
19 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repository status
Phase 0–5 (M1–M5) are all shipped; there is no active phase. The monorepo is scaffolded and the full MVP loop runs end-to-end: 立项 → 设定库 → 大纲 → 写章(SSE) → 四审(一致性/伏笔/文风/节奏) → 验收事务,底层带多 provider 网关(路由/回退/熔断) + 声明式 skill 注册表(§5.5) + Kimi Code OAuth(K1)。Last green gate: backend ruff/format clean · mypy 154 files · alembic no drift · pytest 434 passed; frontend lint/typecheck clean · vitest 155 passed · build OK. The build/test tooling exists — see §Toolchain below; do not re-scaffold.
PROGRESS.md is the authoritative status ledger (current phase + archived M1–M5 detail + changelog). Read it before any task. Next work if resumed: DEV_PLAN §4 P2/后续 (向量检索 / 真任务队列 / 全书一致性扫描 / 社区市场), or the 对标竞品功能补齐 plan (~/.claude/plans/streamed-giggling-fiddle.md — AI续写/扩写、AI拆书、通用生成器框架、细纲、模板库).
Product: an AI-assisted Chinese web-novel writing workflow, delivered as a web app. The core thesis (PRODUCT_SPEC.md §1.5) is to treat long-form novel writing like software engineering — establish architecture (worldbuilding/characters/outline), generate against spec, test every chapter, maintain a single source of truth.
Documentation map — read the right doc
The four specs are layered. Read in this order; each later doc refines the earlier.
| Doc | Answers | Read when |
|---|---|---|
PRODUCT_SPEC.md |
What & why — problem, features (+priorities P0/P1/P2), data model, endpoints, agents | Understanding scope or a feature |
UX_SPEC.md |
What it looks like — IA, user flows, page wireframes, components, visual tokens (纸感/warm-cream theme) | Building any UI |
ARCHITECTURE.md |
How to implement — full DDL, LLM gateway internals, orchestration engine, API contracts, cross-cutting concerns | Implementing backend/data/gateway |
DEV_PLAN.md |
In what order — Phase 0–5, decoupled tasks each tagged with a discipline skill (@backend/@frontend/@db/@llm/@devops/@qa) |
Picking the next task |
ARCHITECTURE.md sections are anchored to PRODUCT_SPEC.md sections (← PRODUCT_SPEC §x). DEV_PLAN tasks reference both.
冲突裁决:若两份 spec 出现矛盾,以更具体/更靠后的文档为准(ARCHITECTURE > UX_SPEC/DEV_PLAN > PRODUCT_SPEC),并回写修正上游文档以消除分歧(见下文 §Conventions 的 spec 一致性纪律)。具体的枚举值(DDL 字段、SSE 事件、日志字段、错误码、§x.y 锚点)以 ARCHITECTURE/PRODUCT_SPEC 为唯一真源——本文件只重述跨文档、易错的规则,不复制会变的清单。
Locked tech stack — do not relitigate
These were decided after extended discussion (DEV_PLAN.md §0). Honor them; do not reintroduce the rejected alternatives.
- Frontend: Next.js + TypeScript (UI only; calls the backend via an OpenAPI-generated TS client — no hand-written shared types, no business logic in Next API routes).
- Backend: Python + FastAPI (async, SSE).
- Orchestration: LangGraph (the write→review→accept graph).
- LLM access: a thin self-built gateway over
anthropic+openai(baseURL covers DeepSeek/Kimi/Qwen/GLM) +google-genai. Not LangChain, not vendor CLIs/managed-agent runtimes. - Structured output: Pydantic +
instructor. - ORM/migrations: SQLAlchemy 2.0 (async) + Alembic.
- Storage: Postgres. No pgvector in the prototype.
- Long tasks: FastAPI BackgroundTasks + a
jobstable. No dedicated queue.
Prototype scope — explicitly deferred (do not build unless asked): multi-tenancy / auth (prototype is single-user, users/owner_id are stubs), vector retrieval (P2), a real task queue.
Architectural invariants — easy to get wrong, must hold
These span multiple docs and were the hard-won conclusions of design review. Violating them breaks the product's core value (long-form consistency) or its provider-neutrality.
- Memory (the DB) is the single source of truth. Agents communicate only through DB tables, never by calling each other directly (
ARCHITECTURE.md §5.4). - Agents declare a capability
tier(writer/analyst/light), never a concrete model. The gateway maps tier→provider+model per config. Never hardcode a provider or SDK in agent/orchestrator code (ARCHITECTURE.md §3.3/§4). - The four reviewers (continuity/foreshadow/style/pace) are READ-ONLY. No AI output is written silently. Every write goes through the accept transaction (HITL gate) after the author adjudicates (
§5.5). Unresolved conflicts must block accept (CONFLICT_UNRESOLVED). - Chapter digest is extracted from the FINAL accepted text, not the review-time draft. The author may edit during adjudication; extracting from the draft would pollute the truth source (
§5.4/§5.5/§6.1). - In-flight truth-source boundary:
chapters(text) andchapter_reviews(review results) are authoritative; the LangGraph checkpoint holds only control-flow position + a pending-decision handle. On resume, re-read domain tables (§5.2). - Memory injection is deterministic selection (explicit-named + main characters + recent N chapters), not vector search. Be aware of the coverage-gap mitigations (foreshadow-window entities, name-match FTS, author pin) in
§3.4. 章 = f(outline, selected state, fingerprint)— writing a chapter is a pure function; all state mutation is isolated to the accept transaction.- Naming contract: backend is Python/Pydantic → snake_case everywhere (request/response fields, schemas). The frontend consumes these via OpenAPI codegen.
- Prompt caching = stable prefix (
cache_controlbreakpoint): stable core (world hard rules + fingerprint) insystembefore the breakpoint, volatile state (latest_state + outline) after it. Never put timestamps/UUIDs in the cached prefix.
Development practices (开发模式) — non-negotiable
TDD — test-first, always
- RED → GREEN → REFACTOR. Write the failing test before the implementation. Target ≥80% coverage (global rule).
- Mock the LLM in every unit/integration test — inject a fake gateway/provider returning fixtures. Tests must be deterministic, fast, cost-free; never hit a real LLM API in tests/CI.
- Scope: Unit (one node/function/Repository, mocked IO) → Integration (FastAPI endpoint + DB + mocked gateway; LangGraph flow paths) → E2E (write→review→accept with mock gateway).
- Run a single test:
pytest path/to/test.py::test_name -q(backend) ·vitest run -t "name"(frontend).
Python
- async-first: FastAPI async endpoints, SQLAlchemy 2.0 async, async gateway calls. Never block the event loop (no sync DB/HTTP in async paths).
- Full type hints;
mypy+ruff(lint+format) clean before commit. snake_case everywhere. - Pydantic at every boundary (request/response, structured LLM output, config).
- Depend on Repository/gateway interfaces, not concrete impls, so tests inject fakes (ARCHITECTURE §3.5). Small focused functions/files; immutable updates (return new objects, per global coding-style rule).
LangGraph (orchestrator)
- Nodes are small deterministic units; keep LLM nondeterminism behind the gateway so node logic is testable. Test nodes by calling the node function directly (no graph runtime) + test flow paths with a mocked gateway.
- Put
interrupt()(the accept HITL pause) at the start of the node — don't duplicate logic or mutate state before interrupting. - Use the Postgres checkpointer (durable/resumable across the write→accept request gap). Run
checkpointer.setup()in migrations/CI, not in app runtime. - Transient-failure retries (backoff/tenacity) live in the gateway (§4.5); a node surfaces a clean failure, never loops. On resume, re-read
chapters/chapter_reviewsfrom DB (checkpoint = control-flow only).
Frontend
- TS strict; use the OpenAPI-generated client — never hand-write API types (regenerate on backend schema change).
- Server Components for read views; Client Components for editor/streaming/interaction.
- SSE: handle every event type in the stream contract (ARCHITECTURE §7 /
memory/contracts.md— the authoritative event list); "stop" = abort the connection; reconnect resumes from the saved draft. - Optimistic updates with rollback + toast on failure; respect
prefers-reduced-motion; meet the a11y bar inUX_SPEC.md §10.
Logging (埋好 log,便于查错) — wire from day one
Aligns ARCHITECTURE.md §9.3. This is how every failure gets diagnosed.
- Structured logs (
structlog; JSON outside dev). Neverprint. - Correlation id per request: generate/propagate
request_id; one "write a chapter" = one trace spanning assemble→write→4 reviews→accept. Putrequest_idon every log line and in the error response envelope so a user-reported error is greppable end-to-end. - Log every LLM call with the full call-metadata field set — feeds the cost ledger; the authoritative field list lives in
ARCHITECTURE §4.8(don't duplicate it here). - Log graph transitions (node enter/exit, interrupt, resume) tagged with
project_id/chapter_no/request_id. - Errors logged with full context at the boundary, mapped to the error envelope (
ARCHITECTURE §7.1is the source of truth for envelope shape + error codes). Frontend logs client errors with the samerequest_id. - Redact: never log API keys; truncate/hash full prompts and manuscript text (log lengths/hashes, not the novel).
Definition of Done (每次编码完成的硬门禁) — 不通过不算完成
每一次代码改动(无论多小)在声明「完成」前,必须按顺序全部通过以下门禁。任一项失败就回去修,不得跳过、不得只跑部分、不得仅靠肉眼判断。
- TDD 已遵守 — 先写失败测试(RED)→ 实现到通过(GREEN)→ 重构(REFACTOR)。新增/修改的逻辑都有对应测试;改 bug 先写能复现的失败测试。见上文 §TDD。
- 测试覆盖率 ≥ 80%(已工具化 + CI 强制) — 改动涉及的模块覆盖率不得低于 80%;新代码不许拉低整体覆盖率。跑命令核对真实数字,不要估:
- 后端:
uv run pytest -q --cov --cov-report=term-missing --cov-fail-under=80(低于 80% 直接非零退出。当前基线 ~88%)。配置在根pyproject.toml的[tool.coverage.*]。 - 前端:
cd apps/web && pnpm test:coverage(vitest.config.ts设 lines/functions/branches/statements 阈值 80。当前基线 ~93%)。 - ⚠️ 前端覆盖率范围 =
lib/**纯逻辑层:组件(.tsx)由 E2E/人工验收覆盖;React hooks(use*.ts)暂排除(node-only 测试栈测不了)。待办(@frontend/@qa):接入 jsdom +@testing-library/react,把 hooks 纳入 vitest 与 80% 门禁,并在vitest.config.ts移除use*.ts排除。
- 后端:
- 跑完三类自动化测试(改动触及的那侧必跑,跨栈改动两侧都跑):
- 后端 Unit + Integration:
uv run pytest -q(需要 pg 的集成测试先docker compose up -d pg)。 - 前端自动化测试:
cd apps/web && pnpm test(vitest)。 - E2E(动到 write→review→accept 主链路时):跑相关 E2E 流程(mock gateway)。
- LLM 一律 mock,绝不在测试/CI 命中真实 LLAPI(见上文 §TDD)。
- 后端 Unit + Integration:
- 全部门禁绿(与 §Toolchain、CI 一致):
- 后端:
uv run ruff check .·uv run ruff format .·uv run mypy packages apps·uv run pytest -q· 动过模型时uv run alembic check无漂移。 - 前端:
cd apps/web && pnpm lint·pnpm typecheck·pnpm test·pnpm build;动过后端 schema 先pnpm gen:api再校验。
- 后端:
- 符合架构与编程规范 — 不违反上文 §Architectural invariants、§Locked tech stack、§Python/§Frontend/§LangGraph 实践,以及全局编码风格(不可变更新、KISS/DRY/YAGNI、文件 <800 行、函数 <50 行、显式错误处理、边界校验、无硬编码密钥)。安全敏感改动(auth/输入处理/DB 查询/外部 API/加密)按 §code-review 触发安全自查。
- 提交(每次完成都 commit) — 见下文 §Commit discipline。
- 回写状态/文档 — 按 §Per-task workflow 更新
PROGRESS.md;若实现暴露 spec 缺口,回写对应 spec(见 §Conventions)。
报告要诚实:测试失败就如实说明并贴输出;跳过了哪一步要讲明;只有真正跑过且全绿,才说「完成」。
Commit discipline (提交纪律) — 每次完成即提交
- 每次完成一个可工作的改动就 commit,小步提交、一次一个聚焦改动;不要把多个无关改动堆进一个提交。
- 提交前 Definition of Done 的门禁(1–5)必须已全绿——不提交跑不过测试或 lint/类型不干净的代码。
- 提交信息格式:遵循 Conventional Commits ——
<type>: <description>(type ∈ feat/fix/refactor/docs/test/chore/perf/ci),中文描述与本仓库现有提交风格一致;需要时附 body 说明「为什么」。 - 提交信息禁止任何署名/归属尾注 — 不得包含
Co-Authored-By:或任何Generated with/ AI 署名行(全局已在~/.claude/settings.json关闭 attribution;此处再次明确,覆盖任何默认加尾注的行为)。 - 分支:不在
main上直接提交;按 §Git Workflow 在特性分支工作。develop为当前集成分支。 - push / 开 PR 仅在用户明确要求时进行;PR body 同样不加 AI 署名。
Toolchain (Phase 0 已落地)
工具:uv(Python workspace,4 members: apps/api + packages/{shared,config,db}) + pnpm(前端,apps/web)。Python 3.12+,Node 22。
- 环境变量:
uv装到~/.local/bin,命令前export PATH="$HOME/.local/bin:$PATH"。 - 依赖安装:
uv sync(后端,仓库根);pnpm install(前端,cd apps/web)。 - 本地起服务:
docker compose up(pg + api + web)。仅起库:docker compose up -d pg。裸跑 API:uv run uvicorn ww_api.main:app --reload。 - 迁移:
uv run alembic upgrade head;改模型后uv run alembic revision --autogenerate -m "...";漂移校验uv run alembic check(需 pg 在跑)。 - 后端门禁:
uv run ruff check .·uv run ruff format .·uv run mypy packages apps·uv run pytest -q。覆盖率门禁:uv run pytest -q --cov --cov-report=term-missing --cov-fail-under=80。单测:uv run pytest path::test -q(单测不带--cov,避免 fail-under 误报)。 - 前端门禁(
cd apps/web):pnpm lint·pnpm typecheck·pnpm test(vitest)·pnpm build。覆盖率门禁:pnpm test:coverage(阈值 80%,范围lib/**)。 - 重生成 TS 客户端:改后端 schema 后
cd apps/web && pnpm gen:api(离线:uv run python -m ww_api.export_openapi→openapi-typescript生成lib/api/schema.d.ts)。 - pnpm 配置:在
apps/web/pnpm-workspace.yaml(pnpm 11 不再读 package.json/.npmrc)——含onlyBuiltDependencies白名单 +verifyDepsBeforeRun: false(见memory/gotchas.md)。 - CI:
.github/workflows/ci.yml(backend job 带 pg service 跑 ruff/mypy/alembic/pytest;frontend job 跑 gen:api/lint/typecheck/test)。
Conventions specific to this repo
- All four specs are kept mutually consistent. If implementation reveals a spec gap, update the spec(s) too — don't let code and spec drift. (The specs already track this discipline; see ARCHITECTURE's "待回写规格的发现" pattern.)
- Docs are in Chinese; keep new docs/comments consistent with surrounding language.
Multi-agent collaboration (多 Agent 协同)
DEV_PLAN tasks are tagged with discipline skills (@backend/@frontend/@db/@llm/@devops/@qa) so they can run in parallel. Coordination is file-based through two artifacts. Read both before starting any task.
PROGRESS.md— the single shared task ledger + chronological log. Claim, sequence, and report here.memory/— durable shared knowledge across sessions/agents:memory/contracts.md— the decoupling seams (OpenAPI endpoints, Pydantic/TS schemas, gateway/orchestrator interfaces). Contract-first: the owning agent registers a contract here and marks it稳定before dependent agents start.memory/decisions.md— implementation decisions not covered by the specs, with rationale (append-only, one decision per entry).memory/gotchas.md— pitfalls/conventions discovered while building, so others don't repeat them (append-only).
Directory ownership (sole-writer; others read-only)
Edit only files inside your skill's directories. This eliminates write conflicts. To change a file outside your scope, write a request entry in PROGRESS.md for the owner instead of editing it.
| Skill | Owns (sole writer) |
|---|---|
@db |
packages/db/ |
@llm |
packages/llm_gateway/, packages/agents/, packages/core/orchestrator/ |
@backend |
apps/api/, packages/core/{memory,domain}/, packages/skills/, packages/shared/, packages/config/ |
@frontend |
apps/web/ |
@devops |
repo-root config (docker-compose*, CI, pyproject.toml, package.json, Alembic config) |
@qa |
tests/ (integration/E2E). Unit tests live with the owning module and are written by that module's owner. |
packages/shared/ (Pydantic schemas) and apps/api OpenAPI are contracts — changing them requires a memory/contracts.md entry and a PROGRESS.md note so dependents re-generate the TS client / re-sync.
Per-task workflow (every agent, every task)
- Read
PROGRESS.md+ relevantmemory/files. - Check dependencies — if an upstream task isn't
✅, don't start; pick another or mark⛔ blocked. - Claim — set the task to
🔵 in-progress @<skill> <date>inPROGRESS.md. If already🔵by someone else, skip it. - Contract-first — if your task defines a contract, register it in
memory/contracts.mdand mark稳定before dependents proceed. If you consume one, build against the registered contract, not assumptions. - Work only in your owned directories, one file at a time.
- On completion — set
✅, append a one-line entry toPROGRESS.md's log (what changed + which contracts/files affected), and record any decision/gotcha inmemory/. - Honor the architectural invariants above — they are not negotiable for speed.
Memory hygiene
Append, don't rewrite history. One fact per entry. Don't duplicate what the specs or git already record — capture only what's non-obvious and useful to a sibling agent. Delete entries proven wrong.