# AGENTS.md

Project conventions for lspz (LSP compression proxy).

> **SSOT (Single Source of Truth)**: 本文档是 lspz 项目的通用规范文档。所有开发指南、文档都应引用本文档，避免重复和不一致。

---

## Rust

### Edition & Toolchain

- Edition 2024
- Stable toolchain (pinned in `rust-toolchain.toml`)
- Single crate `lspz` with feature flags (`cli`, `mcp`, `agent-sdk`, `transport-tcp`, `transport-websocket`)

### Code Quality

- Clippy thresholds configured in `.clippy.toml`; lint flags in justfile
- Format config in `rustfmt.toml`
- Nightly-only options are not allowed

### 命名约定

| 类型 | 约定 | 示例 |
|------|------|------|
| Struct | `PascalCase` | `Proxy`, `Config` |
| Enum | `PascalCase` | `Direction`, `Severity` |
| Function | `snake_case` | `get_diagnostics`, `compress` |
| Const | `SCREAMING_SNAKE_CASE` | `MAX_BUFFER_SIZE` |
| Trait | `PascalCase` | `Transport`, `Interceptor` |

### 错误处理

- 使用 `Result<T, LspzError>` 统一错误类型
- 使用 `thiserror` 定义错误枚举
- 避免使用 `.unwrap()` 和 `.expect()`（除测试代码）

### 异步代码

- 使用 `tokio` 作为异步运行时
- 使用 `async-trait` 定义异步 trait
- 所有 I/O 操作基于异步

### 日志规范

- 使用 `tracing` 库（非 `log`）
- 日志级别：ERROR, WARN, INFO, DEBUG, TRACE
- 结构化日志，使用 `info!`, `debug!`, `error!`, `warn!`

---

## Scripts (`scripts/`)

- **Purpose**: 只用于复杂的、项目特定的逻辑
- **Naming**: kebab-case
- **避免**: 简单的 cargo 命令包装（直接在 justfile 中调用）
- **No underscores** in filenames — prevents accidental Python module import

### 适合 scripts/ 的场景

- ✅ 复杂的验证逻辑（多步骤、条件判断）
- ✅ 需要与其他工具集成的脚本
- ✅ 项目特定的构建/部署逻辑

### 不适合 scripts/ 的场景

- ❌ 简单的 `cargo fmt`, `cargo clippy`, `cargo test` 等
- ❌ 只是转发命令的包装器（如 `check-rust-clippy.py`）

### 示例

**好的脚本** (`check-conventional-commit.py`):
- 包含复杂的 Git 提交消息解析和验证逻辑
- 项目特定的提交规范

**不必要的脚本** (`check-rust-clippy.py`, `check-rust-fmt.py`, `check-rust-test.py`):
- ❌ 已删除，直接在 justfile 中调用 cargo 命令

---

## Git Hooks (`prek.toml`)

- Only `repo = "builtin"` + `repo = "local"` — zero network dependency at runtime
- Local hooks: `pass_filenames = false` + `require_serial = true` (avoid cargo concurrent conflicts)
- Stages:
  - **pre-commit**: cargo-fmt, cargo-clippy (+ builtin whitespace/toml/yaml checks)
  - **commit-msg**: conventional commits validation
  - **pre-push**: cargo-test (full tests, not on every commit)

---

## Commits

- Conventional Commits format: `type(scope)!: description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
- Subject line max 72 characters
- Validated by `scripts/check-conventional-commit.py`

---

## Task Runner (`justfile`)

- `just setup` = 安装 prek hooks + 构建
- `just fmt` = 格式化代码（写入）
- `just fmt-check` = 检查格式（只读）
- `just lint` = 运行 clippy（直接调用 cargo）
- `just test` = 运行测试（直接调用 cargo）
- `just qa` = fmt-check + lint + test（所有检查）
- `just check` = qa 的别名
- `just ci` = prek run --all-files + qa（完整 CI 模拟）
- Shell: `bash -euo pipefail`

**原则**: Scripts 是 SSOT，justfile 和 prek hooks 委托给脚本执行。

---

## 文档规范

### Markdown

- 使用 [Mermaid](https://mermaid.js.org/) 绘制图表
- 代码块指定语言：\`\`\`rust, \`\`\`bash
- 链接使用相对路径（内部文档）

### 代码注释

- 公共 API 必须有文档注释（`///` 或 `//!`）
- 模块级文档使用 `//!`
- 包含使用示例（可运行）

### 文档优先级

1. **AGENTS.md** (本文件) - 通用规范 SSOT
2. **docs/specs/** - 技术规格
3. **docs/plan/** - 阶段计划
4. **docs/guides/** - 开发指南（引用 AGENTS.md，不重复）

### 文档生成规则 (SSOT)

> **详细规则**: 参见 [docs/src/specs/ssot-rules.md](docs/src/specs/ssot-rules.md)

#### 两层文档体系

| 层 | 工具 | 内容 | 维护方式 |
|----|------|------|----------|
| API 文档 | `cargo doc` | `///` / `//!` 注释 | 编辑源码注释 |
| Book | `mdbook` | 指南、规格、架构 | 编辑 `docs/src/**/*.md` |

#### SSOT 位置

| 内容类型 | SSOT 位置 | 生成命令 | 输出 |
|---------|----------|----------|------|
| API 文档 | `src/**/*.rs` 注释 | `cargo doc --no-deps --all-features` | `target/doc/lspz/` |
| 配置参考 | `src/config.rs` 结构体 | `cargo doc` | `target/doc/lspz/config/` |
| 错误类型 | `src/error.rs` 枚举 | `cargo doc` | `target/doc/lspz/error/` |
| 文档示例 | `///` 代码块 | `cargo test --doc` | CI 验证 |
| Mermaid 图表 | `docs/src/diagrams/*.mmd` | mdbook-mermaid | 嵌入 book |

#### 文档命令

```bash
just doc          # 构建 API 文档并在浏览器打开
just doc-check    # 检查 API 文档构建是否成功 (CI 使用)
just doc-test     # 运行文档测试 (验证 /// 示例编译)
just book         # 构建 mdbook
just book-serve   # 本地预览 book (热重载)
```

#### CI 保证

- `cargo doc --no-deps --all-features` — API 文档构建检查
- `cargo test --doc --all-features` — 文档示例编译验证
- `#![warn(missing_docs)]` — 未文档化公开项产生警告

---

## 测试规范

### 测试组织

- 单元测试：与源码同目录的 `tests` 模块
- 集成测试：`tests/` 目录
- 性能测试：`benches/` 目录（使用 criterion）

### 测试命名

- 测试函数：`test_<功能>_<场景>`
- 测试模块：`tests` 或 `<module>_tests`

### 覆盖率目标

| 层级 | 目标覆盖率 |
|------|-----------|
| 核心逻辑 (codec/) | ≥ 90% |
| Proxy 核心 | ≥ 80% |
| Transport | ≥ 70% |
| 配置/错误 | ≥ 60% |

---

---

## 异步超时与 CI 可靠性

### 教训: 无限循环必须有超时保护

2026-05-12: CI 在 `cargo test --workspace` 步骤卡死 1h+，原因是 `LspSession::send_request()`
和 `LspTestHarness::get_diagnostics()` 中的 `loop { transport.receive()... }` 没有超时保护。
当 LSP 服务器启动缓慢或版本不兼容时，这些循环永远不退出。

### 强制规则

| 模式 | 规则 | 示例 |
|------|------|------|
| 异步 receive 循环 | 必须使用 `tokio::time::timeout` 包裹 | `tokio::time::timeout(Duration::from_secs(30), self.transport.receive()).await?` |
| 测试 LSP 服务器 | 必须优雅跳过而非 panic | `if initialize failed → SKIP` |
| CI job | 必须设置 `timeout-minutes` | `timeout-minutes: 15` (job 级别) |
| CI step | 每个耗时步骤单独设置超时 | `timeout-minutes: 10` (test 步骤) |

### 超时值参考

| 场景 | 建议超时 | 说明 |
|------|---------|------|
| LSP 请求/响应 | 30s | 足够 LSP 服务器返回结果 |
| E2E 测试 (单个) | 30–60s | 含 LSP 服务器启动 + 分析 |
| CI test 步骤 | 10 min | 完整测试套件 |
| CI job 总超时 | 15 min | 全流程 (fmt + lint + test + doc-check + doc-test) |
| CI clippy 步骤 | 5 min | 纯静态检查 |
| CI fmt 步骤 | 3 min | 纯静态检查 |
| CI doc-check | 5 min | cargo doc 构建 |
| CI doc-test | 5 min | cargo test --doc |

### E2E 测试最佳实践

1. **外部服务检测**: 使用 `which` 检查二进制是否存在，不存在 → 优雅跳过
2. **启动失败处理**: 服务器启动或初始化握手失败 → 优雅跳过，不 panic
3. **断言宽容**: 不假设外部服务一定返回特定结果；验证"通知已收到"而非"诊断非空"
4. **项目脚手架**: 为每个语言创建最小项目结构（`Cargo.toml`、`go.mod` 等），否则 LSP 服务器无法分析文件
5. **启动参数**: 不同服务器需要不同参数（如 `typescript-language-server --stdio`），通过 `try_new_with_args()` 支持

### 错误示例 vs 正确示例

```rust
// ❌ 错误: 无限循环，无超时
loop {
    let raw = self.transport.receive().await?;
    // 如果服务器无响应，永远不退出
}

// ✅ 正确: 带超时的 receive 循环
loop {
    let raw = tokio::time::timeout(
        Duration::from_secs(30),
        self.transport.receive()
    )
    .await
    .map_err(|_| "timeout waiting for response")??;
    // 30s 后优雅返回错误
}
```

```rust
// ❌ 错误: 外部服务失败导致测试 panic
let result = server.initialize().await.unwrap();

// ✅ 正确: 外部服务失败时优雅跳过
if harness.initialize().await.is_err() {
    eprintln!("  SKIP: server init failed");
    return None;
}
```

### CI 费用控制

GitHub Actions 按运行时间计费。必须为每个 job 和耗时 step 设置合理的 `timeout-minutes`：

```yaml
jobs:
  qa:
    timeout-minutes: 15    # 最高安全阀
    steps:
      - name: Run tests
        timeout-minutes: 10  # 每个步骤独立超时
        run: cargo test --workspace
```

> 原则: 宁可超时失败（CI 红色），不可让 job 无限运行（费用爆炸）。

---

## SSOT Harness (代码即 SSOT)

### 原则

**代码是唯一的真相源 (Single Source of Truth)**。API 文档由 `cargo doc` 从代码注释自动生成，book 由 `mdbook` 从 `docs/src/` 构建。

### 生成规则

| SSOT 位置 | 生成命令 | 输出 |
|-----------|---------|------|
| `///` / `//!` 注释 | `cargo doc --no-deps --all-features` | `target/doc/lspz/` |
| `docs/src/**/*.md` | `mdbook build` (在 `docs/` 目录) | `docs/book/` |
| `docs/src/diagrams/*.mmd` | mdbook-mermaid | 嵌入 book HTML |

### 架构不变量 (Architecture Invariants)

以下规则在代码实现中必须遵守，任何偏离需在 AGENTS.md 和设计评审中记录：

1. **Interceptor chain 是核心抽象**: 所有 Server→Client 消息转换必须通过 `Interceptor` trait，禁止在 Proxy 核心中直接硬编码消息处理逻辑
2. **Fail-open**: 任何压缩/转换失败 → 只记录 WARN 日志，透明转发原始消息，禁止抛出异常或中断 LSP 通信
3. **Transport-agnostic core**: 核心不依赖任何特定传输实现（stdio/TCP/WS），只依赖 `Transport` trait
4. **Config-driven**: 所有运行时行为通过 `Config` 结构体控制，禁止硬编码开关或行为
5. **Zero-copy preference**: 在 Hot Path（消息收发、拦截器处理）中优先使用引用 `&str` / `&[u8]`、`Cow`、`Arc`，避免不必要的克隆
6. **LSP version locked**: 明确锁定 LSP 3.17 规范，不引入 3.18+ 特性直到项目正式声明支持

### 文档漂移检测

`just qa` 包含以下检查：

1. `cargo doc --no-deps --all-features` → API 文档构建是否成功
2. `cargo test --doc --all-features` → 文档示例是否编译通过
3. `#![warn(missing_docs)]` → 未文档化公开项产生警告
4. 架构不变量 → 代码审查中人工核对

### 架构级代码注释规范

公共 API 必须包含模块级（`//!`）注释，格式如下：

```rust
//! # 模块名
//!
//! 一句话说明模块职责。
//!
//! ## 核心概念
//!
//! - **概念 A**: 解释
//! - **概念 B**: 解释
//!
//! ## 注意事项
//!
//! - 扩展点预留说明
//! - 线程安全说明
//! - 错误处理说明
```

公共结构体、trait、枚举必须包含 `///` 文档注释，包含：

- 一句话说明（必须）
- 使用示例（复杂类型必须）
- 通过 `[`TypeName`]` 引用关联类型

## 依赖管理

### Cargo.toml

- 单一 crate，通过 feature flags 控制编译
- 避免循环依赖

### 外部依赖

| 依赖 | 用途 |
|------|------|
| tokio | 异步运行时 |
| serde | 序列化框架 |
| serde_json | JSON 支持 |
| async-trait | 异步 trait |
| thiserror | 错误定义 |
| tracing | 日志/追踪 |
| rstest | 参数化测试（开发依赖） |

---

## 安全考虑

### 输入验证

- 验证外部输入（配置、命令参数）
- 避免未检查的索引访问（使用 `.get()`）
- 命令注入防护（验证 backend_cmd）

### 错误处理

- 压缩失败降级到透明转发
- 传输错误记录日志，不 panic
- 配置错误早期检测

---

## 性能考虑

### 避免不必要的克隆

- 优先使用引用 (`&str`, `&[u8]`)
- 使用 `Cow<T>` 避免条件分配
- 大对象使用 `Arc` 共享

### 异步最佳实践

- 避免 `.await` 在循环中（如可能）
- 使用 `tokio::spawn!` 并行独立任务
- 合理设置超时

---

## 版本管理

### 语义化版本

- v0.1.0: MVP (Library + Proxy)
- v0.2.0: MCP 集成
- v0.3.0: Agent SDK

### 兼容性承诺

- lspz 公共 API: 主版本不变时向后兼容
- 压缩格式: Agent 端解压库支持所有历史版本
- LSP 兼容性: 永不破坏标准 LSP 协议

---

---

## 检查点规则 (Checkpoint Save)

### 原则

每个阶段或重要模块完成后，必须进行验证 → 提交 → 版本号更新 → 打 tag，以此建立可追溯的检查点链。

### 执行流程

1. **验证**: 运行 `just qa` (lint + test + gen-check) 确保无错误
2. **提交**: 按照 Conventional Commits 格式提交，消息中注明当前阶段/模块
3. **版本号更新**: 在 Cargo.toml 中递增版本号（语义化版本）
4. **打 tag**: 创建对应版本的 git tag: `git tag v{版本号}`

### 检查点示例

| 里程碑 | 版本 | 说明 |
|--------|------|------|
| 项目初始化 | v0.0.1 | crate name locking |
| Codec 层完成 | v0.1.0-alpha.1 | JSON-RPC 编解码 |
| Transport 层完成 | v0.1.0-alpha.2 | StdioTransport |
| Proxy Core 完成 | v0.1.0-beta.1 | LSP 握手 + 路由 |
| 诊断压缩完成 | v0.1.0-rc.1 | MVP 功能冻结 |
| MVP 发布 | v0.1.0 | 正式版 |

### 历史版本管理

- `git tag` 列表展示所有已发布的检查点
- `git diff v0.1.0-alpha.1..v0.1.0-alpha.2` 查看阶段间变更
- 禁止在打 tag 后修改历史（如有紧急修复，递增 patch 版本）

## 参考资源

- [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/)
- [The Rust Style Guide](https://rust-lang.github.io/style-guide/)
- [Conventional Commits](https://www.conventionalcommits.org/)
- [LSP 3.17 Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/)
