# Chord 开发约束

## 使用方式

- 这个文件是给自动化 agent 和开发者的根入口：记录必须长期遵守的约束、阅读入口与提交前检查。
- 内部技术文档（架构、排障、坑点、计划等）位于**独立的私有 git 仓库**，不属于本仓库的提交历史。
  - 本仓库的 commit / PR / tag 历史中 **不得包含** 任何内部文档文件。
  - 常见本地布局是把内部文档仓库放在工作区的 `.internal-docs/` 目录，但该目录必须保持为本仓库的 *untracked / ignored* 内容。
- 涉及某个子系统时：先读公开文档与代码，再结合内部文档仓库里的专题材料核对不变量与排障路径。

## 关键不变量

- 默认模式是**本地进程内直连**：TUI 与 MainAgent 同进程；remote control plane 通过 `chord headless` 提供。
- MainAgent 使用单 `eventCh` + `select` 事件循环。
- 辅助 LLM 调用（如思考过程翻译、context compaction）必须沿用主 LLM client 的模型池/key 遍历语义：按当前游标遍历一整轮，只有整轮没有任何模型成功才失败；需要跨请求保留游标的路径必须复用或显式复制模型池快照，不能退化成只试单个模型。
- 每条用户消息创建新 turn；turn 内共享 LLM / 工具 cancel context。
- Streaming delta 不走主 `eventCh`，直接走输出路径。
- 工具卡必须显式终态收口：`success | error | cancelled`。
- `ctxmgr` 是消息唯一存储；请求级裁剪与 durable compaction 分离。
- Headless 控制面里，状态缓存更新不等于外部 gateway 已看到终态；面向集成方的重要状态变化必须显式推送事件。
- TUI 不能阻塞 Bubble Tea 主循环；overlay 必须是最终绘制层，不得挤进 flow layout。
- TUI Markdown 渲染可复用正文 renderer，但 ANSI 背景恢复必须由当前容器决定：`preserveCardBg` / card 背景只用于消息卡片；Confirm/Question 等 overlay 不得硬套 `AssistantCardBg`、`ToolCallBg` 等 card surface。
- 界面上不得输出日志/调试信息；诊断信息写日志，用户可见提示必须使用明确的产品化 UI（如 status、toast、overlay、boundary marker），不得伪装成会话正文或普通消息卡片。

## 代码地图

- `internal/agent/`：MainAgent、SubAgent、turn、事件循环、handoff、coordination。
- `internal/llm/`：provider、streaming、fallback、retry、key rotation、responses。
- `internal/tools/`：工具注册、执行、截断、Question/Task/Handoff/Bash。
- `internal/ctxmgr/`：消息存储、usage、裁剪、compaction。
- `internal/session/`：JSONL、snapshot、resume、restore、recovery。
- `internal/tui/`：layout、viewport、block、overlay、图片、性能优化。
- `cmd/chord/headless.go` / `internal/protocol/`：headless 控制面、stdio JSONL、协议 envelope。
- `internal/config/` / `internal/permission/`：配置合并、模型、权限。
- `internal/lsp/` / `internal/mcp/` / `internal/hook/` / `internal/skill/` / `internal/analytics/`：集成、扩展与统计。

## 伴随仓库

- 如本地存在 `../chord-gateway`，涉及 headless control plane、gateway 可见性、事件契约、状态同步或集成问题时，应同时参考该伴随仓库源码。
- `../chord-gateway` 是本地/私有伴随仓库路径，不是 Chord 主仓库的必需依赖；不存在时不要阻塞普通 Chord 开发。

## 公开文档入口

- 首页：`docs/index.md` / `docs/index_CN.md`
- 快速开始：`docs/quickstart.md` / `docs/quickstart_CN.md`
- 使用说明：`docs/usage.md` / `docs/usage_CN.md`
- 排障：`docs/troubleshooting.md` / `docs/troubleshooting_CN.md`

## 内部文档入口（本地私有，不提交到本仓库）

先按主题查 `.internal-docs/architecture/index.md`，排障看 `.internal-docs/troubleshooting/index.md`，历史坑点看 `.internal-docs/pitfalls/index.md`，常见改动路径看 `.internal-docs/guides/index.md`。

常用专题入口：

- Runtime / turn / cancellation / agent 协调 → `.internal-docs/architecture/runtime-model.md`
- Provider / streaming / finalize / fallback → `.internal-docs/architecture/llm-runtime.md`
- Tool call / tool result / Question / Confirm → `.internal-docs/architecture/tool-runtime.md`、`.internal-docs/architecture/user-confirmation.md`
- Ctxmgr / usage / compaction → `.internal-docs/architecture/context-management.md`、`.internal-docs/architecture/analytics.md`
- Session / resume / restore / shutdown → `.internal-docs/architecture/session-recovery.md`、`.internal-docs/architecture/session-switching.md`、`.internal-docs/architecture/shutdown-lifecycle.md`
- Headless 控制面 / 输出事件契约 / gateway 可见性 → `.internal-docs/architecture/control-plane.md`、`.internal-docs/architecture/event-contract.md`
- Config / auth / OAuth / proxy / permission → `.internal-docs/architecture/config-and-auth.md`
- LSP / MCP / Hook / Skills / commands → `.internal-docs/architecture/integrations.md`、`.internal-docs/architecture/hooks.md`、`.internal-docs/architecture/commands-and-session-controls.md`
- IME / input modes / queued drafts / reclaim / continue → `.internal-docs/architecture/ime-and-input-modes.md`、`.internal-docs/architecture/tui-architecture.md`
- `@` 文件路径补全 / 文件注入 → `.internal-docs/architecture/file-mentions.md`
- 图片 inline / viewer / protocol backend → `.internal-docs/architecture/image-support.md`
- Viewport spill / cold-hot blocks / hydrate / recover → `.internal-docs/architecture/viewport-spill.md`
- Planner / builder / Task workflow → `.internal-docs/architecture/planning-workflow.md`
- System prompt 组合 / AGENTS.md / MCP discoverability / prompt freeze / turn overlay → `.internal-docs/architecture/prompt-and-context-engineering.md`
- TUI 结构 / overlay / input / image / layout → `.internal-docs/architecture/tui-architecture.md`
- TUI 热路径 / 缓存 / benchmark / regression → `.internal-docs/architecture/tui-performance.md`
- TUI 诊断 dump / 间歇性渲染问题 → `.internal-docs/architecture/tui-diagnostics.md`

## 排障入口（本地私有，不提交到本仓库）

排查 bug 时先看 `.internal-docs/troubleshooting/index.md`，再按症状进入对应专题；同时检查 `.internal-docs/pitfalls/index.md` 是否已有历史坑点。

- TUI 渲染 / 布局 / overlay / viewport / 状态卡 → `.internal-docs/troubleshooting/tui-rendering.md`
- TUI 性能 / 卡顿 / benchmark 回归 → `.internal-docs/troubleshooting/tui-performance.md`
- LLM streaming / delta / finalize / fallback 异常 → `.internal-docs/troubleshooting/llm-streaming.md`
- 工具调用 / tool result / 权限确认 / 截断 → `.internal-docs/troubleshooting/tool-calls.md`
- Session restore / resume / recovery → `.internal-docs/troubleshooting/session-restore.md`
- Startup / 初始化 / 配置加载 → `.internal-docs/troubleshooting/startup.md`
- Context usage / token 统计 / 压缩触发 → `.internal-docs/troubleshooting/context-usage.md`
- Compaction / durable summary / 消息裁剪 → `.internal-docs/troubleshooting/compaction.md`
- Max output tokens / completion recovery → `.internal-docs/troubleshooting/max-output-tokens-recovery.md`
- LSP diagnostics / 编辑后诊断反馈 → `.internal-docs/troubleshooting/lsp-diagnostics.md`
- MCP remote / 远程 MCP 连接 → `.internal-docs/troubleshooting/mcp-remote.md`

若症状横跨多个子系统，先从用户可见症状对应的 troubleshooting 文档定位复现与验证路径，再回到对应 architecture 文档确认不变量和事件流。

排查运行期报错时，不要先凭经验猜日志位置；先读本节对应 troubleshooting 文档。当前默认运行日志入口是 `${XDG_STATE_HOME:-~/.local/state}/chord/logs/chord.log`，可被 `CHORD_LOGS_DIR`、`paths.logs_dir` 或 `--logs-dir` 覆盖。默认会话根目录是 `${XDG_STATE_HOME:-~/.local/state}/chord/sessions/`，可被 `CHORD_SESSIONS_DIR`、`paths.sessions_dir` 或 `--sessions-dir` 覆盖；单个会话位于 `<sessions-root>/<project-key>/<session-id>/`，主记录是 `main.jsonl`，LLM raw dump 在该会话目录的 `dumps/llm/` 下。

## 开发规则

- Go 错误使用 `fmt.Errorf("...: %w", err)` 包装。
- 修改 Go 代码后先跑受影响包 `go test`；需要全仓库覆盖率与 CI 输出泄漏检查时，使用 `scripts/check_ci_local.sh`（覆盖率门槛默认 70%）。该脚本本地默认保留 Go test cache，便于迭代验证；最终提交前或复现 CI 时必须显式冷跑：`CHORD_TEST_COUNT=1 scripts/check_ci_local.sh`（也可设置 `CHORD_CI_STRICT=1` 或 `GITHUB_ACTIONS=true`）。需要加速定位时可传包列表，例如 `scripts/check_ci_local.sh ./cmd/chord ./internal/llm`。涉及并发/生命周期等需要 race 验证的改动，使用 `scripts/check_ci_race.sh`；同样可传包列表加速定位，但最终 CI 等价检查不要直接跑裸 `go test -race ./...`。收尾前跑 `go vet ./...`、`staticcheck -checks 'all,-ST1000' ./...` 与 `git ls-files '*.go' | xargs gopls check`。`gopls check` 接收文件列表，不支持 `./...` 包模式。
- 提交前必须用 `goimports -w -local github.com/keakon/chord <file>` 整理 import 分组（标准库 / 第三方 / 本地）。`.githooks/pre-commit` 会自动处理；本地未安装 goimports 时用 `go install golang.org/x/tools/cmd/goimports@latest` 安装。
- 若改动触及 goroutine 生命周期、channel/select、context cancel、session switch / shutdown、流式输出、headless stdio、后台任务/子进程、共享缓存/全局状态或任何“测试里需要 sleep/等待异步完成”的路径，除常规测试外必须额外运行受影响包的 `go test -race`；若无法明确收敛影响面，直接运行 `go test -race ./...`。
- 修改顶层入口文档后，额外运行 `./scripts/check_docs_consistency.sh`。
- 单元测试中的 provider/model/base_url 命名必须使用中性占位值（如 `sample/test-model`、`provider/model-1`、`https://example.invalid/v1`）；不要写入自定义渠道名、私有 provider 前缀、真实代理域名或其他只在个人环境成立的标识。
- 非 i18n 单元测试默认使用英文样本与断言；只有在专门验证 Unicode / CJK / 多语言输入输出、显示宽度、编码、排序或本地化行为时，才使用中文或其他非 ASCII 文本。
- 单元测试中的文本样本应使用中性、通用的示例，不要带入真实故障、实际工单、排障过程、私有路径、模型调参细节或开发中的中间结论。
- 新增功能或新逻辑前，先判断其是否属于现有文件的单一阅读主题；若不属于，优先创建同前缀的新主题文件（如 `app_*.go`、`input_*.go`、`filecompletion_*.go`），避免继续把 state / render / input / IO / bridge 等职责混入既有大文件。仅当改动很小、主题完全一致，或出于初始化顺序、热路径局部性、risky helper 邻近性等原因确有必要时，才继续放入现有文件。
- 本项目 1.0 前重构或清理时默认**不保留兼容性代码**、过渡分支、旧入口别名或 deprecated 包装层；直接替换为当前正确实现，并同步更新调用方、测试、注释和文档。
- 代码、文件名、注释、error message、`docs/` 与 `CHANGELOG*.md` 中**不得**出现项目计划意义上的中间状态标记（`Phase 1/2/...`、`Phase A/B`、`v2 路径`、`Step N`、`新版/旧版分支` 等）；落地后这些只是临时立项口径，对未来读者无意义。新增内容直接以稳定不变量、当前事实或领域概念表述。`chunkPhaser` / `AnimationPhase` / 控制面 `phase` / `tool card execution phase` 等真实领域概念不在此列。
- 单文件规模硬约束：单个 `.go` 文件超过 3000 行时，新增 public 方法 / 新事件 handler / 新工具路径必须拆到新文件，不得继续 append；超过 4000 行必须在下一个改动该文件的 PR 里先做拆分再落功能。测试文件（`*_test.go`）不受此约束，但建议同样遵循。
- Write/Edit 后注意 LSP 通知与 Error 级诊断反馈。
- 超过 2000 行或 50KB 的工具输出会截断并保存 artifact；不要把长输出直接塞回上下文。
- Shell / Shell 权限匹配是最佳努力，不是安全沙箱。

## 性能优化与基准

- 优化前先找真实业务热路径，不要只用孤立的锁、map 或分配微基准推导方案；优先 benchmark 实际流程（如工具调用、权限判断、上下文快照、TUI 渲染、LLM streaming）。
- benchmark 循环里不要混入无关测试数据构造（如 `fmt.Sprintf` 生成 key、临时 JSON 拼装），除非它本来就是被测业务路径；否则会掩盖真正瓶颈。
- 先查重复构造、无谓分配、I/O、快照复制语义，再考虑替换 `Mutex` / `RWMutex` / `sync.Map` / `atomic.Value`；不要为了“看起来更快”的同步原语破坏一致性、不变量或 ownership 语义。
- 保留能防止回归的业务 benchmark；只用于探索且没有形成生产优化结论的 benchmark，验证后删除。
- 性能结论、取舍理由和历史数据写入内部文档仓库对应专题；`AGENTS.md` 只保留跨子系统长期规则。

## TUI 热路径

若触及以下任一路径，除常规测试外，必须额外运行 `./scripts/bench_tui_regression.sh`：

- `scheduleStreamFlush`、`StreamTextEvent`、`StreamThinkingDeltaEvent`、`animTick`
- `View()` / `Draw()` / UV / `screenBuf` / 相关缓存
- assistant/thinking streaming cheap path
- viewport 高度测量、`blockStartsCache`、`blockSpansCache`、spill 可见窗、inline image 可见性
- benchmark / regression script / `AllocsPerRun` 相关改动

并至少执行：

- `go test ./internal/tui/ -run 'TestInfoPanel|TestSeparator'`
- `go test ./internal/tui/ -bench=Benchmark -benchmem`

如需性能剖析：

- `CHORD_PPROF_PORT=6060 chord`
- `go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=15`

## 文档维护

### 公开 / 内部边界

- 内部技术文档位于独立的私有 git 仓库，不属于本仓库；本仓库的 commit 历史不得包含任何内部文档文件。
- 不要通过修改 `.gitignore` 的反忽略规则把内部文档纳入本仓库（例如 `!.internal-docs`）。
- `.internal-docs/**` 的路径只允许出现在面向开发者/agent 的入口文档（如本文件）中，用于本地查阅指引；不要在用户可见的公开材料中引用内部文档路径或私有背景：
  - 公开代码注释、错误信息、`docs/`、`README.md`、`CHANGELOG*.md`
  - commit message、release notes、issue/PR 描述
- 若公开变更需要文档说明，应写入面向用户/贡献者可读的公开文档（如 `docs/`、`README.md`、`CHANGELOG.md`、`CHANGELOG_CN.md`）。

### 中文文案规范

中文文档不是逐字翻译英文，而是按中文技术写作习惯重新组织。核心原则：**像跟同事解释技术那样写，不要像翻译软件那样写。**

常见要避免的问题：

- **开头自报家门**：删掉「本文档面向……目标是……」这类套话，直接进入正题。
- **「如果你……可以……」泛滥**：英文 "If you…, you can…" 逐句翻过来太啰嗦。用「已安装 Go 1.26+ 时」「网络不通的话」等紧凑前置。
- **被动句直译**：「是否可用取决于……」→「能不能用要看……」；「会被过滤」太被动时直接说「Chord 会过滤掉」。
- **长定语嵌套**：避免超过两层「的」字。长定语拆成逗号短句或用括号后置。
- **「的」字瘦身**：能省则省。「项目 canonical 根」不需要写成「项目的 canonical 根」。
- **连接词生硬**：「可执行：」→「可以用以下命令排查：」；「先检查：」→「先看看这几项：」。
- **裸英文术语**：英文术语不要直接嵌在中文句子中间。公共术语首次出现加括号注中文，之后直接用中文；代码级别术语用反引号包裹。
- **「进行」「做」虚动词**：「对历史消息做一次标准化」→「对历史消息做标准化」或直接「标准化历史消息」。
- **语气**：别端着。技术文档不需要字字对应英文结构，口语化的中文反而更清晰易懂。写完后读一遍，看像不像人说的话。

写作后自检：读一遍，能不能顺畅地念出来？念着拗口的句子就值得改。

同步更新中文文档后，运行 `node scripts/sync-docs.mjs` 刷新网站版。

### 更新位置判断

- 用户需要学习或依赖的功能、配置、命令、迁移步骤、限制、兼容性说明或稳定可见行为 → 更新 `docs/`、`README*.md`；若行为已出现在发布版本中，还要按 Changelog 规则判断是否更新 `CHANGELOG.md` 和 `CHANGELOG_CN.md`。
- 面向用户的公开文档默认只写**当前问题、当前行为、用户影响和排查动作**；不要让用户通过“使用最新版本 / 新构建 / 已修复某个 bug”来理解文档，也不要写入修复历史、未发布回归、内部错误分类或维护者视角的实现过程，除非这些信息直接影响用户操作。
- 贡献者需要遵守的全仓库规则、提交前检查、长期入口索引或跨子系统硬约束 → 更新 `AGENTS.md`；不要把专题细节搬进 `AGENTS.md`。
- 只服务维护者的实现细节、不变量、事件流、内部错误语义、排障经验、历史坑点或中长期方案 → 更新内部文档仓库对应专题（不在本仓库提交）。
- 仅为测试覆盖、内部重构、错误文案去重、日志/诊断口径、未发布回归修复等用户不必学习的细节 → 优先更新单元测试；必要时在内部文档仓库补充说明，但不要把内部文档带入本仓库。
- 新增或修改用户需要主动了解的功能、配置、命令、限制或迁移流程时，同步更新对应公开文档（`docs/` / `README*.md`）与测试。内部文档仓库如需同步更新，应在其仓库内单独提交。
- 发现文档过时、与代码不一致或只剩历史价值时，直接更新、删除或改写，不保留“未来再修”的占位描述。

### Changelog 规则

- Changelog 只记录相对“最新已发布版本”的用户可见变化；不要机械更新 changelog，先确认变更是否需要用户通过发布说明了解。
- 需要记录：已发布版本中存在的 bug 修复、用户可见功能/行为变化、性能或可靠性改进、deprecation、breaking change、安全修复、迁移要求。
- 不需要记录：从未进入任何发布版本的未发布回归及其修复、纯测试/重构/内部实现调整、仅日志或诊断字段口径调整且用户无需感知、只影响私有内部文档或开发流程的变更。
- 若某个功能、文档页或行为是在最新已发布版本之后才新增、且尚未进入任何发布版本，那么本周期内对它的修正、重命名、链接修复或文案调整不应在 changelog 中写成“修复/改动”；需要记录时只描述最终对用户可见的新增能力或新增文档。
- 即使是最终新增的 docs，若用户不需要通过 changelog 了解它、且不影响升级决策、迁移或使用方式，则不要记录。
- 若某个 bug 是否已发布不确定，先用 tag/发布分支确认引入提交与最新 release 的关系；确认不了时倾向记录，避免漏掉用户可见修复。
- `CHANGELOG.md` 与 `CHANGELOG_CN.md` 必须保持语义同步；不要在 changelog 中提及内部文档路径、私有背景或 ignored 文件。

### 内部技术文档归档

内部文档仓库按目录归档：

- `architecture/`：设计/约束类结论
- `troubleshooting/`：症状与排查步骤
- `pitfalls/`：教训与回归风险
- `guides/`：常见操作路径
- `plans/`：未落地方案或中长期计划
- `archive/`：已完成/已过时/仅历史参考的文档

存档 plan 前，先把仍然有效的稳定结论更新到公开文档（本仓库 `docs/`）或内部文档仓库对应专题，再更新索引。

## 提交规则

- 使用 Conventional Commits / 约定式提交规范，例如 `fix(tui): ...`、`test: ...`、`docs: ...`。
- 提交前确认测试、静态检查和文档更新符合本文件规则。
- 内部文档仓库的变更必须在其仓库内单独提交；本仓库提交历史不得包含 `.internal-docs/**` 文件。
