# CLAUDE.md

## 定位

- 本文件是 Claude Code 在本仓库中的正式入口文档。
- `CLAUDE.md` 与 `AGENTS.md` 是并行入口，必须保持规则一致、结构对齐。
- 任一文件发生规则性修改时，另一份必须同步更新。
- 如 `CLAUDE.md`、`AGENTS.md` 与 `docs/` 冲突，以 `docs/` 为准，并立即同步修正两份入口文档。
- 详细标准放在 `docs/`；本文件只保留高频、稳定、必须始终遵守的规则。

## 必读入口

1. `docs/standards/00-product-philosophy.md`（元规则：二常驻 + 一临时；约束该不该做）
2. `docs/standards/05-development-workflow.md`
3. `docs/standards/06-documentation-system.md`
4. `docs/standards/14-feature-request-workflow.md`（需求工单工作流：状态机 + 派活规则 + PRD review 卡点）
5. `docs/standards/16-data-layering-and-metadata-policy.md`（数据分层 L1/L2/L3 + 默认不立元数据驱动 L4 + 加回触发条件）
6. `docs/standards/17-gitea-issue-handling.md`（Gitea 工单处理：接单/释放事实源 = assignee，跨工单类型通用）
7. `.agents/skills/README.md`

涉及具体模块时，先读模块 README 作为路由索引，再按"何时读取"列按需加载下游文档：
- `docs/modules/{module}/README.md`（路由索引，查看"何时读取"列决定加载哪些文件）
- `docs/modules/{module}/01-prd.md`
- `docs/modules/{module}/05-ui-interaction-spec.md`
- `docs/modules/{module}/06-data-model.md`
- `docs/modules/{module}/07-api.md`
- `docs/modules/{module}/09-test-scenarios.md`
- `docs/modules/{module}/04-state-machine.md`（如存在）

## 目录约定

- `docs/`：项目事实源与标准文档。
- `docs/standards/`：开发规范、文档体系、架构规则。
- `docs/modules/{module}/`：模块级 PRD / 数据模型 / API / 状态机 / 测试方案。
- `docs/ops/`：服务器架构、SSH/部署目录、运维策略、Gitea/SES/RAGFlow/SAP 等基础设施文档；**任何涉及 UAT/生产服务器、改 env、加凭据、改证书、查端口/PM2/日志位置的任务，先读这里**。
- `.agents/skills/`：团队唯一人工维护的共享 skills 源目录。
- `.claude/skills/`：Claude Code 分发目录（自动同步），不作为人工维护入口。
- 注：原 `.codex/skills/` 与 `.cursor/skills/` 已删除——团队当前仅使用 Claude Code 与 Codex CLI；Codex CLI 直接读 `.agents/skills/`，不再需要分发目录。
- `testing/reports/`：测试与验证报告目录。

修改 skills 时：
1. 先改 `.agents/skills/`
2. 再执行同步脚本
3. 不要直接编辑分发目录

修改 AI 入口文档时：
1. 如修改 `CLAUDE.md`，必须同步检查并更新 `AGENTS.md`
2. 如修改 `AGENTS.md`，必须同步检查并更新 `CLAUDE.md`
3. 不允许只更新单一入口文档后结束任务

## 核心不变量

### 1. 文档即契约
- 文档默认是事实来源。
- 未经明确指令，不得擅自改变 API、数据库模式、事件、鉴权、错误码等外部契约。
- 如需临时偏离，必须记录偏离点、原因与后续文档对齐建议。

### 2. 禁止静默决策
- 文档与代码冲突时，必须立刻停止。
- 明确列出差异、影响和可选方案。
- 等待用户确认后再继续。

### 3. 最小改动
- 优先最小可评审改动。
- 除非明确要求，避免重构。
- 不得通过隐式改动改变外部行为。

### 4. 必须可验证
- 每次变更都必须给出验证方式。
- 至少包含测试命令、检查命令或可复现步骤。
- 无法验证时必须明确说明原因和风险。

### 5. Git 安全
- 绝不自动提交。
- 绝不直接推送保护分支。
- 总结改动后，等待用户决定是否提交、推送或合并。

### 5a. 生产环境只读（铁律）
- **绝不直接修改任何生产环境内容**，包括但不限于：
  - 生产服务器上的代码、配置、`.env`、`pm2` 进程、Docker 容器/卷、Nginx 配置
  - 生产数据库里的任何数据或 schema
  - 生产分支（`production`）或其构建产物 `.next/`
- 生产服务器上**只允许只读操作**（`git log`、`pm2 logs --nostream`、`pm2 list`、`docker ps`、`docker logs`、`ls`、`cat`、`grep`、健康检查接口）。禁止 `git pull` / `pm2 restart` / `pm2 reload` / `docker restart` / 写文件 / 数据库写操作。
- 需要分析的文件（如 minified chunk、日志）应下载到本地再分析，不在生产机上落盘修改。
- 修复生产 bug 的正确路径：本地改 → hotfix 分支 → PR → CI → UAT 验证 → 合并 production → 走部署流程（`deploy-ops` skill）。
- 任何会对生产产生副作用的命令在执行前必须停下来向用户确认，即使用户先前授权过类似操作——授权按作用域生效，不跨场景延续。

### 6. 前后端契约对齐
- 新增或修改后端 API 后，必须验证返回字段名和结构与前端 TypeScript 接口一致。
- 状态转换类 API（publish、start-xxx 等）必须返回完整资源对象，不得只返回部分字段。
- 列表/概览类 API 在无数据时返回空结构，不抛异常。
- 详见 `.agents/skills/backend-main/references/api-contract-checklist.md`。

### 7. 零技术债倾向
- 优先正确的数据结构和清晰边界。
- 不接受"先绕过去以后再改"的默认方案。
- 避免前端用复杂逻辑弥补后端设计缺陷。

### 8. 统一使用中文
- 所有回复统一使用中文。

### 9. 持续优化 AI-first 工作方式
- 本项目默认以 AI 协作为主要开发方式。
- 如果发现文档、模板、skills、脚本、hooks 或流程存在更优方案，应主动指出。
- 涉及全局规则、契约或目录结构时，先说明影响并等待确认。
- **强制回顾触发条件**：完成以下任务后，AI 必须主动执行"回顾与规则沉淀"（默认工作方式第 6 步），不需要用户提醒：
  - 完成一个模块的后端开发
  - 完成一轮完整的测试（集成测试或 MCP 测试）
  - 修复了 3 个以上的 Bug
  - 对文档或规范做了大规模更新

### 10. Bug 解决用第一性原理
- 任何 bug / 事故按 3 层拆解（表面症状 → 直接原因 → 元根因），不许停在层 2。
- 修复分两层：**应用层** + **工程化保险**，缺工程化保险 = 没真正修好。
- 方法论 + P0/P1 复盘 6 段模板：`docs/standards/11-troubleshooting-methodology.md`
- 5 条元根因规则（契约面 / 依赖图 / 环境一致 / 实测完工 / 监控告警）：`docs/standards/12-five-meta-rules.md`

### 11. 外部 API 调用优先评估 CLI 化
- AI 准备 curl / 手写 HTTP 调用任何外部 API（Gitea / GitHub / SAP / 第三方）前，先停一拍问：会不会再调第二次？有没有 ID 硬编码（label id / user id / entity id）？错误信息要不要翻译成项目语境？library 已有但没 CLI 出口？
- 满足 ≥ 2 条 → **主动提议做成 CLI 工具或扩展现有 CLI**，不要默默手写第 N 次 boilerplate。提议时给摘要：当前痛点 + 调用模式 + 估计工作量 + 是否单 PR。
- **不立即开干**——告诉用户、等明示。
- 设计规范：`docs/standards/15-cli-design-spec.md`；当前 CLI 索引：`scripts/ops/gitea`（Gitea ops）。
- 反例：AI 凭印象硬编码 label id 把 #331 改错（`.learnings/2026-05-14-gitea-label-id-hardcoding-trap.md`）——CLI 强制 name-based 解析后这种坑关闭。

### 12. 项目相关知识落项目文件，不落 AI 个人 memory
- 任何 AI 推断或用户告知的**项目级**规则 / 约定 / 标准 / 流程 / 工具索引 / 反模式 / 踩坑，**必须**写到项目文件（`CLAUDE.md` / `AGENTS.md` / `docs/standards/` / `docs/modules/` / `.learnings/`），而不是 AI 自己的 `~/.claude/.../memory/`。
- 理由：项目文件是**所有 AI（Claude / Codex / 其他）和团队成员都能看到**的 source of truth；AI 个人 memory 只有当前那一份 AI 实例能看到，换 session / 换 AI 就丢，无法对齐。
- **落地决策树**：
  - 项目规则 / 约定 / 高频 / 跨任务 → `CLAUDE.md` + `AGENTS.md`（必须同步）
  - 项目详细标准 / 流程 → `docs/standards/`
  - 模块业务知识 → `docs/modules/{module}/`
  - 项目级踩坑 / 复盘 → `.learnings/`
  - 用户个人偏好 / 跨项目通用 / 用户环境（SSH 拓扑等）/ AI 行为反馈 → AI 个人 memory
- 判断准则：**"换一个 AI 来做这个项目，需不需要知道这条？"**——需要 → 项目文件；只跟当前用户跨项目习惯有关 → memory。
- 反例（已修正）：2026-05-14 把"AI 调外部 API 前评估 CLI 化"先落 memory，应该直接落 CLAUDE.md #11；user 当场指正后迁移到项目文件。

### 13. 数据分层与元数据策略
- 业务数据按三层架构：**L1 平台公共**（Customer/Supplier/Location/字典）/ **L2 领域主**（模块专属、仅不变属性）/ **L3 业务交易**（流程产生、事件源）。依赖方向单向，反向走事件流。
- **默认不立 L4 元数据驱动层**（FieldDef/StatusDef/WorkflowDef/GuardDef）：AI 时代加字段 30 分钟，元数据驱动绕开"工程师瓶颈"的原始价值已失效，反而损失类型安全 + AI 协作效率 + 调试链路。字段全部强类型落 prisma schema；状态用 enum；状态转换硬编码常量。
- **L2 核心实体强制"状态 vs 不变属性"分离**：状态字段（currentStatus / currentLocationId 等）必须剥离到 L3 事件 + 物化 Snapshot（同事务刷），禁止挂在 L2 实体本身。
- **跨模块写 L2 实体必须走事件流**：模块 A 不得直接 import 模块 B 的 prisma client 写 B 的主数据；emit 事件 + B 自己消费。
- 加回 L4 的明确触发条件（满足任一）：SaaS 多租户化 / 业务运营要在生产 UI 自助加字段 / 状态机租户级定制 / 跨 ≥3 个模块字段定义真重复。**禁止以"未来可能要灵活"为由立 L4**。
- 详见 `docs/standards/16-data-layering-and-metadata-policy.md`。

### 14. 主仓库工作目录只读（铁律）
- **主仓库工作目录（`<repo>/`，非 `<repo>-wt/<name>/` 命名 worktree、非 `.agent-pool/slot-*/`）的 git 状态必须永远 `nothing to commit, working tree clean`**。
- **禁止**在主仓库做：Edit/Write 任何文件（含 untracked 截图 / 临时脚本 / tmp 文件）、`git commit/stash/branch -d` 等修改类操作。
- **允许**在主仓库做：`git fetch/pull --ff-only/checkout`（同步类）、`git status/log/diff/show/blame`（只读查询）、运行不写入仓库根的命令。
- 任何"改"必须先 `claim` 一个 slot（或开命名 worktree）→ 在 slot 里做 → 主仓库永远只是事实快照。
- **理由**：主仓库 dirty → `git pull` 冲突 → 长期不同步 → 本地 `origin/*` ref 和工作目录代码都落后远端 → 任何"读代码 / 读 ref 推理"基准全部过期。详见 `.learnings/2026-05-19-main-repo-drift-meta-rootcause.md`（3 次连环踩坑全记录）。
- **机制保险**：`UserPromptSubmit` hook（`scripts/ops/hooks/main-repo-readonly-guard.sh`）每条消息检测——主仓库 dirty 输出强警告 system-reminder；干净则后台 `git fetch + pull --ff-only`（5 分钟节流）。AI 偶尔忘了铁律 → hook 立刻把现状摆出来。
- 历史 untracked 文件（截图 / 调试产物 / leak learning）按"是真工作 → claim slot 挪 / 是临时文件 → 移到 `/tmp` / 是别人 leak → 给用户审"分类处理，**不允许"先放着以后再说"**。

## 开发与文档规则摘要

### 文档
- **文档写决策和约束，不写操作步骤。** 操作交给 AI 和 skills。
- 无文档不实现；缺必要文档时先补最小占位文档。
- 模块文档统一放在 `docs/modules/{module}/`。
- 代码能表达的不写文档（版本看 `package.json`，结构看 `schema`）。
- 文档模板和写作细则以 `docs/standards/06-documentation-system.md` 为准。

### 后端
- 控制器只处理 HTTP，请求外的业务逻辑放服务层。
- 仓储层只做持久化。
- 修改后端功能前，先读对应模块的 PRD、数据模型、API、状态机文档。
- **业务模块对 agent 暴露能力（`@AgentTool` 装饰器 / `AgentToolsModule.forFeature`）必须读 [`docs/standards/21-agent-business-module-integration.md`](docs/standards/21-agent-business-module-integration.md)**：12 条接入标准 + 反模式清单 + CI 强制项 + inputSchema v1.0 限制（扁平 3 类型，嵌套用 string + JSON.parse）。新业务 service 加 `@AgentTool` 时**必须**同步在模块 `07-api.md` 标注"对 agent 暴露"。

### 前端
- 用户可见文案必须国际化。
- 视觉与交互遵循现有设计系统和标准文档。
- 未文档化的导航、结构或设计系统改动，不要直接落代码。

### 数据库
- Prisma schema 变更必须走标准迁移流程。
- 禁止修改已应用或已合并的迁移。
- **每次提交最多包含一个迁移文件**：一个功能涉及多个 schema 变更时，合并为一个迁移文件；禁止一个提交中包含多个迁移文件。
- 生产数据库不能手工改。
- **新建业务表强制字段**：`id` / `createdAt` / `updatedAt` / `createdById` / `organizationId`，按需加 `departmentId` / `regionId`。命中即 DataScope 零配置。禁用 `creatorId` / `ownerId` / `orgId` 等同义词。详见 `docs/standards/04-database-architecture.md` 「标准字段」。
- **Schema PR / 模块 audit 必查 22 条原则清单**：`docs/standards/20-database-review-checklist.md`（项目特有 11 条 + 业界通用 11 条 + 严重度分级 + 迁移安全 checklist + Audit 模板）。

### 环境变量
- **新增 `process.env.XXX` 引用必须同步加到 `.env.example`**，附注释说明用途、默认值、缺失症状。CI 强制（`scripts/ops/check-env-coverage.sh`）。
- 启动型 env（端口、数据库名）由 `scripts/dev/setup-worktree.sh` 注入，不进 `.env.example`。
- 历史遗留漂移在 `scripts/ops/env-coverage-baseline.txt`，新 PR 不应往里加，鼓励逐项清理。

### 测试（三层全覆盖：契约 / 集成 / 端到端）

**测试金字塔三大层**（详见 `docs/standards/05-development-workflow.md`）：
- **契约层** L0a/L0b/L0c — 字段对不对
- **集成层** L1（L1a + L1b）/ L1c — 后端跑得对不对
- **端到端层** L2 / L3 — 用户用起来对不对

> L0a/L0b/L0c/L1a/L1b/L1c 是子项命名（向后兼容历史报告）。L0 页面清点是测试范围规划，不是测试本身。

**执行顺序：L0 → L0a/L0b → L0c → L1 → L1c → L2 → L3，前一项未通过则后一项无意义。**

- L0（页面清点）：扫描前端可达页面和 API 调用，定义后续所有项的测试范围。
- L0a/L0b（契约校验）：静态脚本对比前端请求/响应字段 ↔ 后端 DTO/返回字段，捕获字段名不匹配。
- L0c（响应快照校验）：实际发请求，对比真实响应结构 vs 前端 interface。**L2 CI 阶段作为 PR 门禁**，复用 backend container 几乎零增量成本。
- L1（集成测试）：HTTP → 真实数据库，覆盖业务逻辑（L1a API 结构 + L1b 业务规则）、状态机、权限。
- L1 必须连接独立测试数据库，禁止复用开发数据库；推荐通过 Docker 自动拉起测试库。
- L1c（数据质量校验）：校验**种子数据 / 脱敏快照**的 JSONB 结构完整性和 FK 引用有效性。**禁止直连生产 DB**，仅可连脱敏快照、staging snapshot 或生产 read-only 监控视图。每层用不同数据源：L1 用 seed / L2 用 test 环境累积 / L3 用 UAT 验收库 / L4 用生产 read-only。
- L2（E2E 测试）：Playwright MCP 走业务流程，关闭 mock，验证前后端连通和交互逻辑。
  - **L2 在 test 环境（develop 分支 merge 后）不跑** — AI 高频合并（10+ 次/天）跑 E2E 会拖死 CI；E2E 全部留给 L3 兜底。
  - **L1 本地阶段** AI 改了 frontend 时跑改动页 MCP（窄而快）；**L3 UAT 阶段** 跑全量 + 双语回归。
  - **本次开发范围必须全覆盖**：从 git diff 提取本次新增/修改的页面/按钮/操作，逐项过（含正常流 + 边界 + 空数据 + 无权限态），不依赖 `10-e2e-test-spec.md` 是否列全。
  - **i18n 双语验证**：每次必须切 zh-CN ↔ en-US 两套 locale，验证关键文案无硬编码、控制台无 missing key warning、日期/数字按 locale 格式化。
- L3（人工验收）：用户按页面级验收清单逐项确认视觉、体验、边界；含本次开发按钮逐项手工点一遍、切换语言后文案完整一致。
- 后端以集成测试为主，不写单元测试（除非有复杂纯计算逻辑）。
- 现有后端单元测试视为历史遗留资产，不作为默认新增或默认维护要求；仅纯逻辑、高价值规则按需保留或新增。
- 前端不写组件测试，连通性验证通过 Playwright MCP 完成。
- MCP 使用 accessibility tree 定位元素（role/name），不依赖 data-testid。
- E2E 通过 AI + MCP 执行，不编写新的 E2E 测试代码。
- **任何 API 变更必须先过 L0a/L0b/L0c 再跑 L1。**
- **协议级 / 客户端集成功能：必须用真实客户端跑（curl 不算）**：实现 / 改动外部协议（MCP / OAuth / WebSocket / gRPC / SSE 等）或机器对机器集成，curl + Postman + 看 JSON 字段**不算端到端**——必须用该协议官方客户端真实跑一次握手 + 业务调用，验证证据要落地（截图 / log / `claude mcp list` 输出）。背景：internal-app-platform PoC 只用 curl 验收，整个 MCP 链路在真实 Claude Code 上完全不可用，直到 2026-05-18 真跑通才暴露。详见 `docs/standards/05-development-workflow.md` § "协议级 / 客户端集成功能" + `.learnings/2026-05-18-mcp-controller-not-jsonrpc-compliant.md`。
- **API 变更必须同步测试**：修改后端 API 的 HTTP method、路径或响应结构时，必须同步更新 `testing/backend/integration/` 下对应的测试文件。
- **模块重构后评估测试**：模块发生大规模重构后，必须评估对应集成测试的有效性，过时的测试删除或重写。
- **测试数据隔离**：集成测试中创建的资源必须使用随机标识（`Date.now()` + 随机后缀），禁止硬编码固定名称（如 `'Test Organization'`），避免测试间唯一约束冲突。
- **测试数据生命周期**：
  - 种子数据（角色、权限、岗位等）由 `seed.ts` 在 `force-reset` 时创建一次，全程不删。
  - 测试数据由每个 `beforeEach` 创建，cleanup 时只删测试数据、保留种子。
  - **cleanup 优先用前缀过滤**（`WHERE code/name LIKE 't_%'`），schema 增删表零维护负担；按表精确 DELETE 是历史方案，仅旧模块沿用；禁止 `TRUNCATE`。
  - raw SQL 的 `.catch()` 必须输出错误信息（`console.warn`），禁止空 catch。
  - 写 raw SQL 时必须查实际表名（Prisma model 名 ≠ PostgreSQL 表名，用 `@@map` 定义）。
- 契约校验脚本：`npx ts-node --transpile-only testing/scripts/contract-check.ts`
- 测试报告放在 `testing/reports/`。

详细规则见：
- `docs/standards/05-development-workflow.md`
- `.agents/skills/test-backend/references/testing-standards.md`
- `.agents/skills/frontend-main/references/frontend-standards.md`
- `.agents/skills/database-main/references/database-standards.md`

## Git 规则摘要

- `develop`、`staging`、`production` 都是保护分支。
- 三者均禁止直接推送与强推，必须通过 PR 合并。
- 功能开发使用 `feature/*`、`bugfix/*`、`hotfix/*` 分支。
- **一个分支只做一件事，PR merge 后立即删**：本地 `git branch -D <task>` + 远端走 Gitea 仓库设置的 auto-delete-on-merge。详见 `docs/standards/05-development-workflow.md` 「分支生命周期与四道防线」段。
- **本机一次性配置**：`git config --global fetch.prune true`（每次 fetch 自动清远端已删的本地 tracking ref）。
- **周期性兜底（本地 gone）**：`bash scripts/dev/sweep-stale-branches.sh` 找 `: gone]` 本地分支批量清，默认 dry-run。
- **周期性兜底（远端 stale）**：`python3 scripts/ops/sweep-remote-stale.py` 扫远端 ≥14d 且内容已被吸收（L1 merge-base / L2 patch-id 等价）的分支，按提交人分组 → `--post-issue` @ 出本人自删，`--sweep-orphan --execute` 代删无主（CI bot）分支。≥30d 未被吸收的分支进 `## 长期未处理` 段提醒作者处理。默认 dry-run，审计日志写 `testing/reports/stale-cleanup/`。**Gitea cron 自动每周一 09:13 跑**（`.gitea/workflows/weekly-stale-sweep.yml`），空扫静默跳过不刷屏。
- 禁止在保护分支直接提交。
- 提交信息使用约定式提交，主题用中文。
- **提交前必须运行 `/simplify`**：在执行 `git commit` 之前，必须先对本次变更的代码执行 `/simplify` 检查，修复发现的问题后再提交。
- **提交前检查迁移文件数量**：确认本次提交中 `prisma/migrations/` 下最多只有一个新增的迁移目录；若有多个，必须合并后再提交。
- **提交前必须通过构建检查**：在提交前运行 `cd backend && npm run build` 和 `cd frontend && npm run build`，确保前后端构建无报错。构建失败必须修复后再提交。
- **本次 PR 相关的踩坑沉淀必须同 PR commit**：调试中发现的 ERR/learning 必须**在 git commit 之前**写入 `.learnings/`，跟代码改动同一个 commit / 同一个 PR push 上去；不允许等 PR merge 后再补。下次开 PR 时容易忘 + 信息已经凉。例外：跟功能 PR 完全无关的中立笔记（团队约定、思考、复盘）可以走 `chore/notes-rolling` 批量。
- **提交后询问是否创建 PR**：`git push` 成功后，主动询问用户是否需要创建 PR。

### PR 拆分准则（按主题与风险，不按规模）

**拆 PR 看的是主题数和风险等级，不是文件数、commit 数或时间。** 一个 PR 100 文件只要主题单一、风险一致，review 比 3 个主题混杂的小 PR 轻松；2 文件如果一个改 schema 一个改 UI 文案该拆，跟"小"无关。

**默认不拆**——AI 节奏下拆 PR 的切换成本（CI / review / merge / rebase）大概率超过"小 PR 好 review"的收益。**只在以下两条硬触发上拆**：

1. **主题不同必拆**：一个 PR 出现 ≥ 2 个**无关主题**（不同功能 / 不同模块 / 改动动机不同）→ 按主题拆。判据是"reviewer 读 commit message 能否一句话讲清楚这个 PR 干啥"——讲不清 = 主题不止一个。
2. **高风险路径单独 PR**（即使主题相关也抽出去）：
   - `prisma/schema/**`、`prisma/migrations/**`（DB schema）
   - `docs/standards/**`、`CLAUDE.md`、`AGENTS.md`（项目契约）
   - `.gitea/workflows/**`（CI 自身）
   - 任何 API 契约面（路径/方法/请求/响应字段/错误码）
   - 性能关键代码 / 热路径
   - 理由：回滚代价高 + 影响范围广，单 PR 让 review 集中、bisect 干净

**不再用的旧阈值**（已废弃，AI / ai-review 都不应再据此建议拆分）：~~时间窗 ≥ 1 小时~~ / ~~≥ 5 commits~~ / ~~≥ 8 文件~~。规模本身**不是**拆分信号——大而单一的 PR 完全可以接受。

但仍然**不要每个 commit 一个 PR**——AI 节奏下 10 分钟 10 个 PR 会把 CI 和 reviewer 拖垮。多个小 commit 在同一主题下批量进一个 PR 是正确做法。
- `feature/* → develop` 默认 **squash**（develop 历史线干净）。
- `develop → staging` 和 `staging → production` 用 **fast-forward-only merge**（环境指针推进，禁止 merge commit）。**必须用 `scripts/ops/gitea promote uat|prod`，禁止 Gitea web UI 手点合并**（UI 默认 merge style 是 squash，曾误点造成 PR #383 事故；CLI 把 `Do=fast-forward-only` 写死 + post-merge 校验 base tip == head sha）。详见 `docs/standards/05-development-workflow.md` § 「环境升级合并策略（FF-only）」。
- 仓库级 merge styles 已收敛为只剩 `squash` + `fast-forward-only`，`merge` / `rebase` / `rebase-merge` 已关闭以防误点。
- **存在 rolling 分支**（如 `chore/notes-rolling`）专门收纳 `.learnings/` 笔记，定期批量 PR——这种"中立内容 + 高频小更新"场景适合 batching，跟功能 PR 不同

### Gitea 平台配置

- 地址：`http://43.130.59.228`（Gitea，非 GitHub）
- SSH：`ssh://git@43.130.59.228:2222`
- 仓库：`FFAIWorkspace/workspace`
- 用户：`hongwei.zhang`
- **API 认证（重要）**：优先使用本机环境变量 `$GITEA_API_TOKEN`（在 `~/.bashrc` 或 `~/.zshrc` 中配置）。若不存在，再从 `git credential store` 读取：
  ```bash
  # 优先：直接用环境变量
  GITEA_TOKEN="${GITEA_API_TOKEN}"

  # 备选：从 credential store 读
  GITEA_TOKEN=$(git credential fill <<< $'protocol=http\nhost=43.130.59.228\n' 2>/dev/null | grep password | cut -d= -f2)

  # 认证：Authorization: token $GITEA_TOKEN
  ```
  说明：两种方式拿到的 token 均有完整 API 写权限（PR 创建 / 合并 / 分支保护修改）。
- 没有 `gh` 或 `tea` CLI，使用 `curl` + Gitea REST API
- 创建 PR：`POST http://43.130.59.228/api/v1/repos/FFAIWorkspace/workspace/pulls`
- **PR 合并规则（强制）**：**禁止 PR 作者批/合自己的 PR**。当前分支保护实际配置（2026-05-19 API 实测）：`develop` (`required_approvals=0` + `enable_status_check=True` + 6 required check + `block_on_outdated_branch=True`，0 approvals 是因为 AI auto-merge 走 AIBot token 不需要 self-approve；outdated=True 把"PR-B 必须基于含 PR-A 的最新 develop 才能合"变成硬门禁，auto-merge daemon 自动 update PR 分支让这层门禁对作者无感)；`staging` / `production` (`required_approvals=1` + `block_on_outdated_branch=True`)。**不允许通过脚本临时放宽**（旧的 `scripts/ops/gitea-pr-merge.py` relax-dance 脚本已在 PR #304 删除，理由见 `.learnings/2026-05-11-no-self-merge-policy.md`）。完整设计理由 + 各字段语义见 [`docs/standards/16-gitea-actions-platform-semantics.md`](docs/standards/16-gitea-actions-platform-semantics.md) §10。**配置变更需同步三处：CLAUDE.md / AGENTS.md / docs/standards/16-...md §10 表格，防漂移**。
- **例外：env-promote PR 允许 self-approve**——`develop→staging` / `staging→production` 这类 PR 由 `gitea promote` CLI 创建并自动 self-approve，因为：（1）合并动作纯机械（FF-only，base 严格等于 head，无逻辑判断空间）；（2）token 已限制谁能调用（merge_whitelist：chentao.jia / hongwei.zhang）；（3）feature PR 的 review 价值在 develop 阶段已实现，env 升级只是指针推进。**feature PR 仍严禁自合**。
- **合并路径**：
  - **AI 自动合（默认，feature/* → develop）**：**两台 dedicated runner（43.166.205.48 + 43.166.182.155）上的 systemd timer + auto-merge daemon**，错峰 2 分钟（`*:0/5` + `*:2/5`）= effective 2-3 分钟反应窗口 + 双机 HA。**严格 verdict=pass + 所有 required check 通过 + 无 REQUEST_CHANGES + 无 `do-not-auto-merge` label** 才合（squash）。同时把"7/7 但 base outdated"的 PR 自动 `update_branch`（解决 PR 队列）。AIBot 不是 PR 作者，不违反 no-self-merge 规则。判定逻辑详见 [`scripts/ops/auto-merge-develop.py`](scripts/ops/auto-merge-develop.py)；架构 + 部署 + 故障模式见 [`docs/ops/03-auto-merge-daemon.md`](docs/ops/03-auto-merge-daemon.md)。**应急手动入口**保留 [`.gitea/workflows/auto-merge.yml`](.gitea/workflows/auto-merge.yml) 的 `workflow_dispatch:`（两台 daemon 同时挂时从 Gitea UI 触发）。
  - **作者逃生口**：想自己再补 commit / 想人工 review 的 PR，加 label `do-not-auto-merge`。
  - **人工兜底（备用）**：自动合判定不过的 PR（verdict=pass_with_risk / block / check 失败），由另一位团队成员在 Gitea web UI 点 Approve → Merge。不允许作者自合。
  - **不适用范围**：`develop → staging` 和 `staging → production` 仍走 `gitea promote` CLI（FF-only），不自动。
- **常见报错**：
  - `405 The head branch is behind the base branch` → 等 auto-merge daemon 自动 `update_branch`（5 min 内），或在 Gitea web UI 点 "Update branch"（`block_on_outdated_branch=True` 三个保护分支都开）
  - `405 Please try again later` → mergeable 缓存未刷新，等 30s 重试
- **共享 Gitea API helper**：`scripts/ops/_gitea_api.py` 提供 `Api` / `get_token` / `detect_repo` 等，所有 ops 脚本（`weekly-review.py` / `weekly-retro-issue.py` / `sweep-remote-stale.py`）通过 `from _gitea_api import ...` 复用。**禁止在新脚本里重抄一遍 Api class**。
- **AI / 团队成员日常 Gitea 操作优先用 `scripts/ops/gitea` CLI**，而不是手写 curl + python heredoc。子命令：`gitea issue <num>` / `gitea issue list --label X` / `gitea issue comment <num> --body "..."` / `gitea label add/remove <num> <name>` / `gitea pr <num>`；支持 `--json` 机读模式、`--dry-run`、结构化错误。详见 [`docs/standards/15-cli-design-spec.md`](docs/standards/15-cli-design-spec.md)（CLI 设计规范，写新 CLI 时必读）。
- JSON body 复杂时用 Python 生成，避免 shell 转义坑（**仅在 `gitea` CLI 不覆盖的场景**）

详细规则见：
- `docs/standards/05-development-workflow.md`
- `docs/standards/agent-assets-workflow.md`

## Skills 规则

- 是否使用某个 skill，以 `.agents/skills/README.md` 和对应 `SKILL.md` 为准。
- skill 的职责是提供可复用工作流，不是替代 `docs/` 里的事实源。
- 需求改动时，先更新文档，再做代码实现。

## 本地启动约定

- 本地开发优先使用 `scripts/dev/dev.sh` 作为统一入口，不要先假设使用 `scripts/deploy/deploy.sh`。
- `scripts/deploy/deploy.sh` 主要用于 `dev/uat/production` 的部署与运维编排；日常本地开发启动优先使用 `scripts/dev/dev.sh`。
- 如果只是把项目在本机跑起来，优先检查并执行：
  1. `bash scripts/dev/dev.sh up`
  2. `cd backend && npm run prisma:generate`
  3. `cd backend && npm run db:push`
  4. `cd backend && npm run db:seed`  ← 首次/空库必须：种入权限/角色/岗位/测试用户。跳过这步 start:dev 能起来但登不进去
  5. `cd backend && npm run init:itadmin`  ← `db:seed` **不创建 itadmin**（2026-05 新机器部署实测确认），必须额外跑这一步才能登录
  6. `cd backend && npm run start:dev`
  7. `cd frontend && npm run dev`
- `itadmin` 默认密码见 `scripts/backend/init/init-itadmin.ts` 顶部（`Admin@2024`，首次登录后改）。`init:itadmin` **当前未废弃**——`db:seed` 不创建 itadmin。已废弃脚本：`npm run init:permissions`（仍可单独跑）。
- 若 `.env` 只够应用读取、不够 `docker compose` 读取，先补 `POSTGRES_PORT`、`CONTAINER_PREFIX` 等 Docker 端口/容器前缀变量，或在命令行临时传入后再启动容器。具体报错表现：`_postgres_data` / `-network` 等带前缀分隔符的名字被 docker 拒绝（`invalid characters for a local volume name`）——这就是 compose 读到空字符串把 `${CONTAINER_PREFIX}_postgres_data` 拼成了 `_postgres_data`。

## Agent Pool（开发 agent 池，新工作方式）

新功能或临时任务的**默认入口**是 agent pool，而不是手工 `setup-worktree.sh`。池预先把 `npm install` / PG/Redis 容器 / .env / Caddy 域名都准备好，每次 claim 一个 slot 是秒级（旧方式 80-90 秒，加速 ~140×）。

- **机制原理 / 不变量 / 设计决策**：[`docs/standards/10-agent-pool.md`](docs/standards/10-agent-pool.md)（事实源）
- **工具参考（命令 / env / 调试 / 测试）**：[`scripts/dev/agent-pool/README.md`](scripts/dev/agent-pool/README.md)

**IDE 入口（VSCode multi-root + Remote-SSH）**：`pool-init` / `pool-resize` /
`agent-claim` / `agent-release` 自动维护 `<repo-parent>/<repo>.code-workspace` 文件，
列 main + 全部 slot。本地 VSCode 通过 Remote-SSH 打开此文件，**单窗口同时看到 main +
全部 slot 的改动**——多 agent 并行时一个 IDE 即可。详见 README §「VSCode multi-root
+ Remote-SSH 入口」。

**池路径解析**：池根 = `$FFOA_AGENT_POOL_ROOT` > `<repo-parent>/<repo>-wt/.agent-pool`（新规则）> `<repo-parent>/ffworkspace-wt/.agent-pool`（legacy 兼容）> 默认按新规则建。详见 README §「路径解析顺序」。

### Agent 自报家门（每轮启动必做）

按优先级判断当前位置：

1. `$FFOA_AGENT_SLOT` 已 export → 在某 slot 里（slot 编号、目录、端口都已注入），可放心改动
2. `pwd` 匹配 `*/.agent-pool/slot-*` → 同上（fallback）
3. 都没有，且当前在主仓库（`<repo>/`）→ **拒绝任何需求改动**（写代码 / 写 docs / 写 skill / 写 test / 提交 / 改 `.env` / merge / prisma migrate / db:reset 等任何写动作）。**池存在前提下必须先 claim slot**；池未初始化（exit 4）**必须先 init 池**再 claim，不接受"反正改动小、直接在主仓库干"。**例外**（仅以下可在主仓库做）：只读探索（`git log` / `grep` / 看文档 / `agent-status.sh`）、跨 slot 协调（把 develop 同步到主仓库 + 看整体状态）、claim 失败的诊断（看 lock / sweep）。命名 worktree（`<repo>-wt/<feature-name>/` 这种长期 feature 沙箱）算"在 worktree 里"，可以做需求

### 入口决策（开新任务时）

**第一步永远是尝试 claim**，**根据 exit code 决策，不要把命令抛回给用户**——执行者是 AI：

```bash
bash scripts/dev/agent-pool/agent-claim.sh <task-branch> [<base>]
```

| Exit | 含义 | AI 应该做 |
|---|---|---|
| `0` | 成功，stdout 有 export 行 | `eval` 后开干 |
| `2` | 同分支已被别的 slot 占用 | 用 `AskUserQuestion` 问换分支 / 接手已占的 slot |
| `3` | 池满 | 问用户：等其他释放 / `pool-resize` 扩 / 走旧路 `setup-worktree.sh` |
| `4` | 池未初始化（罕见，setup-project 默认初始化） | 用 `AskUserQuestion` 问"现在 init 池（约 5 分钟）吗？"——按铁律必须先 init 池或走命名 worktree（仅长期 feature），**不接受**"在主仓库直接干"。详见 `start-feature` skill exit 4 段 |
| `5` | 池被禁用（`FFOA_AGENT_POOL_ENABLED=false`）| 自动走旧路 `setup-worktree.sh`，不打扰用户 |

stderr 含结构化字段 `EXIT_REASON=...` / `EXISTING_SLOT=...` 可供 grep 决策。

### 常用命令

```bash
# 池级（一次性 / 偶尔）
bash scripts/dev/agent-pool/pool-init.sh [--size 3]
bash scripts/dev/agent-pool/pool-resize.sh --size N
bash scripts/dev/agent-pool/pool-destroy.sh

# slot 级（每个任务）
eval "$(bash scripts/dev/agent-pool/agent-claim.sh feature/xxx)"
bash scripts/dev/agent-pool/agent-release.sh         # 用 $FFOA_AGENT_SLOT
bash scripts/dev/agent-pool/agent-status.sh
```

### 切任务协议（"换任务"触发词）

触发短语：「切到下一个任务」/「切下一件」/「下一个任务」/「做下一件」/
「switch task」/「换任务」。

AI 接到触发后**主动**走 release → claim 序列，不要每步等用户确认：

1. **自报当前 slot**：分支名 / commit 状态 / push 状态 / PR 状态（如有）
2. **`agent-release`**：
   - 干净（内容已 absorbed 或远端已删 + 工作区无脏）→ 进 step 3
   - dirty / unpushed / PR 未合 → **STOP**，用 AskUserQuestion 给三选项：
     先收口 / `--keep-changes` 暂存 / `--force` 丢弃。**不擅自 --force**
3. **询问下一个任务**：用 AskUserQuestion 收分支名 + 一句话描述
4. **`agent-claim <new-branch>`**：在同一 slot 接新任务（slot 目录不变，只换 HEAD）
5. **汇报新 slot**：编号 / 端口段 / Caddy 域名 / 起点 base / 当前 HEAD

#### 边界处理

- 池满（claim exit 3）→ AskUserQuestion: pool-resize / 命名 worktree / 等释放
- 同分支已被别的 slot 占（claim exit 2）→ AskUserQuestion: 换分支 / 接手已占 slot
- 当前 slot 占着 PR 等合、不想释放 → 提议**保留当前 slot + 用别的 slot 干新任务**，不强制切走

### 命名 worktree（手工长期 feature 沙箱）

仍可用 `setup-worktree.sh` 开命名 worktree（`adp-sync/`、`asset-management/` 这种），适合：
- 单 feature 持续多周
- 想给 worktree 起一个语义名，不是 `slot-N`

池槽 vs 命名 worktree 的选择见 `docs/standards/05-development-workflow.md` § "并行开发选择树"。

## 默认工作方式

0. **契约面识别（开工前强制）**：先判定本次改动是否触碰**契约面**——API（路径/方法/请求/响应字段/错误码）、数据模型（schema/表/字段/约束）、状态机（状态枚举/转换）、权限（角色/权限点）、UI 规格（页面结构/关键交互/可见文案）、事件（Temporal signal/MQ 事件）。判定结果必须显式声明：
   - **触碰契约面** → 走"文档先行"：先读对应模块文档（`docs/modules/{module}/`），缺口先补文档并与用户确认，再动代码。
   - **仅内部实现**（私有函数拆分/重命名、算法替换、性能优化、注释/风格）→ 直接动手，不强制改文档。
   - **不确定** → 当作触碰契约面处理，宁可多读一次文档。
   - 详细判定流程见 `.agents/skills/contract-check`。
1. 先确认事实源和边界。
2. 需要时先补或更新文档。
3. 再做最小实现。
4. 执行验证并记录结果。
5. 总结改动、风险和后续建议。
6. **回顾与规则沉淀**（大任务完成后必须执行）：
   - 回顾过程中遇到的问题，哪些是可以提前避免的
   - 将可复用的经验写入对应的规范文档（`docs/standards/`）或 skill（`.agents/skills/`）
   - 更新 memory 记录关键发现
   - 不需要用户提醒，AI 应在大任务结束时主动执行

## 发现问题时的处理

- 发现规则重复、过期、低效或互相冲突时，不要静默绕过。
- 先指出问题，再给出更优方案。
- 能直接收敛到标准文档的，优先减少重复，而不是继续堆规则。
