From d9cc49b1404583429635d900a616bc4e8bf6f4fd Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 30 Mar 2026 04:28:52 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E9=9B=86=E4=B8=AD=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E6=96=87=E6=A1=A3=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=B9=B6=E8=A1=A5=E5=85=85=E6=A0=A1=E9=AA=8C=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Qwen-Coder --- CONTRIBUTING.md | 12 + README.md | 73 +++-- docs/SUMMARY.md | 11 +- docs/comparison/features.md | 2 +- docs/comparison/pricing.md | 32 +- docs/comparison/privacy-telemetry.md | 34 +- docs/comparison/system-requirements.md | 26 +- docs/data/CHANGELOG.md | 27 ++ docs/data/README.md | 77 +++++ docs/data/SCHEMA.md | 155 +++++++++ docs/data/agents-metadata.json | 415 +++++++++++++++++++++++++ docs/data/agents-metadata.schema.json | 166 ++++++++++ docs/evidence-index.md | 83 +++++ docs/guides/getting-started.md | 2 +- docs/tools/README.md | 4 +- scripts/check_all.py | 32 ++ scripts/check_data_schema.py | 161 ++++++++++ scripts/check_repo_consistency.py | 125 ++++++++ scripts/check_stale_data.py | 157 ++++++++++ 19 files changed, 1506 insertions(+), 88 deletions(-) create mode 100644 docs/data/CHANGELOG.md create mode 100644 docs/data/README.md create mode 100644 docs/data/SCHEMA.md create mode 100644 docs/data/agents-metadata.json create mode 100644 docs/data/agents-metadata.schema.json create mode 100644 docs/evidence-index.md create mode 100644 scripts/check_all.py create mode 100644 scripts/check_data_schema.py create mode 100644 scripts/check_repo_consistency.py create mode 100644 scripts/check_stale_data.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 187b9fc..1007967 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,12 @@ - 用新数据更新基准测试 - 修正不准确之处 - 提高清晰度 +- 如果涉及动态指标(Stars、下载量、价格、验证日期),优先更新 `docs/data/agents-metadata.json` +- 修改数据层前,先核对 `docs/data/SCHEMA.md` +- 如果动态数据发生变更,同步更新 `docs/data/CHANGELOG.md` +- 如果涉及证据完备度变化,同步更新 `docs/evidence-index.md` +- 提交前优先运行统一检查:`python3 scripts/check_all.py` +- 如需单独执行,可运行:`python3 scripts/check_data_schema.py`、`python3 scripts/check_repo_consistency.py` 和 `python3 scripts/check_stale_data.py` ### 3. 添加示例 @@ -160,6 +166,12 @@ - [ ] 信息准确 - [ ] 写作清晰中立 - [ ] 无不必要的格式更改 +- [ ] 动态数据已同步到 `docs/data/agents-metadata.json`(如适用) +- [ ] 已核对 `docs/data/SCHEMA.md`(如适用) +- [ ] 动态数据变更已记录到 `docs/data/CHANGELOG.md`(如适用) +- [ ] 证据状态已同步到 `docs/evidence-index.md`(如适用) +- [ ] 已运行 `python3 scripts/check_all.py` +- [ ] 如有需要,已分别运行 `python3 scripts/check_data_schema.py`、`python3 scripts/check_repo_consistency.py` 和 `python3 scripts/check_stale_data.py` ## 我们希望覆盖的领域 diff --git a/README.md b/README.md index b50f0d4..cfc111f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ **👉 [一页总结(选型速查)](./docs/SUMMARY.md)** — 给没时间看全部文档的人 +> **维护说明**:高频变化的数据(Stars、下载量、价格、验证日期)开始统一沉淀到 [`docs/data/`](./docs/data/README.md)。证据完备度与验证状态见 [`docs/evidence-index.md`](./docs/evidence-index.md)。 + ## 核心发现 ### 源码分析纠正的"常识" @@ -29,26 +31,21 @@ > Rust 二进制(50ms)比 Node.js SEA(72ms)快 30%,比 npm 包(1.5s)快 **30 倍**。Qwen Code 作为 Gemini CLI 分叉,安装仅 48MB(上游 509MB 的 9%),启动快 2.5 倍。 -### 实际采用量(2026-03-26 API 实时查询) - -| Agent | npm 周下载 | 4 周趋势 | Stars | 版本数 | 首发 | -|-------|-----------|---------|-------|-------|------| -| **Claude Code** | **1,020 万** | 934→951→987→1,020 万 ↑ | 83k | 359 | 2025-02 | -| **Codex CLI** | **368 万** | 214→275→356→368 万 ↑↑ | 68k | 1,543 | 2025-04 | -| **Gemini CLI** | 74 万 | 76→66→73→74 万 → | **99k** | 539 | 2025-06 | -| **Copilot CLI** | 64 万 | 19→25→42→64 万 **↑↑↑** | 10k | 588 | 2025-09 | -| **Qwen Code** | 8.4 万 | 6→16→11→8.4 万 ↑ | 21k | 353 | 2025-07 | +### 实际采用量(汇总入口) -| Agent | PyPI 月下载 | Stars | -|-------|-----------|-------| -| **OpenHands** | **125 万** | 70k | -| **Aider** | 78 万 | 42k | -| **Kimi CLI** | 45 万 | 7k | -| **OpenCode** | — | **~132k** | +> 高频变化数据已集中维护在 [`docs/data/agents-metadata.json`](./docs/data/agents-metadata.json)。 +> +> 当前 README 仅保留结论性摘要,详细数字、验证日期和证据状态请查看: +> - [`docs/data/agents-metadata.json`](./docs/data/agents-metadata.json) +> - [`docs/evidence-index.md`](./docs/evidence-index.md) +> - [`docs/comparison/evolution-community.md`](./docs/comparison/evolution-community.md) -> **Stars ≠ 采用**:Gemini CLI Stars(99k)是 Claude Code(83k)的 1.2 倍,但 npm 下载仅其 **7%**。Copilot CLI 增长最猛(4 周 ↑240%)。 +- Claude Code、Codex CLI 在 npm 生态采用量领先 +- Gemini CLI 社区热度高,但采用量与 Claude Code 不完全同步 +- Qwen Code、Kimi CLI 在中文开发者语境下增长明显 +- OpenHands、Aider 在 Python / research-oriented 场景保持稳定关注度 -*数据来源:[npm Registry API](https://api.npmjs.org/)、[PyPI Stats](https://pypistats.org/)、`gh api`* +*数据来源:[npm Registry API](https://api.npmjs.org/)、[PyPI Stats](https://pypistats.org/)、`gh api`;具体数值请以 `docs/data/agents-metadata.json` 为准。* --- @@ -59,7 +56,7 @@ | **日常编码** | Claude Code 或 Aider | 最强推理 / 最好 Git 集成 | | **免费使用** | Qwen Code 或 Gemini CLI | 1000 次/天免费 OAuth / Google 账号 | | **多模型切换** | OpenCode 或 Goose | 100+ models.dev / 58+ 提供商 | -| **VS Code 用户** | Cline 或 Continue | 59k Stars IDE 原生 / PR Checks | +| **VS Code 用户** | Cline 或 Continue | IDE 原生集成 / PR Checks | | **中文开发者** | Qwen Code 或 Kimi CLI | 6 语言 UI / 月之暗面中文模型 | | **CI/CD 自动化** | SWE-agent 或 OpenHands | 批量评估 / Docker 沙箱 | | **安全沙箱** | Codex CLI 或 Gemini CLI | 三平台 OS 沙箱 / TOML 策略引擎 | @@ -70,25 +67,27 @@ ## 快速对比表 -| Agent | 开发者 | 许可证 | Stars | 语言 | 提供商 | 特色 | -|------|--------|--------|-------|------|-------|------| -| [OpenCode](./docs/tools/opencode/) | Anomaly | MIT | **~132k** | TypeScript(Bun) | 100+ | 多客户端(TUI+Web+桌面),37 LSP | -| [Gemini CLI](./docs/tools/gemini-cli/) | Google | Apache-2.0 | **99k** | TypeScript | 1 | 8 策略模型路由,TOML 策略引擎 | -| [Claude Code](./docs/tools/claude-code/) | Anthropic | 专有 | **83k** | Rust | 1 | 50ms 启动,24 Hook 事件,Channels | -| [OpenHands](./docs/tools/openhands.md) | OpenHands | MIT | **70k** | Python | 100+ | Docker 沙箱,三层安全,多代理 | -| [Codex CLI](./docs/tools/codex-cli/) | OpenAI | Apache-2.0 | **68k** | Rust | 1 | 三平台 OS 沙箱,Cloud 远程执行 | -| [Cline](./docs/tools/cline.md) | Cline | Apache-2.0 | **59k** | TypeScript | 48+ | VS Code 原生,Git Checkpoint | -| [Aider](./docs/tools/aider/) | Paul Gauthier | GPL-3.0 | **42k** | Python | 100+ | 14 编辑格式,三槽位模型,/undo | -| [Goose](./docs/tools/goose/) | Block | Apache-2.0 | **34k** | Rust | 58+ | MCP 原生,11 Platform Extension,Recipe + Cron 调度 | -| [Continue](./docs/tools/continue.md) | Continue | Apache-2.0 | **32k** | TypeScript | 60+ | PR Checks CI 审查,语义索引 | -| [Warp](./docs/tools/warp.md) | Warp | 专有 | **26k** | Rust | 多种 | GPU 渲染终端,块结构输出 | -| [Qwen Code](./docs/tools/qwen-code/) | 阿里云 | Apache-2.0 | **21k** | TypeScript | 6+ | 免费 1000 次/天,Arena 多模型竞争,41 命令 | -| [SWE-agent](./docs/tools/swe-agent.md) | Princeton | MIT | **19k** | Python | 100+ | SWE-bench 评估,Docker 沙箱 | -| [Copilot CLI](./docs/tools/copilot-cli/) | GitHub | 专有 | **10k** | Shell | 多种 | 67 GitHub 工具,增长 ↑240%/月 | -| [Kimi CLI](./docs/tools/kimi-cli/) | 月之暗面 | Apache-2.0 | **7k** | Python | 6 | Wire 协议,D-Mail 时间回溯 | -| [Cursor](./docs/tools/cursor-cli.md) | Cursor | 专有 | - | TypeScript | 多种 | AI 原生 IDE,Background Agent | -| [Qoder CLI](./docs/tools/qoder-cli/) | QoderAI | 专有 | - | Go | 多种 | Quest 模式,Claude Code 兼容 | -| [Oh My OpenAgent](./docs/tools/oh-my-openagent.md) | code-yeongyu | SUL-1.0 | **~44k** | TypeScript | 多种 | OpenCode Harness 层,7~10 Discipline Agent | +> 为减少动态数字重复维护,Stars / 下载量 / 免费层等高频变化数据已迁移到 [`docs/data/agents-metadata.json`](./docs/data/agents-metadata.json)。 + +| Agent | 开发者 | 许可证 | 语言 | 提供商 | 特色 | +|------|--------|--------|------|-------|------| +| [OpenCode](./docs/tools/opencode/) | Anomaly | MIT | TypeScript(Bun) | 100+ | 多客户端(TUI+Web+桌面),37 LSP | +| [Gemini CLI](./docs/tools/gemini-cli/) | Google | Apache-2.0 | TypeScript | 1 | 8 策略模型路由,TOML 策略引擎 | +| [Claude Code](./docs/tools/claude-code/) | Anthropic | 专有 | Rust | 1 | 50ms 启动,24 Hook 事件,Channels | +| [OpenHands](./docs/tools/openhands.md) | OpenHands | MIT | Python | 100+ | Docker 沙箱,三层安全,多代理 | +| [Codex CLI](./docs/tools/codex-cli/) | OpenAI | Apache-2.0 | Rust | 1 | 三平台 OS 沙箱,Cloud 远程执行 | +| [Cline](./docs/tools/cline.md) | Cline | Apache-2.0 | TypeScript | 48+ | VS Code 原生,Git Checkpoint | +| [Aider](./docs/tools/aider/) | Paul Gauthier | GPL-3.0 | Python | 100+ | 14 编辑格式,三槽位模型,/undo | +| [Goose](./docs/tools/goose/) | Block | Apache-2.0 | Rust | 58+ | MCP 原生,11 Platform Extension,Recipe + Cron 调度 | +| [Continue](./docs/tools/continue.md) | Continue | Apache-2.0 | TypeScript | 60+ | PR Checks CI 审查,语义索引 | +| [Warp](./docs/tools/warp.md) | Warp | 专有 | Rust | 多种 | GPU 渲染终端,块结构输出 | +| [Qwen Code](./docs/tools/qwen-code/) | 阿里云 | Apache-2.0 | TypeScript | 6+ | 免费 1000 次/天,Arena 多模型竞争,41 命令 | +| [SWE-agent](./docs/tools/swe-agent.md) | Princeton | MIT | Python | 100+ | SWE-bench 评估,Docker 沙箱 | +| [Copilot CLI](./docs/tools/copilot-cli/) | GitHub | 专有 | Shell | 多种 | 67 GitHub 工具,GitHub 生态集成 | +| [Kimi CLI](./docs/tools/kimi-cli/) | 月之暗面 | Apache-2.0 | Python | 6 | Wire 协议,D-Mail 时间回溯 | +| [Cursor](./docs/tools/cursor-cli.md) | Cursor | 专有 | TypeScript | 多种 | AI 原生 IDE,Background Agent | +| [Qoder CLI](./docs/tools/qoder-cli/) | QoderAI | 专有 | Go | 多种 | Quest 模式,Claude Code 兼容 | +| [Oh My OpenAgent](./docs/tools/oh-my-openagent.md) | code-yeongyu | SUL-1.0 | TypeScript | 多种 | OpenCode Harness 层,7~10 Discipline Agent | --- diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5017022..3623acf 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,6 +1,8 @@ # 0. 一页总结:AI 编程 Code Agent 选型 > 给没时间看 34,600+ 行文档的人。2026 年 3 月。 +> +> **维护说明**:本页包含若干动态数据摘要。更新时请先核对 [`docs/data/agents-metadata.json`](./data/agents-metadata.json) 与 [`docs/evidence-index.md`](./evidence-index.md)。 ## 一句话定位 @@ -10,8 +12,8 @@ | **Copilot CLI** | GitHub 原生集成,读取所有主流指令文件(CLAUDE.md/GEMINI.md/AGENTS.md),67 个内置工具 | GitHub 重度用户和企业团队 | | **Codex CLI** | OpenAI 官方,三平台 OS 级沙箱(Seatbelt/Bubblewrap/Windows Tokens),Rust 核心 | 安全敏感场景和 OpenAI 生态用户 | | **Aider** | Git 原生老牌工具,14 种编辑格式,PageRank 仓库地图,99% 一人开发 | Git 重度用户和喜欢细粒度控制的开发者 | -| **Gemini CLI** | Google 官方,Stars 最多(99k),TOML 策略引擎,四阶段压缩算法 | Google Cloud 用户和大团队 | -| **Qwen Code** | Gemini CLI 分叉 + 阿里云生态,每天 1000 次免费,Arena 多模型竞争模式 | 中文开发者和成本敏感用户 | +| **Gemini CLI** | Google 官方,TOML 策略引擎,四阶段压缩算法 | Google Cloud 用户和大团队 | +| **Qwen Code** | Gemini CLI 分叉 + 阿里云生态,提供免费层,Arena 多模型竞争模式 | 中文开发者和成本敏感用户 | | **Kimi CLI** | 月之暗面出品,零遥测(隐私最佳),双模式交互(TUI + Shell) | 隐私敏感用户和国内开发者 | | **Goose** | Block 出品后捐赠 Linux 基金会,MCP 原生架构,398 个贡献者 | MCP 生态和开源社区 | | **OpenCode** | 多客户端(TUI+Web+桌面),37 个 LSP,100+ 模型提供商 | 需要多客户端和 IDE 集成的团队 | @@ -34,15 +36,14 @@ ## 关键数字对比 +> 动态数字(Stars、免费层、验证日期)请以 [`docs/data/agents-metadata.json`](./data/agents-metadata.json) 为准。 + | | Claude Code | Copilot CLI | Codex CLI | Aider | Gemini CLI | Qwen Code | Kimi CLI | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:| -| **Stars** | 82k | 10k | 67k | 42k | 99k | 21k | 7k | | **命令数** | ~79 | 34 | 28 | 42 | 39 | 40+ | 28 | | **工具数** | 20+ | 67 | 9 | — | 23 | 18 | 18 | | **沙箱** | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ | ✗ | | **遥测** | 782 事件 | 有 | 有 | opt-in 10% | 194 键 | 阿里 RUM | **零** | -| **免费层** | ✗ | 有限 | ✗ | ✗ | 1500/天 | 1000/天 | 有限 | -| **月费** | $20-200 | $0-39 | 按量 | 按量 | 按量 | 按量 | 按量 | ## 安全等级 diff --git a/docs/comparison/features.md b/docs/comparison/features.md index 18c16c4..45b5c6a 100644 --- a/docs/comparison/features.md +++ b/docs/comparison/features.md @@ -175,7 +175,7 @@ | Copilot CLI | 订阅制 | 1 premium request | 1 premium request | Copilot 订阅含配额 | | Cursor | 订阅制 | 1 fast request | 多个 request | Pro $20/月 500 次 | | Goose | API 按量 | ~$0.02-0.10 | ~$0.50-3 | 多提供商 | -| Gemini CLI | API 按量/免费 | 免费 | ~$0.10-1 | 有免费层级 | +| Gemini CLI | API 按量/免费 | 免费 | ~$0.10-1 | 1500 次/天免费层 | | OpenHands | API 按量 | ~$0.05-0.20 | ~$2-10 | 多代理消耗更高 | | Qwen Code | 免费/API | 免费 | 免费 | 每日 1000 次 | | Kimi CLI | API 按量 | ~$0.01-0.05 | ~$0.20-1 | 国内模型成本低 | diff --git a/docs/comparison/pricing.md b/docs/comparison/pricing.md index e525d1f..e2a3863 100644 --- a/docs/comparison/pricing.md +++ b/docs/comparison/pricing.md @@ -1,20 +1,22 @@ # 14. 定价与成本对比 -> 2026 年 3 月数据。AI 编程工具的费用模式差异巨大——从完全免费到每月 $200+。 +> 动态定价与免费层信息请优先以 [`../data/agents-metadata.json`](../data/agents-metadata.json) 和 [`../data/CHANGELOG.md`](../data/CHANGELOG.md) 为准。本文保留结构化对比与成本分析方法。 ## 定价模式总览 -| Agent | 模式 | 月费 | 免费层 | 按量计费 | -|------|------|------|--------|---------| -| **Claude Code** | 订阅 + API | Pro $20, Max $100/$200 | 无 | API: 按 token | -| **Copilot CLI** | 订阅 | Free/Pro $10/Business $19/Enterprise $39 | ✓(有限) | Premium Requests 配额 | -| **Codex CLI** | API 按量 | 无月费 | 无 | 按 token | -| **Aider** | API 按量(自带 key) | 无月费 | 无(需 API key) | 取决于所选模型 | -| **Gemini CLI** | API + 免费层 | 无月费 | **1,500 次/天**(Gemini API) | API 按量 | -| **Qwen Code** | 免费 + API | 无月费 | **1,000 次/天**(OAuth) | DashScope 按量 | -| **Kimi CLI** | API 按量 | 无月费 | 有限免费额度 | Moonshot API 按量 | -| **Goose** | API 按量(自带 key) | 无月费 | 无 | 取决于所选模型 | -| **OpenCode** | API 按量 | 无月费 | 无 | 取决于提供商 | +> 本表仅保留定价结构。具体月费、免费层额度等高频变化数字请查阅 `docs/data/agents-metadata.json`。 + +| Agent | 模式 | 免费层形态 | 按量计费 | +|------|------|-----------|---------| +| **Claude Code** | 订阅 + API | 无 | API: 按 token | +| **Copilot CLI** | 订阅 | 有限 | Premium Requests 配额 | +| **Codex CLI** | API 按量 | 无 | 按 token | +| **Aider** | API 按量(自带 key) | 无 | 取决于所选模型 | +| **Gemini CLI** | API + 免费层 | 每日额度 | API 按量 | +| **Qwen Code** | 免费 + API | 每日额度 | DashScope 按量 | +| **Kimi CLI** | API 按量 | 有限免费额度 | Moonshot API 按量 | +| **Goose** | API 按量(自带 key) | 无 | 取决于所选模型 | +| **OpenCode** | API 按量 | 无 | 取决于提供商 | ## Claude Code 详细定价 @@ -94,12 +96,12 @@ API 按量价格(参考): | 策略 | 适用工具 | 说明 | |------|---------|------| | **选择小模型做简单任务** | 全部 | Haiku/mini 比 Opus 便宜 10-20 倍 | -| **利用免费层** | Gemini CLI, Qwen Code, Copilot | 日常简单任务可完全免费 | +| **利用免费层** | Gemini CLI, Qwen Code, Copilot | 先核对 `docs/data/agents-metadata.json` 中当前免费层额度 | | **Prompt Caching** | Claude Code, Aider | 减少重复系统提示的 token 费用 | | **上下文压缩** | 全部 | 定期 /compact 减少 token 累积 | | **Aider 的 /architect** | Aider | 用强模型规划、弱模型执行,节省 50%+ | -| **Copilot 免费模型** | Copilot CLI | gpt-5-mini 和 gpt-4.1 为 0x 倍率 | -| **订阅 vs API** | Claude Code | 日均 >$3.3 时 Max $100 更划算 | +| **Copilot 免费模型** | Copilot CLI | 具体模型倍率以官方定价页为准 | +| **订阅 vs API** | Claude Code | 应结合最新月费与任务频率重新估算 | --- diff --git a/docs/comparison/privacy-telemetry.md b/docs/comparison/privacy-telemetry.md index efa3d32..a2492ca 100644 --- a/docs/comparison/privacy-telemetry.md +++ b/docs/comparison/privacy-telemetry.md @@ -1,21 +1,23 @@ # 3. 隐私与遥测对比 -> 各 CLI Agent 的数据采集、安全监控和隐私控制对比。基于源码分析和二进制反编译。 +> 各 CLI Agent 的数据采集、安全监控和隐私控制对比。高频变化的验证日期和证据状态请结合 [`../data/CHANGELOG.md`](../data/CHANGELOG.md) 与 [`../evidence-index.md`](../evidence-index.md) 一起查看。 ## 遥测系统对比 +> 本表保留能力边界与采集类型。若默认状态或证据完备度发生变化,请同步更新 `docs/evidence-index.md` 与 `docs/data/CHANGELOG.md`。 + | Agent | 遥测提供商 | 默认状态 | 采集 Machine ID | 采集硬件信息 | 采集 MAC 地址 | |------|-----------|---------|-----------------|-------------|-------------| -| **Claude Code** | Anthropic Metrics + Datadog + Segment | 开启 | **是**(IOPlatformUUID / /etc/machine-id) | **是**(CPU、主机名、平台) | **否** | -| **Copilot CLI** | GitHub/Microsoft 内部 | 开启 | 未确认 | **是**(平台信息) | 未确认 | -| **Codex CLI** | OpenAI 内部 | 开启 | 未确认 | **是**(平台信息) | 未确认 | -| **Aider** | PostHog | **关闭**(opt-in,10% 采样) | **是**(随机 UUID) | **是**(OS、架构) | **否** | -| **Gemini CLI** | OpenTelemetry + Google Clearcut | 开启 | **是**(持久化 UUID) | **是**(CPU/GPU/RAM 详细) | **否** | -| **Kimi CLI** | **无** | — | **否** | **否** | **否** | -| **OpenCode** | **无** | — | **否** | **否** | **否** | -| **Goose** | PostHog | **关闭**(opt-in) | **是**(随机 UUID) | **是**(OS、架构) | **否** | -| **Qwen Code** | OTEL(重品牌)+ **阿里云 RUM**(新增) | 开启 | **是**(继承 Gemini UUID) | **是**(继承 CPU/GPU/RAM) | **否** | -| **Qoder CLI** | Qoder 自有(api1.qoder.sh) | 开启 | **是**(`getMachineKey`) | 未确认 | **否** | +| **Claude Code** | Anthropic Metrics + Datadog + Segment | 开启 | 是 | 是 | 否 | +| **Copilot CLI** | GitHub/Microsoft 内部 | 开启 | 未确认 | 是 | 未确认 | +| **Codex CLI** | OpenAI 内部 | 开启 | 未确认 | 是 | 未确认 | +| **Aider** | PostHog | 默认关闭(opt-in) | 是 | 是 | 否 | +| **Gemini CLI** | OpenTelemetry + Google Clearcut | 开启 | 是 | 是 | 否 | +| **Kimi CLI** | 无 | — | 否 | 否 | 否 | +| **OpenCode** | 无 | — | 否 | 否 | 否 | +| **Goose** | PostHog | 默认关闭(opt-in) | 是 | 是 | 否 | +| **Qwen Code** | OTEL(重品牌)+ 阿里云 RUM | 开启 | 是 | 是 | 否 | +| **Qoder CLI** | Qoder 自有 | 开启 | 是 | 未确认 | 否 | ## 遥测端点 @@ -102,13 +104,15 @@ ## 证据来源 +> 证据状态、最后验证日期、补强优先级请统一查看 [`../evidence-index.md`](../evidence-index.md)。本节只保留分析方式入口。 + | Agent | 分析方式 | 证据文件 | |------|----------|----------| -| Claude Code | Bun 字节码反编译(v2.1.81) | `claude-code/EVIDENCE.md` | -| Copilot CLI | Node.js SEA 反编译(v1.0.11/0.0.403) | `copilot-cli/EVIDENCE.md` | -| Codex CLI | Rust 二进制 strings + --help(v0.116.0) | `codex-cli/EVIDENCE.md` | +| Claude Code | Bun 字节码反编译 | `claude-code/EVIDENCE.md` | +| Copilot CLI | Node.js SEA 反编译 | `copilot-cli/EVIDENCE.md` | +| Codex CLI | Rust 二进制 strings + --help | `codex-cli/EVIDENCE.md` | | Aider | 源码 `aider/analytics.py` | `aider/EVIDENCE.md` | | Gemini CLI | 源码 `packages/core/src/telemetry/` + `safety/` | `gemini-cli/EVIDENCE.md` | | Kimi CLI | 源码 `src/kimi_cli/` 全量搜索 | `kimi-cli/EVIDENCE.md` | | OpenCode | 源码 `internal/` 全量搜索 | `opencode/EVIDENCE.md` | -| Goose | 源码 `crates/goose/src/` + `crates/goose-cli/src/` | 源码分析 | +| Goose | 源码 `crates/goose/src/` + `crates/goose-cli/src/` | `goose/EVIDENCE.md` | diff --git a/docs/comparison/system-requirements.md b/docs/comparison/system-requirements.md index 0f51dc1..b854191 100644 --- a/docs/comparison/system-requirements.md +++ b/docs/comparison/system-requirements.md @@ -1,6 +1,6 @@ # 5. 系统要求对比 -> 各工具的运行环境要求。数据来源: package.json engines / pyproject.toml / 二进制分析。 +> 各工具的运行环境要求。动态运行时版本与体积信息如有变化,请优先参考 [`../data/CHANGELOG.md`](../data/CHANGELOG.md) 记录的最近更新时间,并结合源码/安装包重新验证。 ## 语言与运行时要求 @@ -37,16 +37,18 @@ ## 二进制大小 -| Agent | 二进制大小 | 包含内容 | -|------|-----------|---------| -| **Claude Code** | ~227 MB | Bun 运行时 + JS bundle + 原生模块 (tree-sitter, sharp, audio) | -| **Copilot CLI** | ~133 MB | Node.js SEA + JS bundle (15MB + 11MB) + 原生模块 (keytar, pty) | -| **Codex CLI** | ~137 MB | Rust 静态链接 (musl) + ripgrep | -| **Goose** | ~50 MB | Rust 编译 (rmcp SDK) | -| **Gemini CLI** | ~50 MB (npm) | TypeScript + WASM (tree-sitter) | -| **Qwen Code** | ~55 MB (npm) | TypeScript + WASM (tree-sitter) | -| **Aider** | ~20 MB (pip) | Python 包 + 依赖 | -| **Kimi CLI** | ~15 MB (pip) | Python 包 + 依赖 | +> 安装体积和包大小属于高频变化信息,详细数值建议与 `docs/data/CHANGELOG.md` 一起维护;本表保留量级与组成说明。 + +| Agent | 体积量级 | 包含内容 | +|------|---------|---------| +| **Claude Code** | ~200MB+ | Bun 运行时 + JS bundle + 原生模块 (tree-sitter, sharp, audio) | +| **Copilot CLI** | ~100MB+ | Node.js SEA + JS bundle + 原生模块 (keytar, pty) | +| **Codex CLI** | ~100MB+ | Rust 静态链接 (musl) + ripgrep | +| **Goose** | ~50MB | Rust 编译 (rmcp SDK) | +| **Gemini CLI** | ~50MB | TypeScript + WASM (tree-sitter) | +| **Qwen Code** | ~50MB | TypeScript + WASM (tree-sitter) | +| **Aider** | ~20MB | Python 包 + 依赖 | +| **Kimi CLI** | ~15MB | Python 包 + 依赖 | ## 沙箱依赖 @@ -79,6 +81,6 @@ |------|-----| | 实现语言 | Go | | 运行时 | 无需额外运行时(静态链接二进制) | -| 二进制大小 | 43 MB | +| 二进制大小 | ~40MB | | 安装 | `npm install -g @qoder-ai/qodercli` | | Node.js 要求 | 仅 npm 安装需要(二进制本身不依赖 Node.js) | diff --git a/docs/data/CHANGELOG.md b/docs/data/CHANGELOG.md new file mode 100644 index 0000000..26788a0 --- /dev/null +++ b/docs/data/CHANGELOG.md @@ -0,0 +1,27 @@ +# docs/data 变更日志 + +> 用于记录动态数据层的更新时间、更新范围、来源与备注。 +> +> 目标:降低“数字改了但没人知道改了什么”的维护成本。 + +## 记录格式 + +每次更新建议记录以下内容: + +| 日期 | 文件 | 更新范围 | 数据来源 | 备注 | +|------|------|---------|---------|------| +| 2026-03-30 | `agents-metadata.json` | Stars / 下载量 / 证据状态 | npm Registry API / PyPI Stats / `gh api` | 初始化统一数据层 | + +## 变更记录 + +| 日期 | 文件 | 更新范围 | 数据来源 | 备注 | +|------|------|---------|---------|------| +| 2026-03-30 | `agents-metadata.json` | 初始化统一数据层;补充 Agent 元数据、免费层、证据状态、最后验证日期 | npm Registry API / PyPI Stats / `gh api` / 各 Agent 文档与 EVIDENCE.md | 首次建立 `docs/data/` | +| 2026-03-30 | `README.md`, `docs/SUMMARY.md` | 收敛高频变化的 Stars / 下载量 / 免费层引用,改为指向数据层 | `docs/data/agents-metadata.json` | 降低重复维护 | +| 2026-03-30 | `scripts/check_stale_data.py`, `scripts/check_all.py` | 新增动态数据漂移检查与统一检查入口 | 本仓库维护脚本 | 用于提交前校验 | + +## 使用建议 + +- 每次批量更新动态数据时,同时更新本文件 +- 如果只是修正文案、不涉及真实数据变更,可在备注中标明“文案收敛” +- 对外引用动态数字时,优先从 `docs/data/agents-metadata.json` 读取,再在本文件记录更新时间 diff --git a/docs/data/README.md b/docs/data/README.md new file mode 100644 index 0000000..d9ca9e0 --- /dev/null +++ b/docs/data/README.md @@ -0,0 +1,77 @@ +# docs/data + +本目录存放需要跨多个文档复用、且容易过时的数据源。 + +## 文件说明 + +- `agents-metadata.json` + - 统一维护 Agent 的动态指标和基础元数据 + - 包括:Stars、下载量、免费层、定价摘要、证据状态、最后验证日期 +- `SCHEMA.md` + - 说明 `agents-metadata.json` 的字段定义、推荐取值和维护约定 +- `agents-metadata.schema.json` + - 机器可校验的 JSON Schema,用于脚本校验字段结构 +- `CHANGELOG.md` + - 记录动态数据层的更新时间、来源、调整范围和维护备注 + +## 使用原则 + +### 1. 区分静态事实与动态事实 + +- **静态事实**:实现语言、架构模式、是否为分叉、证据路径 +- **动态事实**:Stars、下载量、价格、版本数、启动时间、趋势 + +### 2. 先看 schema,再改数据 + +修改 `agents-metadata.json` 前,建议先阅读 [`SCHEMA.md`](./SCHEMA.md)。结构校验由 [`agents-metadata.schema.json`](./agents-metadata.schema.json) 和 `python3 scripts/check_data_schema.py` 提供。 + +### 3. 动态事实必须带时间戳 + +所有动态数据都应包含 `as_of` 或 `last_verified`: + +```json +{ + "stars": "83k", + "downloads": { + "type": "npm_weekly", + "value": "1020万", + "as_of": "2026-03-26" + } +} +``` + +### 3. 汇总页尽量引用数据源,而不是手工散落维护 + +以下页面应优先从本目录读取或至少手动同步校验: + +- `README.md` +- `docs/SUMMARY.md` +- `docs/comparison/features.md` +- `docs/comparison/pricing.md` +- `docs/comparison/privacy-telemetry.md` +- `docs/comparison/system-requirements.md` +- `docs/tools/README.md` + +### 4. 证据状态标准 + +- `complete`:有完整多文件分析,且存在 `EVIDENCE.md` +- `partial`:有目录级分析,但证据仍不完整或主要来自二进制/外部线索 +- `single-file-only`:目前只有单文件综述,没有目录级深挖 + +## 维护建议 + +- 每次批量更新 Stars / 下载量 / 定价后,同步更新 `last_updated` +- 每次发生动态数据变更时,同时更新 `docs/data/CHANGELOG.md` +- 每次新增 Agent 后,同时补充此数据文件 +- 提交前运行: + +```bash +python3 scripts/check_all.py +``` + +如需单独执行,也可以使用: + +```bash +python3 scripts/check_repo_consistency.py +python3 scripts/check_stale_data.py +``` diff --git a/docs/data/SCHEMA.md b/docs/data/SCHEMA.md new file mode 100644 index 0000000..af00799 --- /dev/null +++ b/docs/data/SCHEMA.md @@ -0,0 +1,155 @@ +# agents-metadata.json Schema 说明 + +> 本文档说明 `docs/data/agents-metadata.json` 的字段结构、用途和维护约定。 +> +> 目标:让维护者知道每个字段的含义、哪些属于动态数据、哪些属于静态事实,以及何时应该更新。 + +## 顶层结构 + +```json +{ + "schema_version": 1, + "last_updated": "2026-03-30", + "maintainer_note": "...", + "agents": [ + { + "id": "claude-code", + "name": "Claude Code", + "category": "deep-analysis", + "license": "专有", + "developer": "Anthropic", + "implementation_language": "Rust", + "runtime": "Bun(内嵌)/ 原生二进制", + "package_ecosystem": "npm", + "stars": "83k", + "downloads": { + "type": "npm_weekly", + "value": "1020万", + "as_of": "2026-03-26" + }, + "pricing_summary": "$20-200 / API 按量", + "free_tier": "无", + "evidence": { + "status": "complete", + "source_type": "binary-analysis", + "evidence_path": "docs/tools/claude-code/EVIDENCE.md", + "last_verified": "2026-03-26" + } + } + ] +} +``` + +## 顶层字段 + +| 字段 | 类型 | 必填 | 含义 | +|------|------|------|------| +| `schema_version` | number | 是 | schema 版本号,用于后续字段演进 | +| `last_updated` | string (`YYYY-MM-DD`) | 是 | 数据文件最后一次整体更新日期 | +| `maintainer_note` | string | 否 | 给维护者的说明 | +| `agents` | array | 是 | Agent 元数据列表 | + +## Agent 对象字段 + +| 字段 | 类型 | 必填 | 动态/静态 | 含义 | +|------|------|------|-----------|------| +| `id` | string | 是 | 静态 | 稳定标识符,建议 kebab-case | +| `name` | string | 是 | 静态 | 面向文档显示的 Agent 名称 | +| `category` | string | 是 | 半静态 | 当前文档覆盖层级,如 `deep-analysis` / `single-file` | +| `license` | string | 是 | 半静态 | 许可证类型 | +| `developer` | string | 是 | 静态 | 开发者/组织 | +| `implementation_language` | string | 是 | 静态 | 主要实现语言 | +| `runtime` | string | 是 | 半静态 | 运行时或分发形态 | +| `package_ecosystem` | string | 是 | 半静态 | npm / pypi / desktop / none 等 | +| `stars` | string | 否 | 动态 | 社区热度指标,建议保持与外部来源一致 | +| `downloads` | object | 否 | 动态 | 下载量信息 | +| `pricing_summary` | string | 否 | 动态 | 定价摘要,适合汇总页使用 | +| `free_tier` | string | 否 | 动态 | 免费层摘要 | +| `evidence` | object | 是 | 半静态 | 证据完备度与入口 | + +## downloads 对象 + +| 字段 | 类型 | 必填 | 含义 | +|------|------|------|------| +| `type` | string | 是 | 指标类型,如 `npm_weekly` / `pypi_monthly` / `none` / `unknown` | +| `value` | string | 是 | 下载量展示值,如 `1020万`、`—` | +| `as_of` | string (`YYYY-MM-DD`) | 是 | 该下载量的统计时间 | + +## evidence 对象 + +| 字段 | 类型 | 必填 | 含义 | +|------|------|------|------| +| `status` | string | 是 | `complete` / `partial` / `single-file-only` | +| `source_type` | string | 是 | `source-analysis` / `binary-analysis` / `summary-analysis` / `official-docs` | +| `evidence_path` | string | 是 | 对应证据文档路径 | +| `last_verified` | string (`YYYY-MM-DD`) | 是 | 最近一次证据验证日期 | + +## 推荐取值 + +### `category` + +- `deep-analysis`:已有目录级多文件分析 +- `single-file`:目前只有单文件综述 + +### `downloads.type` + +- `npm_weekly` +- `pypi_monthly` +- `none` +- `unknown` + +### `evidence.status` + +- `complete` +- `partial` +- `single-file-only` + +### `evidence.source_type` + +- `source-analysis` +- `binary-analysis` +- `summary-analysis` +- `official-docs` + +## 维护约定 + +### 1. 哪些情况需要更新 `last_updated` + +以下任一情况都应更新顶层 `last_updated`: +- 批量更新 Stars / 下载量 / 免费层 / 定价摘要 +- 新增 Agent +- 修改 evidence 状态或最后验证日期 + +### 2. 哪些情况需要更新 `docs/data/CHANGELOG.md` + +以下情况建议同步记录: +- 动态数据变化 +- 字段结构变化 +- 批量文档收敛到数据层 + +### 3. 哪些情况需要同步检查其他文档 + +当下列字段变化时,应同步检查: + +| 字段 | 建议同步检查 | +|------|-------------| +| `stars` / `downloads` | `README.md`, `docs/SUMMARY.md`, `docs/comparison/features.md` | +| `pricing_summary` / `free_tier` | `docs/comparison/pricing.md`, `README.md`, `docs/SUMMARY.md` | +| `runtime` / `implementation_language` | `docs/comparison/system-requirements.md`, `README.md` | +| `evidence.*` | `docs/evidence-index.md`, `docs/comparison/privacy-telemetry.md` | + +## 校验建议 + +提交前运行: + +```bash +python3 scripts/check_all.py +``` + +如需单独排查: + +```bash +python3 scripts/check_data_schema.py +python3 scripts/check_repo_consistency.py +python3 scripts/check_stale_data.py +``` diff --git a/docs/data/agents-metadata.json b/docs/data/agents-metadata.json new file mode 100644 index 0000000..5aea711 --- /dev/null +++ b/docs/data/agents-metadata.json @@ -0,0 +1,415 @@ +{ + "schema_version": 1, + "last_updated": "2026-03-30", + "maintainer_note": "Dynamic metrics should be updated from official APIs or clearly labeled estimates. Static architecture facts should be sourced from repository analysis or EVIDENCE.md.", + "agents": [ + { + "id": "claude-code", + "name": "Claude Code", + "category": "deep-analysis", + "license": "专有", + "developer": "Anthropic", + "implementation_language": "Rust", + "runtime": "Bun(内嵌)/ 原生二进制", + "package_ecosystem": "npm", + "stars": "83k", + "downloads": { + "type": "npm_weekly", + "value": "1020万", + "as_of": "2026-03-26" + }, + "pricing_summary": "$20-200 / API 按量", + "free_tier": "无", + "evidence": { + "status": "complete", + "source_type": "binary-analysis", + "evidence_path": "docs/tools/claude-code/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "copilot-cli", + "name": "Copilot CLI", + "category": "deep-analysis", + "license": "专有", + "developer": "GitHub", + "implementation_language": "TypeScript / Node.js SEA", + "runtime": "原生二进制(回退需 Node.js >= 24)", + "package_ecosystem": "npm", + "stars": "10k", + "downloads": { + "type": "npm_weekly", + "value": "64万", + "as_of": "2026-03-26" + }, + "pricing_summary": "$0-39 / Premium Requests", + "free_tier": "有限", + "evidence": { + "status": "complete", + "source_type": "binary-analysis", + "evidence_path": "docs/tools/copilot-cli/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "codex-cli", + "name": "Codex CLI", + "category": "deep-analysis", + "license": "Apache-2.0", + "developer": "OpenAI", + "implementation_language": "Rust + Node.js wrapper", + "runtime": "原生二进制(npm 启动器需 Node.js >= 16)", + "package_ecosystem": "npm", + "stars": "68k", + "downloads": { + "type": "npm_weekly", + "value": "368万", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "无", + "evidence": { + "status": "complete", + "source_type": "binary-analysis", + "evidence_path": "docs/tools/codex-cli/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "gemini-cli", + "name": "Gemini CLI", + "category": "deep-analysis", + "license": "Apache-2.0", + "developer": "Google", + "implementation_language": "TypeScript", + "runtime": "Node.js >= 20", + "package_ecosystem": "npm", + "stars": "99k", + "downloads": { + "type": "npm_weekly", + "value": "74万", + "as_of": "2026-03-26" + }, + "pricing_summary": "免费层 + API 按量", + "free_tier": "1500 次/天", + "evidence": { + "status": "complete", + "source_type": "source-analysis", + "evidence_path": "docs/tools/gemini-cli/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "qwen-code", + "name": "Qwen Code", + "category": "deep-analysis", + "license": "Apache-2.0", + "developer": "阿里云", + "implementation_language": "TypeScript", + "runtime": "Node.js >= 20", + "package_ecosystem": "npm", + "stars": "21k", + "downloads": { + "type": "npm_weekly", + "value": "8.4万", + "as_of": "2026-03-26" + }, + "pricing_summary": "免费层 + DashScope 按量", + "free_tier": "1000 次/天", + "evidence": { + "status": "complete", + "source_type": "source-analysis", + "evidence_path": "docs/tools/qwen-code/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "aider", + "name": "Aider", + "category": "deep-analysis", + "license": "GPL-3.0", + "developer": "Paul Gauthier", + "implementation_language": "Python", + "runtime": "Python >= 3.10", + "package_ecosystem": "pypi", + "stars": "42k", + "downloads": { + "type": "pypi_monthly", + "value": "78万", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "无", + "evidence": { + "status": "complete", + "source_type": "source-analysis", + "evidence_path": "docs/tools/aider/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "kimi-cli", + "name": "Kimi CLI", + "category": "deep-analysis", + "license": "Apache-2.0", + "developer": "月之暗面", + "implementation_language": "Python", + "runtime": "Python >= 3.12", + "package_ecosystem": "pypi", + "stars": "7k", + "downloads": { + "type": "pypi_monthly", + "value": "45万", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "有限免费额度", + "evidence": { + "status": "complete", + "source_type": "source-analysis", + "evidence_path": "docs/tools/kimi-cli/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "opencode", + "name": "OpenCode", + "category": "deep-analysis", + "license": "MIT", + "developer": "Anomaly", + "implementation_language": "TypeScript(v1.0+)", + "runtime": "Bun 1.3+", + "package_ecosystem": "none", + "stars": "130k", + "downloads": { + "type": "none", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "无", + "evidence": { + "status": "complete", + "source_type": "source-analysis", + "evidence_path": "docs/tools/opencode/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "goose", + "name": "Goose", + "category": "deep-analysis", + "license": "Apache-2.0", + "developer": "Block / Linux Foundation", + "implementation_language": "Rust", + "runtime": "原生二进制 / Rust 构建", + "package_ecosystem": "none", + "stars": "34k", + "downloads": { + "type": "none", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "无", + "evidence": { + "status": "complete", + "source_type": "source-analysis", + "evidence_path": "docs/tools/goose/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "qoder-cli", + "name": "Qoder CLI", + "category": "deep-analysis", + "license": "专有", + "developer": "QoderAI", + "implementation_language": "Go", + "runtime": "原生静态链接二进制", + "package_ecosystem": "npm-wrapper", + "stars": "-", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "信用制", + "free_tier": "300 credits", + "evidence": { + "status": "partial", + "source_type": "binary-analysis", + "evidence_path": "docs/tools/qoder-cli/EVIDENCE.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "cline", + "name": "Cline", + "category": "single-file", + "license": "Apache-2.0", + "developer": "Cline", + "implementation_language": "TypeScript", + "runtime": "VS Code 扩展", + "package_ecosystem": "vscode-marketplace", + "stars": "59k", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "取决于模型提供商", + "free_tier": "取决于模型提供商", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/cline.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "continue", + "name": "Continue", + "category": "single-file", + "license": "Apache-2.0", + "developer": "Continue", + "implementation_language": "TypeScript", + "runtime": "IDE 扩展", + "package_ecosystem": "vscode-marketplace", + "stars": "32k", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "取决于模型提供商", + "free_tier": "取决于部署方式", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/continue.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "cursor-cli", + "name": "Cursor CLI", + "category": "single-file", + "license": "专有", + "developer": "Cursor", + "implementation_language": "TypeScript", + "runtime": "IDE / Desktop", + "package_ecosystem": "desktop", + "stars": "-", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "订阅制", + "free_tier": "有限", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/cursor-cli.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "openhands", + "name": "OpenHands", + "category": "single-file", + "license": "MIT", + "developer": "OpenHands", + "implementation_language": "Python", + "runtime": "Python / Docker", + "package_ecosystem": "pypi", + "stars": "70k", + "downloads": { + "type": "pypi_monthly", + "value": "125万", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "无", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/openhands.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "swe-agent", + "name": "SWE-agent", + "category": "single-file", + "license": "MIT", + "developer": "Princeton", + "implementation_language": "Python", + "runtime": "Python / Docker", + "package_ecosystem": "pypi", + "stars": "19k", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "API 按量", + "free_tier": "无", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/swe-agent.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "warp", + "name": "Warp", + "category": "single-file", + "license": "专有", + "developer": "Warp", + "implementation_language": "Rust", + "runtime": "桌面终端应用", + "package_ecosystem": "desktop", + "stars": "26k", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "订阅制", + "free_tier": "有限", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/warp.md", + "last_verified": "2026-03-26" + } + }, + { + "id": "mini-swe-agent", + "name": "mini-swe-agent", + "category": "single-file", + "license": "MIT", + "developer": "Princeton", + "implementation_language": "Python", + "runtime": "Python", + "package_ecosystem": "pypi", + "stars": "-", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "教学参考", + "free_tier": "—", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/mini-swe-agent.md", + "last_verified": "2026-03-26" + } + } + ] +} diff --git a/docs/data/agents-metadata.schema.json b/docs/data/agents-metadata.schema.json new file mode 100644 index 0000000..29a62dc --- /dev/null +++ b/docs/data/agents-metadata.schema.json @@ -0,0 +1,166 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/codeagents-x1/docs/data/agents-metadata.schema.json", + "title": "agents-metadata", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "last_updated", + "agents" + ], + "properties": { + "schema_version": { + "type": "integer", + "minimum": 1 + }, + "last_updated": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "maintainer_note": { + "type": "string" + }, + "agents": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/agent" + } + } + }, + "$defs": { + "downloads": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "value", + "as_of" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "npm_weekly", + "pypi_monthly", + "none", + "unknown" + ] + }, + "value": { + "type": "string", + "minLength": 1 + }, + "as_of": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + } + } + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "source_type", + "evidence_path", + "last_verified" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "complete", + "partial", + "single-file-only" + ] + }, + "source_type": { + "type": "string", + "enum": [ + "source-analysis", + "binary-analysis", + "summary-analysis", + "official-docs" + ] + }, + "evidence_path": { + "type": "string", + "minLength": 1 + }, + "last_verified": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + } + } + }, + "agent": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "category", + "license", + "developer", + "implementation_language", + "runtime", + "package_ecosystem", + "evidence" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "category": { + "type": "string", + "enum": [ + "deep-analysis", + "single-file" + ] + }, + "license": { + "type": "string", + "minLength": 1 + }, + "developer": { + "type": "string", + "minLength": 1 + }, + "implementation_language": { + "type": "string", + "minLength": 1 + }, + "runtime": { + "type": "string", + "minLength": 1 + }, + "package_ecosystem": { + "type": "string", + "minLength": 1 + }, + "stars": { + "type": "string" + }, + "downloads": { + "$ref": "#/$defs/downloads" + }, + "pricing_summary": { + "type": "string" + }, + "free_tier": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/evidence" + } + } + } + } +} diff --git a/docs/evidence-index.md b/docs/evidence-index.md new file mode 100644 index 0000000..d6e8143 --- /dev/null +++ b/docs/evidence-index.md @@ -0,0 +1,83 @@ +# 证据索引(Evidence Index) + +> 本页用于汇总每个 Agent 的证据完备度、分析方式、对应文档入口,以及建议的更新频率。 +> +> **目标**:把“这条结论来自哪里、验证到什么程度、何时需要复核”显式化,降低仓库维护成本。 + +## 证据状态定义 + +| 状态 | 含义 | 维护要求 | +|------|------|---------| +| `complete` | 已有目录级分析,且存在 `EVIDENCE.md` 或等价证据文档 | 核心结论需可回溯到源码、反编译结果或官方文档 | +| `partial` | 有多文件分析,但关键结论仍部分依赖外部材料、二进制侧信号或待补证据 | 应补足缺失证据,避免长期停留 | +| `single-file-only` | 当前仅有单文件综述,缺少目录级深挖与证据索引 | 若该 Agent 重要性提升,应优先升级为目录级分析 | + +## 证据来源类型 + +| 类型 | 说明 | +|------|------| +| `source-analysis` | 直接基于开源仓库源码分析 | +| `binary-analysis` | 基于二进制、反编译、strings、CLI 帮助输出等证据 | +| `official-docs` | 主要基于官方文档、定价页、产品文档 | +| `summary-analysis` | 综述级整理,证据粒度较粗 | + +## Agent 证据矩阵 + +| Agent | 分析深度 | 证据状态 | 证据来源 | 证据入口 | 最后验证 | 建议复核频率 | +|------|---------|---------|---------|---------|---------|-------------| +| Claude Code | 多文件 | `complete` | `binary-analysis` | `docs/tools/claude-code/EVIDENCE.md` | 2026-03-26 | 月度 | +| Copilot CLI | 多文件 | `complete` | `binary-analysis` | `docs/tools/copilot-cli/EVIDENCE.md` | 2026-03-26 | 月度 | +| Codex CLI | 多文件 | `complete` | `binary-analysis` | `docs/tools/codex-cli/EVIDENCE.md` | 2026-03-26 | 月度 | +| Gemini CLI | 多文件 | `complete` | `source-analysis` | `docs/tools/gemini-cli/EVIDENCE.md` | 2026-03-26 | 月度 | +| Qwen Code | 多文件 | `complete` | `source-analysis` | `docs/tools/qwen-code/EVIDENCE.md` | 2026-03-26 | 月度 | +| Aider | 多文件 | `complete` | `source-analysis` | `docs/tools/aider/EVIDENCE.md` | 2026-03-26 | 月度 | +| Kimi CLI | 多文件 | `complete` | `source-analysis` | `docs/tools/kimi-cli/EVIDENCE.md` | 2026-03-26 | 月度 | +| OpenCode | 多文件 | `complete` | `source-analysis` | `docs/tools/opencode/EVIDENCE.md` | 2026-03-26 | 月度 | +| Goose | 多文件 | `complete` | `source-analysis` | `docs/tools/goose/EVIDENCE.md` | 2026-03-26 | 月度 | +| Qoder CLI | 多文件 | `partial` | `binary-analysis` | `docs/tools/qoder-cli/EVIDENCE.md` | 2026-03-26 | 双周 | +| Cline | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/cline.md` | 2026-03-26 | 季度 | +| Continue | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/continue.md` | 2026-03-26 | 季度 | +| Cursor CLI | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/cursor-cli.md` | 2026-03-26 | 季度 | +| OpenHands | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/openhands.md` | 2026-03-26 | 季度 | +| SWE-agent | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/swe-agent.md` | 2026-03-26 | 季度 | +| Warp | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/warp.md` | 2026-03-26 | 季度 | +| mini-swe-agent | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/mini-swe-agent.md` | 2026-03-26 | 低优先级 | + +## 优先补强建议 + +### 第一优先级 + +1. **Qoder CLI** + - 已有目录级分析,但证据完备度仍弱于主要开源 Agent + - 建议补更多 CLI 参数、配置格式、遥测/安全、命令行为证据 + +2. **Continue / Cline / OpenHands** + - 影响力较大,但目前仍以单文件综述为主 + - 建议至少升级为“概述 + 架构 + 命令/工具 + EVIDENCE.md”四件套 + +### 第二优先级 + +3. **Cursor CLI / Warp** + - 用户关注度高,但闭源成分多 + - 建议补官方文档证据与行为验证边界,明确哪些结论来自源码、哪些来自官方表述 + +## 维护工作流建议 + +每次新增或更新 Agent 文档时,建议同步完成以下动作: + +1. 更新 `docs/data/agents-metadata.json` +2. 更新本页中的证据状态、最后验证日期、复核频率 +3. 如涉及关键结论变动,同步检查: + - `README.md` + - `docs/SUMMARY.md` + - `docs/tools/README.md` + - `docs/comparison/features.md` + - `docs/comparison/privacy-telemetry.md` + - `docs/comparison/pricing.md` + - `docs/comparison/system-requirements.md` + +## 读者说明 + +> `single-file-only` 不代表内容错误,只表示证据粒度和可审计性弱于目录级分析。 +> +> 对闭源 Agent,应尽量明确“可验证边界”:哪些是通过行为、CLI 帮助、二进制分析得出,哪些仅来自官方文档。 diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index d5c1e43..bb07dab 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -349,4 +349,4 @@ chmod +x script.sh - [工具文档](../tools/) - 详细工具指南 - [功能对比](../comparison/features.md) - 横向对比 - [架构解析](../architecture/overview.md) - 代理如何工作 -- [基准测试](../benchmarks/) - 性能数据 +- [基准测试](../benchmarks/overview.md) - 性能数据 diff --git a/docs/tools/README.md b/docs/tools/README.md index 69952f1..9b881e0 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -47,9 +47,9 @@ ## 新增 Agent -| Agent | 文件 | 行数 | 特色 | +| Agent | 形态 | 深度 | 特色 | |------|------|------|------| -| [Qoder CLI](./qoder-cli/) | 单文件 | 179 | Go 原生,Quest 模式,Claude Code 兼容,信用制 | +| [Qoder CLI](./qoder-cli/) | 目录 | 多文件 | Go 原生,Quest 模式,Claude Code 兼容,信用制 | ## 增强系统 diff --git a/scripts/check_all.py b/scripts/check_all.py new file mode 100644 index 0000000..a15c274 --- /dev/null +++ b/scripts/check_all.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import subprocess +import sys +from pathlib import Path + +ROOT = Path('/root/git/codeagents-x1') +SCRIPTS = ROOT / 'scripts' +CHECKS = [ + ('data schema', SCRIPTS / 'check_data_schema.py'), + ('repository consistency', SCRIPTS / 'check_repo_consistency.py'), + ('stale data', SCRIPTS / 'check_stale_data.py'), +] + + +def run_check(label: str, script: Path) -> int: + print(f'==> Running {label}: {script.name}') + result = subprocess.run([sys.executable, str(script)], cwd=ROOT) + print(f'==> Exit code: {result.returncode}\n') + return result.returncode + + +def main() -> int: + exit_codes = [run_check(label, script) for label, script in CHECKS] + if any(code != 0 for code in exit_codes): + print('FAIL: one or more checks returned non-zero exit codes') + return 1 + print('OK: all repository checks passed') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/check_data_schema.py b/scripts/check_data_schema.py new file mode 100644 index 0000000..fa068c3 --- /dev/null +++ b/scripts/check_data_schema.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +import json +import re +import sys +from datetime import datetime +from pathlib import Path + +ROOT = Path('/root/git/codeagents-x1') +DATA_FILE = ROOT / 'docs' / 'data' / 'agents-metadata.json' +SCHEMA_FILE = ROOT / 'docs' / 'data' / 'agents-metadata.schema.json' +DATE_RE = re.compile(r'^\d{4}-\d{2}-\d{2}$') +ID_RE = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$') +CATEGORY_VALUES = {'deep-analysis', 'single-file'} +DOWNLOAD_TYPES = {'npm_weekly', 'pypi_monthly', 'none', 'unknown'} +EVIDENCE_STATUS = {'complete', 'partial', 'single-file-only'} +EVIDENCE_SOURCE_TYPES = {'source-analysis', 'binary-analysis', 'summary-analysis', 'official-docs'} +TOP_LEVEL_REQUIRED = {'schema_version', 'last_updated', 'agents'} +TOP_LEVEL_OPTIONAL = {'maintainer_note'} +AGENT_REQUIRED = { + 'id', 'name', 'category', 'license', 'developer', + 'implementation_language', 'runtime', 'package_ecosystem', 'evidence' +} +AGENT_OPTIONAL = {'stars', 'downloads', 'pricing_summary', 'free_tier'} +DOWNLOADS_REQUIRED = {'type', 'value', 'as_of'} +EVIDENCE_REQUIRED = {'status', 'source_type', 'evidence_path', 'last_verified'} + + +def load_json(path: Path): + with path.open('r', encoding='utf-8') as f: + return json.load(f) + + +def is_valid_date(value: str) -> bool: + if not isinstance(value, str) or not DATE_RE.match(value): + return False + try: + datetime.strptime(value, '%Y-%m-%d') + return True + except ValueError: + return False + + +def require_keys(obj: dict, required: set[str], optional: set[str], label: str, errors: list[str]): + keys = set(obj.keys()) + missing = required - keys + extra = keys - required - optional + for key in sorted(missing): + errors.append(f'{label}: missing required key `{key}`') + for key in sorted(extra): + errors.append(f'{label}: unexpected key `{key}`') + + +def validate_agent(agent: dict, index: int, errors: list[str], seen_ids: set[str]): + label = f'agents[{index}]' + if not isinstance(agent, dict): + errors.append(f'{label}: must be an object') + return + + require_keys(agent, AGENT_REQUIRED, AGENT_OPTIONAL, label, errors) + + agent_id = agent.get('id') + if not isinstance(agent_id, str) or not ID_RE.match(agent_id): + errors.append(f'{label}.id: must be kebab-case string') + elif agent_id in seen_ids: + errors.append(f'{label}.id: duplicate id `{agent_id}`') + else: + seen_ids.add(agent_id) + + for field in ['name', 'license', 'developer', 'implementation_language', 'runtime', 'package_ecosystem']: + value = agent.get(field) + if not isinstance(value, str) or not value.strip(): + errors.append(f'{label}.{field}: must be a non-empty string') + + category = agent.get('category') + if category not in CATEGORY_VALUES: + errors.append(f'{label}.category: must be one of {sorted(CATEGORY_VALUES)}') + + if 'stars' in agent and not isinstance(agent.get('stars'), str): + errors.append(f'{label}.stars: must be a string when present') + if 'pricing_summary' in agent and not isinstance(agent.get('pricing_summary'), str): + errors.append(f'{label}.pricing_summary: must be a string when present') + if 'free_tier' in agent and not isinstance(agent.get('free_tier'), str): + errors.append(f'{label}.free_tier: must be a string when present') + + downloads = agent.get('downloads') + if downloads is not None: + if not isinstance(downloads, dict): + errors.append(f'{label}.downloads: must be an object') + else: + require_keys(downloads, DOWNLOADS_REQUIRED, set(), f'{label}.downloads', errors) + if downloads.get('type') not in DOWNLOAD_TYPES: + errors.append(f'{label}.downloads.type: must be one of {sorted(DOWNLOAD_TYPES)}') + if not isinstance(downloads.get('value'), str) or not downloads.get('value').strip(): + errors.append(f'{label}.downloads.value: must be a non-empty string') + if not is_valid_date(downloads.get('as_of')): + errors.append(f'{label}.downloads.as_of: must be a valid YYYY-MM-DD date') + + evidence = agent.get('evidence') + if not isinstance(evidence, dict): + errors.append(f'{label}.evidence: must be an object') + else: + require_keys(evidence, EVIDENCE_REQUIRED, set(), f'{label}.evidence', errors) + if evidence.get('status') not in EVIDENCE_STATUS: + errors.append(f'{label}.evidence.status: must be one of {sorted(EVIDENCE_STATUS)}') + if evidence.get('source_type') not in EVIDENCE_SOURCE_TYPES: + errors.append(f'{label}.evidence.source_type: must be one of {sorted(EVIDENCE_SOURCE_TYPES)}') + if not isinstance(evidence.get('evidence_path'), str) or not evidence.get('evidence_path').strip(): + errors.append(f'{label}.evidence.evidence_path: must be a non-empty string') + if not is_valid_date(evidence.get('last_verified')): + errors.append(f'{label}.evidence.last_verified: must be a valid YYYY-MM-DD date') + + +def main() -> int: + errors: list[str] = [] + + if not DATA_FILE.exists(): + print(f'ERROR: missing data file {DATA_FILE.relative_to(ROOT)}') + return 1 + if not SCHEMA_FILE.exists(): + print(f'ERROR: missing schema file {SCHEMA_FILE.relative_to(ROOT)}') + return 1 + + try: + data = load_json(DATA_FILE) + load_json(SCHEMA_FILE) + except json.JSONDecodeError as exc: + print(f'ERROR: invalid JSON: {exc}') + return 1 + + if not isinstance(data, dict): + print('ERROR: top-level JSON must be an object') + return 1 + + require_keys(data, TOP_LEVEL_REQUIRED, TOP_LEVEL_OPTIONAL, 'root', errors) + + if not isinstance(data.get('schema_version'), int) or data.get('schema_version', 0) < 1: + errors.append('root.schema_version: must be an integer >= 1') + if not is_valid_date(data.get('last_updated')): + errors.append('root.last_updated: must be a valid YYYY-MM-DD date') + if 'maintainer_note' in data and not isinstance(data.get('maintainer_note'), str): + errors.append('root.maintainer_note: must be a string when present') + + agents = data.get('agents') + if not isinstance(agents, list) or not agents: + errors.append('root.agents: must be a non-empty array') + else: + seen_ids: set[str] = set() + for idx, agent in enumerate(agents): + validate_agent(agent, idx, errors, seen_ids) + + if errors: + for err in errors: + print(f'ERROR: {err}') + return 1 + + print('OK: data schema checks passed') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/check_repo_consistency.py b/scripts/check_repo_consistency.py new file mode 100644 index 0000000..73e25c3 --- /dev/null +++ b/scripts/check_repo_consistency.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +import json +import re +import sys +from pathlib import Path + +ROOT = Path('/root/git/codeagents-x1') +DOCS = ROOT / 'docs' +DATA_FILE = DOCS / 'data' / 'agents-metadata.json' +README = ROOT / 'README.md' +SUMMARY = DOCS / 'SUMMARY.md' +TOOLS_INDEX = DOCS / 'tools' / 'README.md' +EVIDENCE_INDEX = DOCS / 'evidence-index.md' + +CHECK_FILES = [ + README, + SUMMARY, + TOOLS_INDEX, + DOCS / 'comparison' / 'features.md', + DOCS / 'comparison' / 'pricing.md', + DOCS / 'comparison' / 'privacy-telemetry.md', + DOCS / 'comparison' / 'system-requirements.md', +] + + +def read_text(path: Path) -> str: + return path.read_text(encoding='utf-8') + + +def load_data() -> dict: + return json.loads(read_text(DATA_FILE)) + + +def check_required_files(errors: list[str]): + for path in [DATA_FILE, README, SUMMARY, TOOLS_INDEX, EVIDENCE_INDEX]: + if not path.exists(): + errors.append(f'Missing required file: {path.relative_to(ROOT)}') + + +def check_evidence_paths(metadata: dict, errors: list[str]): + for agent in metadata.get('agents', []): + evidence_path = agent.get('evidence', {}).get('evidence_path') + if evidence_path: + full = ROOT / evidence_path + if not full.exists(): + errors.append(f"Missing evidence path for {agent['id']}: {evidence_path}") + + +def check_agent_mentions(metadata: dict, warnings: list[str]): + files_text = {path: read_text(path) for path in CHECK_FILES if path.exists()} + for agent in metadata.get('agents', []): + name = agent['name'] + mentions = [path.relative_to(ROOT).as_posix() for path, text in files_text.items() if name in text] + if not mentions: + warnings.append(f"Agent not mentioned in tracked summary files: {name}") + + +def check_links(errors: list[str], warnings: list[str]): + link_pattern = re.compile(r'\[[^\]]+\]\(([^)]+)\)') + ignored_literal_targets = {'链接', 'link', 'url', 'example', '.*'} + for md in ROOT.rglob('*.md'): + if '.git' in md.parts: + continue + text = read_text(md) + in_code_block = False + for line in text.splitlines(): + if line.strip().startswith('```'): + in_code_block = not in_code_block + continue + if in_code_block: + continue + for target in link_pattern.findall(line): + if target.startswith(('http://', 'https://', '#', 'mailto:')): + continue + normalized = target.split('#', 1)[0].strip() + if not normalized or normalized in ignored_literal_targets: + continue + candidate = (md.parent / normalized).resolve() + if not candidate.exists(): + errors.append(f'Broken relative link in {md.relative_to(ROOT)} -> {target}') + elif candidate.is_dir(): + index_md = candidate / 'README.md' + if not index_md.exists(): + warnings.append(f'Directory link without README target: {md.relative_to(ROOT)} -> {target}') + + +def check_tools_index_consistency(warnings: list[str]): + text = read_text(TOOLS_INDEX) + qoder_lines = [line for line in text.splitlines() if '[Qoder CLI](./qoder-cli/)' in line] + if any('单文件' in line for line in qoder_lines): + warnings.append('docs/tools/README.md lists Qoder CLI as a directory link but labels it 单文件; consider clarifying depth/type.') + + +def main() -> int: + errors: list[str] = [] + warnings: list[str] = [] + + check_required_files(errors) + if errors: + for item in errors: + print(f'ERROR: {item}') + return 1 + + metadata = load_data() + check_evidence_paths(metadata, errors) + check_agent_mentions(metadata, warnings) + check_links(errors, warnings) + check_tools_index_consistency(warnings) + + if errors: + for item in errors: + print(f'ERROR: {item}') + if warnings: + for item in warnings: + print(f'WARN: {item}') + + if errors: + return 1 + + print('OK: repository consistency checks passed') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/check_stale_data.py b/scripts/check_stale_data.py new file mode 100644 index 0000000..064d92d --- /dev/null +++ b/scripts/check_stale_data.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +import json +import re +import sys +from collections import defaultdict +from datetime import date, datetime +from pathlib import Path + +ROOT = Path('/root/git/codeagents-x1') +DOCS = ROOT / 'docs' +DATA_FILE = DOCS / 'data' / 'agents-metadata.json' + +CHECK_FILES = [ + ROOT / 'README.md', + DOCS / 'SUMMARY.md', + DOCS / 'comparison' / 'features.md', + DOCS / 'comparison' / 'pricing.md', + DOCS / 'comparison' / 'privacy-telemetry.md', + DOCS / 'comparison' / 'system-requirements.md', + DOCS / 'evidence-index.md', +] + +DATE_PATTERN = re.compile(r'20\d{2}-\d{2}-\d{2}') +STALE_DAYS_WARNING = 45 +DYNAMIC_TERMS = ('Stars', 'stars', '下载', 'downloads', '验证', '测量') + + +def read_text(path: Path) -> str: + return path.read_text(encoding='utf-8') + + +def load_data() -> dict: + return json.loads(read_text(DATA_FILE)) + + +def parse_date(value: str) -> date: + return datetime.strptime(value, '%Y-%m-%d').date() + + +def should_scan_line(line: str) -> bool: + stripped = line.strip() + if not any(term in stripped for term in DYNAMIC_TERMS): + return False + if stripped.startswith('>'): + return False + if stripped.startswith('#'): + return False + return '|' in stripped or stripped.startswith('- ') + + +def collect_metadata_tokens(metadata: dict) -> dict[str, dict[str, set[str]]]: + tokens: dict[str, dict[str, set[str]]] = {} + for agent in metadata.get('agents', []): + bucket: dict[str, set[str]] = defaultdict(set) + name = agent['name'] + bucket['stars'].add(str(agent.get('stars', '')).strip()) + downloads = agent.get('downloads', {}) + bucket['downloads'].add(str(downloads.get('value', '')).strip()) + bucket['dates'].add(str(downloads.get('as_of', '')).strip()) + evidence = agent.get('evidence', {}) + bucket['dates'].add(str(evidence.get('last_verified', '')).strip()) + bucket['pricing'].add(str(agent.get('pricing_summary', '')).strip()) + bucket['free_tier'].add(str(agent.get('free_tier', '')).strip()) + tokens[name] = bucket + return tokens + + +def check_metadata_freshness(metadata: dict, warnings: list[str]): + today = date.today() + all_dates = [] + root_last_updated = metadata.get('last_updated') + if root_last_updated: + all_dates.append(('docs/data/agents-metadata.json:last_updated', root_last_updated)) + for agent in metadata.get('agents', []): + downloads_as_of = agent.get('downloads', {}).get('as_of') + evidence_verified = agent.get('evidence', {}).get('last_verified') + if downloads_as_of: + all_dates.append((f"{agent['id']}:downloads.as_of", downloads_as_of)) + if evidence_verified: + all_dates.append((f"{agent['id']}:evidence.last_verified", evidence_verified)) + for label, value in all_dates: + try: + days = (today - parse_date(value)).days + except ValueError: + warnings.append(f'无法解析日期: {label} -> {value}') + continue + if days > STALE_DAYS_WARNING: + warnings.append(f'数据可能过期: {label} 距今 {days} 天(阈值 {STALE_DAYS_WARNING} 天)') + + +def check_tracked_files_for_token_drift(metadata_tokens: dict[str, dict[str, set[str]]], warnings: list[str]): + for path in CHECK_FILES: + if not path.exists(): + continue + text = read_text(path) + for line_no, line in enumerate(text.splitlines(), start=1): + if not should_scan_line(line): + continue + matched_agents = [name for name in metadata_tokens if name in line] + for agent_name in matched_agents: + token_groups = metadata_tokens[agent_name] + if 'Stars' in line or 'stars' in line or 'Stars' in line: + if token_groups['stars'] and not any(token in line for token in token_groups['stars'] if token): + warnings.append( + f'{path.relative_to(ROOT)}:{line_no} 中 {agent_name} 的 Stars 可能未与 docs/data 同步: {line.strip()}' + ) + if '下载' in line or 'downloads' in line: + if token_groups['downloads'] and not any(token in line for token in token_groups['downloads'] if token and token != '—'): + warnings.append( + f'{path.relative_to(ROOT)}:{line_no} 中 {agent_name} 的下载量可能未与 docs/data 同步: {line.strip()}' + ) + + +def check_date_mentions_against_metadata(metadata: dict, warnings: list[str]): + known_dates = {metadata.get('last_updated', '')} + for agent in metadata.get('agents', []): + known_dates.add(agent.get('downloads', {}).get('as_of', '')) + known_dates.add(agent.get('evidence', {}).get('last_verified', '')) + known_dates = {value for value in known_dates if value} + + for path in CHECK_FILES: + if not path.exists(): + continue + text = read_text(path) + for line_no, line in enumerate(text.splitlines(), start=1): + if not should_scan_line(line): + continue + for found in DATE_PATTERN.findall(line): + if found not in known_dates: + warnings.append( + f'{path.relative_to(ROOT)}:{line_no} 出现未在 docs/data 注册的日期 {found}: {line.strip()}' + ) + + +def main() -> int: + warnings: list[str] = [] + if not DATA_FILE.exists(): + print('ERROR: missing docs/data/agents-metadata.json') + return 1 + + metadata = load_data() + metadata_tokens = collect_metadata_tokens(metadata) + check_metadata_freshness(metadata, warnings) + check_tracked_files_for_token_drift(metadata_tokens, warnings) + check_date_mentions_against_metadata(metadata, warnings) + + if warnings: + for item in warnings: + print(f'WARN: {item}') + return 0 + + print('OK: stale data checks passed') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From 55ce1d8b31d729256262d5cb091668411c5dc7d4 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 30 Mar 2026 07:56:10 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E8=84=9A=E6=9C=AC=E7=9A=84=E5=8F=AF=E7=A7=BB?= =?UTF-8?q?=E6=A4=8D=E6=80=A7=E4=B8=8E=E4=B8=80=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Qwen-Coder --- CONTRIBUTING.md | 1 + docs/data/README.md | 7 +- docs/data/SCHEMA.md | 2 + scripts/check_all.py | 2 +- scripts/check_data_schema.py | 107 +++++++++++++++++++++++++----- scripts/check_repo_consistency.py | 35 +++++++++- scripts/check_stale_data.py | 2 +- 7 files changed, 131 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1007967..ce56c0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,7 @@ - 如果涉及证据完备度变化,同步更新 `docs/evidence-index.md` - 提交前优先运行统一检查:`python3 scripts/check_all.py` - 如需单独执行,可运行:`python3 scripts/check_data_schema.py`、`python3 scripts/check_repo_consistency.py` 和 `python3 scripts/check_stale_data.py` +- `check_stale_data.py` 目前为告警型检查,主要用于提示潜在漂移 ### 3. 添加示例 diff --git a/docs/data/README.md b/docs/data/README.md index d9ca9e0..5f83e2d 100644 --- a/docs/data/README.md +++ b/docs/data/README.md @@ -40,7 +40,7 @@ } ``` -### 3. 汇总页尽量引用数据源,而不是手工散落维护 +### 4. 汇总页尽量引用数据源,而不是手工散落维护 以下页面应优先从本目录读取或至少手动同步校验: @@ -52,7 +52,7 @@ - `docs/comparison/system-requirements.md` - `docs/tools/README.md` -### 4. 证据状态标准 +### 5. 证据状态标准 - `complete`:有完整多文件分析,且存在 `EVIDENCE.md` - `partial`:有目录级分析,但证据仍不完整或主要来自二进制/外部线索 @@ -72,6 +72,9 @@ python3 scripts/check_all.py 如需单独执行,也可以使用: ```bash +python3 scripts/check_data_schema.py python3 scripts/check_repo_consistency.py python3 scripts/check_stale_data.py ``` + +> **说明**:`check_stale_data.py` 当前为“告警型检查”,用于提示可能的漂移或过期信息;是否阻断提交应由维护者结合上下文判断。 diff --git a/docs/data/SCHEMA.md b/docs/data/SCHEMA.md index af00799..8b69dc7 100644 --- a/docs/data/SCHEMA.md +++ b/docs/data/SCHEMA.md @@ -153,3 +153,5 @@ python3 scripts/check_data_schema.py python3 scripts/check_repo_consistency.py python3 scripts/check_stale_data.py ``` + +> `check_stale_data.py` 目前为告警型检查,主要用于提示潜在漂移,不默认作为阻断条件。 diff --git a/scripts/check_all.py b/scripts/check_all.py index a15c274..079dd1e 100644 --- a/scripts/check_all.py +++ b/scripts/check_all.py @@ -3,7 +3,7 @@ import sys from pathlib import Path -ROOT = Path('/root/git/codeagents-x1') +ROOT = Path(__file__).resolve().parents[1] SCRIPTS = ROOT / 'scripts' CHECKS = [ ('data schema', SCRIPTS / 'check_data_schema.py'), diff --git a/scripts/check_data_schema.py b/scripts/check_data_schema.py index fa068c3..62e6bc8 100644 --- a/scripts/check_data_schema.py +++ b/scripts/check_data_schema.py @@ -5,15 +5,11 @@ from datetime import datetime from pathlib import Path -ROOT = Path('/root/git/codeagents-x1') +ROOT = Path(__file__).resolve().parents[1] DATA_FILE = ROOT / 'docs' / 'data' / 'agents-metadata.json' SCHEMA_FILE = ROOT / 'docs' / 'data' / 'agents-metadata.schema.json' DATE_RE = re.compile(r'^\d{4}-\d{2}-\d{2}$') ID_RE = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$') -CATEGORY_VALUES = {'deep-analysis', 'single-file'} -DOWNLOAD_TYPES = {'npm_weekly', 'pypi_monthly', 'none', 'unknown'} -EVIDENCE_STATUS = {'complete', 'partial', 'single-file-only'} -EVIDENCE_SOURCE_TYPES = {'source-analysis', 'binary-analysis', 'summary-analysis', 'official-docs'} TOP_LEVEL_REQUIRED = {'schema_version', 'last_updated', 'agents'} TOP_LEVEL_OPTIONAL = {'maintainer_note'} AGENT_REQUIRED = { @@ -30,6 +26,40 @@ def load_json(path: Path): return json.load(f) +def is_schema_shape_expected(schema: dict) -> bool: + if not isinstance(schema, dict): + return False + required_keys = {'$schema', 'type', 'properties', '$defs'} + if not required_keys.issubset(schema.keys()): + return False + if schema.get('type') != 'object': + return False + properties = schema.get('properties', {}) + defs = schema.get('$defs', {}) + return ( + isinstance(properties, dict) + and isinstance(defs, dict) + and 'agents' in properties + and 'agent' in defs + and 'downloads' in defs + and 'evidence' in defs + ) + + +def get_required_keys(schema: dict, path: tuple[str, ...]) -> set[str]: + node = schema + for key in path: + node = node[key] + return set(node.get('required', [])) + + +def get_enum_values(schema: dict, path: tuple[str, ...]) -> set[str]: + node = schema + for key in path: + node = node[key] + return set(node.get('enum', [])) + + def is_valid_date(value: str) -> bool: if not isinstance(value, str) or not DATE_RE.match(value): return False @@ -50,7 +80,16 @@ def require_keys(obj: dict, required: set[str], optional: set[str], label: str, errors.append(f'{label}: unexpected key `{key}`') -def validate_agent(agent: dict, index: int, errors: list[str], seen_ids: set[str]): +def validate_agent( + agent: dict, + index: int, + errors: list[str], + seen_ids: set[str], + category_values: set[str], + download_types: set[str], + evidence_status_values: set[str], + evidence_source_types: set[str], +): label = f'agents[{index}]' if not isinstance(agent, dict): errors.append(f'{label}: must be an object') @@ -72,8 +111,8 @@ def validate_agent(agent: dict, index: int, errors: list[str], seen_ids: set[str errors.append(f'{label}.{field}: must be a non-empty string') category = agent.get('category') - if category not in CATEGORY_VALUES: - errors.append(f'{label}.category: must be one of {sorted(CATEGORY_VALUES)}') + if category not in category_values: + errors.append(f'{label}.category: must be one of {sorted(category_values)}') if 'stars' in agent and not isinstance(agent.get('stars'), str): errors.append(f'{label}.stars: must be a string when present') @@ -88,8 +127,8 @@ def validate_agent(agent: dict, index: int, errors: list[str], seen_ids: set[str errors.append(f'{label}.downloads: must be an object') else: require_keys(downloads, DOWNLOADS_REQUIRED, set(), f'{label}.downloads', errors) - if downloads.get('type') not in DOWNLOAD_TYPES: - errors.append(f'{label}.downloads.type: must be one of {sorted(DOWNLOAD_TYPES)}') + if downloads.get('type') not in download_types: + errors.append(f'{label}.downloads.type: must be one of {sorted(download_types)}') if not isinstance(downloads.get('value'), str) or not downloads.get('value').strip(): errors.append(f'{label}.downloads.value: must be a non-empty string') if not is_valid_date(downloads.get('as_of')): @@ -100,12 +139,17 @@ def validate_agent(agent: dict, index: int, errors: list[str], seen_ids: set[str errors.append(f'{label}.evidence: must be an object') else: require_keys(evidence, EVIDENCE_REQUIRED, set(), f'{label}.evidence', errors) - if evidence.get('status') not in EVIDENCE_STATUS: - errors.append(f'{label}.evidence.status: must be one of {sorted(EVIDENCE_STATUS)}') - if evidence.get('source_type') not in EVIDENCE_SOURCE_TYPES: - errors.append(f'{label}.evidence.source_type: must be one of {sorted(EVIDENCE_SOURCE_TYPES)}') - if not isinstance(evidence.get('evidence_path'), str) or not evidence.get('evidence_path').strip(): + if evidence.get('status') not in evidence_status_values: + errors.append(f'{label}.evidence.status: must be one of {sorted(evidence_status_values)}') + if evidence.get('source_type') not in evidence_source_types: + errors.append(f'{label}.evidence.source_type: must be one of {sorted(evidence_source_types)}') + evidence_path = evidence.get('evidence_path') + if not isinstance(evidence_path, str) or not evidence_path.strip(): errors.append(f'{label}.evidence.evidence_path: must be a non-empty string') + else: + full_path = ROOT / evidence_path + if not full_path.exists(): + errors.append(f'{label}.evidence.evidence_path: file does not exist: {evidence_path}') if not is_valid_date(evidence.get('last_verified')): errors.append(f'{label}.evidence.last_verified: must be a valid YYYY-MM-DD date') @@ -122,16 +166,30 @@ def main() -> int: try: data = load_json(DATA_FILE) - load_json(SCHEMA_FILE) + schema = load_json(SCHEMA_FILE) except json.JSONDecodeError as exc: print(f'ERROR: invalid JSON: {exc}') return 1 + if not is_schema_shape_expected(schema): + print('ERROR: schema file exists but does not match the expected validation structure') + return 1 + if not isinstance(data, dict): print('ERROR: top-level JSON must be an object') return 1 - require_keys(data, TOP_LEVEL_REQUIRED, TOP_LEVEL_OPTIONAL, 'root', errors) + category_values = get_enum_values(schema, ('$defs', 'agent', 'properties', 'category')) + download_types = get_enum_values(schema, ('$defs', 'downloads', 'properties', 'type')) + evidence_status_values = get_enum_values(schema, ('$defs', 'evidence', 'properties', 'status')) + evidence_source_types = get_enum_values(schema, ('$defs', 'evidence', 'properties', 'source_type')) + + top_level_required = get_required_keys(schema, tuple()) + agent_required = get_required_keys(schema, ('$defs', 'agent')) + downloads_required = get_required_keys(schema, ('$defs', 'downloads')) + evidence_required = get_required_keys(schema, ('$defs', 'evidence')) + + require_keys(data, top_level_required, TOP_LEVEL_OPTIONAL, 'root', errors) if not isinstance(data.get('schema_version'), int) or data.get('schema_version', 0) < 1: errors.append('root.schema_version: must be an integer >= 1') @@ -144,9 +202,22 @@ def main() -> int: if not isinstance(agents, list) or not agents: errors.append('root.agents: must be a non-empty array') else: + global AGENT_REQUIRED, DOWNLOADS_REQUIRED, EVIDENCE_REQUIRED + AGENT_REQUIRED = agent_required + DOWNLOADS_REQUIRED = downloads_required + EVIDENCE_REQUIRED = evidence_required seen_ids: set[str] = set() for idx, agent in enumerate(agents): - validate_agent(agent, idx, errors, seen_ids) + validate_agent( + agent, + idx, + errors, + seen_ids, + category_values, + download_types, + evidence_status_values, + evidence_source_types, + ) if errors: for err in errors: diff --git a/scripts/check_repo_consistency.py b/scripts/check_repo_consistency.py index 73e25c3..39819e5 100644 --- a/scripts/check_repo_consistency.py +++ b/scripts/check_repo_consistency.py @@ -4,13 +4,14 @@ import sys from pathlib import Path -ROOT = Path('/root/git/codeagents-x1') +ROOT = Path(__file__).resolve().parents[1] DOCS = ROOT / 'docs' DATA_FILE = DOCS / 'data' / 'agents-metadata.json' README = ROOT / 'README.md' SUMMARY = DOCS / 'SUMMARY.md' TOOLS_INDEX = DOCS / 'tools' / 'README.md' EVIDENCE_INDEX = DOCS / 'evidence-index.md' +EVIDENCE_PATTERN = re.compile(r'^\|\s*([^|]+?)\s*\|\s*[^|]*\|\s*`([^`]+)`\s*\|\s*([^|]+?)\s*\|', re.MULTILINE) CHECK_FILES = [ README, @@ -38,12 +39,13 @@ def check_required_files(errors: list[str]): def check_evidence_paths(metadata: dict, errors: list[str]): - for agent in metadata.get('agents', []): + for index, agent in enumerate(metadata.get('agents', [])): + agent_label = agent.get('id', f'index-{index}') evidence_path = agent.get('evidence', {}).get('evidence_path') if evidence_path: full = ROOT / evidence_path if not full.exists(): - errors.append(f"Missing evidence path for {agent['id']}: {evidence_path}") + errors.append(f"Missing evidence path for {agent_label}: {evidence_path}") def check_agent_mentions(metadata: dict, warnings: list[str]): @@ -91,6 +93,32 @@ def check_tools_index_consistency(warnings: list[str]): warnings.append('docs/tools/README.md lists Qoder CLI as a directory link but labels it 单文件; consider clarifying depth/type.') +def check_evidence_index_matches_data(metadata: dict, errors: list[str]): + text = read_text(EVIDENCE_INDEX) + evidence_rows = {} + for agent_name, status, source_type in EVIDENCE_PATTERN.findall(text): + evidence_rows[agent_name.strip()] = { + 'status': status.strip('` '), + 'source_type': source_type.strip('` '), + } + + for agent in metadata.get('agents', []): + name = agent.get('name') + evidence = agent.get('evidence', {}) + row = evidence_rows.get(name) + if not row: + errors.append(f'docs/evidence-index.md is missing an evidence row for {name}') + continue + if row.get('status') != evidence.get('status'): + errors.append( + f"docs/evidence-index.md status mismatch for {name}: {row.get('status')} != {evidence.get('status')}" + ) + if row.get('source_type') != evidence.get('source_type'): + errors.append( + f"docs/evidence-index.md source_type mismatch for {name}: {row.get('source_type')} != {evidence.get('source_type')}" + ) + + def main() -> int: errors: list[str] = [] warnings: list[str] = [] @@ -106,6 +134,7 @@ def main() -> int: check_agent_mentions(metadata, warnings) check_links(errors, warnings) check_tools_index_consistency(warnings) + check_evidence_index_matches_data(metadata, errors) if errors: for item in errors: diff --git a/scripts/check_stale_data.py b/scripts/check_stale_data.py index 064d92d..b310fe6 100644 --- a/scripts/check_stale_data.py +++ b/scripts/check_stale_data.py @@ -6,7 +6,7 @@ from datetime import date, datetime from pathlib import Path -ROOT = Path('/root/git/codeagents-x1') +ROOT = Path(__file__).resolve().parents[1] DOCS = ROOT / 'docs' DATA_FILE = DOCS / 'data' / 'agents-metadata.json' From 6c3144f547fd994a72dcf274eed0608a1e47dbc0 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 30 Mar 2026 11:27:09 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E5=93=8D=E5=BA=94=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=E5=B9=B6=E6=94=B6=E7=B4=A7=E5=85=83=E6=95=B0=E6=8D=AE=E7=BA=A6?= =?UTF-8?q?=E6=9D=9F=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Qwen-Coder --- docs/data/README.md | 4 +++- docs/data/SCHEMA.md | 9 ++++++++- docs/data/agents-metadata.schema.json | 2 +- scripts/check_data_schema.py | 25 ++++++++++++++++--------- scripts/check_stale_data.py | 2 +- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/docs/data/README.md b/docs/data/README.md index 5f83e2d..ac2a284 100644 --- a/docs/data/README.md +++ b/docs/data/README.md @@ -27,7 +27,9 @@ ### 3. 动态事实必须带时间戳 -所有动态数据都应包含 `as_of` 或 `last_verified`: +所有动态数据都应有明确时间基准: +- 优先使用字段内的 `as_of` 或 `last_verified` +- 对于 `stars` 这类紧凑展示字段,统一以顶层 `last_updated` 作为批次时间基准 ```json { diff --git a/docs/data/SCHEMA.md b/docs/data/SCHEMA.md index 8b69dc7..58609df 100644 --- a/docs/data/SCHEMA.md +++ b/docs/data/SCHEMA.md @@ -61,7 +61,7 @@ | `implementation_language` | string | 是 | 静态 | 主要实现语言 | | `runtime` | string | 是 | 半静态 | 运行时或分发形态 | | `package_ecosystem` | string | 是 | 半静态 | npm / pypi / desktop / none 等 | -| `stars` | string | 否 | 动态 | 社区热度指标,建议保持与外部来源一致 | +| `stars` | string | 否 | 动态 | 社区热度指标,默认表示 GitHub Stars 的紧凑展示值;时间基准由顶层 `last_updated` 统一约束 | | `downloads` | object | 否 | 动态 | 下载量信息 | | `pricing_summary` | string | 否 | 动态 | 定价摘要,适合汇总页使用 | | `free_tier` | string | 否 | 动态 | 免费层摘要 | @@ -98,6 +98,13 @@ - `none` - `unknown` +### `stars` + +- 默认语义:GitHub Stars 的紧凑展示值,如 `83k`、`~132k` +- 时间基准:由顶层 `last_updated` 统一表示本批动态数据的采样时间 +- 允许哨兵值:`-`、`—`、`unknown` +- 对于闭源或无明确仓库映射的 Agent,可使用 `-` / `unknown`,并在相应文档中说明原因 + ### `evidence.status` - `complete` diff --git a/docs/data/agents-metadata.schema.json b/docs/data/agents-metadata.schema.json index 29a62dc..6e90012 100644 --- a/docs/data/agents-metadata.schema.json +++ b/docs/data/agents-metadata.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/codeagents-x1/docs/data/agents-metadata.schema.json", + "$id": "https://example.com/codeagents/docs/data/agents-metadata.schema.json", "title": "agents-metadata", "type": "object", "additionalProperties": false, diff --git a/scripts/check_data_schema.py b/scripts/check_data_schema.py index 62e6bc8..609316b 100644 --- a/scripts/check_data_schema.py +++ b/scripts/check_data_schema.py @@ -19,6 +19,7 @@ AGENT_OPTIONAL = {'stars', 'downloads', 'pricing_summary', 'free_tier'} DOWNLOADS_REQUIRED = {'type', 'value', 'as_of'} EVIDENCE_REQUIRED = {'status', 'source_type', 'evidence_path', 'last_verified'} +STARS_SENTINEL_VALUES = {'-', '—', 'unknown'} def load_json(path: Path): @@ -89,13 +90,16 @@ def validate_agent( download_types: set[str], evidence_status_values: set[str], evidence_source_types: set[str], + agent_required: set[str], + downloads_required: set[str], + evidence_required: set[str], ): label = f'agents[{index}]' if not isinstance(agent, dict): errors.append(f'{label}: must be an object') return - require_keys(agent, AGENT_REQUIRED, AGENT_OPTIONAL, label, errors) + require_keys(agent, agent_required, AGENT_OPTIONAL, label, errors) agent_id = agent.get('id') if not isinstance(agent_id, str) or not ID_RE.match(agent_id): @@ -114,8 +118,12 @@ def validate_agent( if category not in category_values: errors.append(f'{label}.category: must be one of {sorted(category_values)}') - if 'stars' in agent and not isinstance(agent.get('stars'), str): - errors.append(f'{label}.stars: must be a string when present') + if 'stars' in agent: + stars_value = agent.get('stars') + if not isinstance(stars_value, str) or not stars_value.strip(): + errors.append(f'{label}.stars: must be a non-empty string when present') + elif stars_value not in STARS_SENTINEL_VALUES and not re.match(r'^~?\d+(?:\.\d+)?[kKmM]?$', stars_value): + errors.append(f'{label}.stars: must be a compact count like `83k` or a sentinel value {sorted(STARS_SENTINEL_VALUES)}') if 'pricing_summary' in agent and not isinstance(agent.get('pricing_summary'), str): errors.append(f'{label}.pricing_summary: must be a string when present') if 'free_tier' in agent and not isinstance(agent.get('free_tier'), str): @@ -126,7 +134,7 @@ def validate_agent( if not isinstance(downloads, dict): errors.append(f'{label}.downloads: must be an object') else: - require_keys(downloads, DOWNLOADS_REQUIRED, set(), f'{label}.downloads', errors) + require_keys(downloads, downloads_required, set(), f'{label}.downloads', errors) if downloads.get('type') not in download_types: errors.append(f'{label}.downloads.type: must be one of {sorted(download_types)}') if not isinstance(downloads.get('value'), str) or not downloads.get('value').strip(): @@ -138,7 +146,7 @@ def validate_agent( if not isinstance(evidence, dict): errors.append(f'{label}.evidence: must be an object') else: - require_keys(evidence, EVIDENCE_REQUIRED, set(), f'{label}.evidence', errors) + require_keys(evidence, evidence_required, set(), f'{label}.evidence', errors) if evidence.get('status') not in evidence_status_values: errors.append(f'{label}.evidence.status: must be one of {sorted(evidence_status_values)}') if evidence.get('source_type') not in evidence_source_types: @@ -202,10 +210,6 @@ def main() -> int: if not isinstance(agents, list) or not agents: errors.append('root.agents: must be a non-empty array') else: - global AGENT_REQUIRED, DOWNLOADS_REQUIRED, EVIDENCE_REQUIRED - AGENT_REQUIRED = agent_required - DOWNLOADS_REQUIRED = downloads_required - EVIDENCE_REQUIRED = evidence_required seen_ids: set[str] = set() for idx, agent in enumerate(agents): validate_agent( @@ -217,6 +221,9 @@ def main() -> int: download_types, evidence_status_values, evidence_source_types, + agent_required, + downloads_required, + evidence_required, ) if errors: diff --git a/scripts/check_stale_data.py b/scripts/check_stale_data.py index b310fe6..e03c05d 100644 --- a/scripts/check_stale_data.py +++ b/scripts/check_stale_data.py @@ -99,7 +99,7 @@ def check_tracked_files_for_token_drift(metadata_tokens: dict[str, dict[str, set matched_agents = [name for name in metadata_tokens if name in line] for agent_name in matched_agents: token_groups = metadata_tokens[agent_name] - if 'Stars' in line or 'stars' in line or 'Stars' in line: + if 'Stars' in line or 'stars' in line: if token_groups['stars'] and not any(token in line for token in token_groups['stars'] if token): warnings.append( f'{path.relative_to(ROOT)}:{line_no} 中 {agent_name} 的 Stars 可能未与 docs/data 同步: {line.strip()}' From e9c8210afa87594abb292238f31de21ec9e7da60 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 30 Mar 2026 12:52:55 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=E9=81=97=E6=BC=8F=E5=85=83=E6=95=B0=E6=8D=AE=E5=B9=B6=E7=BB=A7?= =?UTF-8?q?=E7=BB=AD=E6=94=B6=E6=95=9B=E5=8A=A8=E6=80=81=E5=AE=9A=E4=BB=B7?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Qwen-Coder --- README.md | 2 +- docs/SUMMARY.md | 2 +- docs/comparison/pricing.md | 46 +++++++++++++++++++--------------- docs/data/agents-metadata.json | 24 ++++++++++++++++++ docs/evidence-index.md | 1 + 5 files changed, 53 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index cfc111f..5c93ad7 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ | [Warp](./docs/tools/warp.md) | Warp | 专有 | Rust | 多种 | GPU 渲染终端,块结构输出 | | [Qwen Code](./docs/tools/qwen-code/) | 阿里云 | Apache-2.0 | TypeScript | 6+ | 免费 1000 次/天,Arena 多模型竞争,41 命令 | | [SWE-agent](./docs/tools/swe-agent.md) | Princeton | MIT | Python | 100+ | SWE-bench 评估,Docker 沙箱 | -| [Copilot CLI](./docs/tools/copilot-cli/) | GitHub | 专有 | Shell | 多种 | 67 GitHub 工具,GitHub 生态集成 | +| [Copilot CLI](./docs/tools/copilot-cli/) | GitHub | 专有 | TypeScript | 多种 | 67 GitHub 工具,GitHub 生态集成 | | [Kimi CLI](./docs/tools/kimi-cli/) | 月之暗面 | Apache-2.0 | Python | 6 | Wire 协议,D-Mail 时间回溯 | | [Cursor](./docs/tools/cursor-cli.md) | Cursor | 专有 | TypeScript | 多种 | AI 原生 IDE,Background Agent | | [Qoder CLI](./docs/tools/qoder-cli/) | QoderAI | 专有 | Go | 多种 | Quest 模式,Claude Code 兼容 | diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3623acf..63c09e4 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -26,7 +26,7 @@ ├── 企业安全合规 → Claude Code(28 条 BLOCK + 5 层设置 + 沙箱) ├── GitHub 深度集成 → Copilot CLI(35 GitHub 工具 + Actions/PR/Issues) -├── 完全免费 → Qwen Code(1000 次/天)或 Gemini CLI(1500 次/天) +├── 完全免费 → Qwen Code 或 Gemini CLI(具体额度见 docs/data/agents-metadata.json) ├── 隐私零遥测 → Kimi CLI(零分析)或 OpenCode(零分析) ├── Git 工作流控制 → Aider(/commit /undo /diff /git + 自动提交归因) ├── 最大模型灵活性 → Goose(58+ 提供商)或 Aider(100+ via LiteLLM) diff --git a/docs/comparison/pricing.md b/docs/comparison/pricing.md index e2a3863..47b7381 100644 --- a/docs/comparison/pricing.md +++ b/docs/comparison/pricing.md @@ -20,11 +20,13 @@ ## Claude Code 详细定价 -| 计划 | 月费 | 包含 | 模型 | Opus 用量 | +> 月费与计划细节属于高频变化信息,请优先以 `docs/data/agents-metadata.json`、`docs/data/CHANGELOG.md` 和官方定价页为准。此处仅保留计划结构与成本方法。 + +| 计划 | 形态 | 包含 | 模型 | Opus 用量 | |------|------|------|------|----------| -| **Pro** | $20 | 有限使用 | Sonnet 4.6(默认) | 有限 | -| **Max (5x)** | $100 | 高速率 | Opus 4.6(默认) | 高 | -| **Max (20x)** | $200 | 最高速率 | Opus 4.6(默认) | 最高 | +| **Pro** | 订阅 | 有限使用 | Sonnet 4.6(默认) | 有限 | +| **Max (5x)** | 高阶订阅 | 高速率 | Opus 4.6(默认) | 高 | +| **Max (20x)** | 高阶订阅 | 最高速率 | Opus 4.6(默认) | 最高 | | **API** | 按量 | 无限(按 token) | 全部 | 无限 | API 按量价格(参考): @@ -37,12 +39,14 @@ API 按量价格(参考): ## Copilot CLI 详细定价 -| 计划 | 月费 | Premium Requests | 免费模型 | +> 订阅价格与配额可能频繁调整,请优先以 `docs/data/agents-metadata.json`、`docs/data/CHANGELOG.md` 和官方定价页为准。 + +| 计划 | 形态 | Premium Requests | 免费模型 | |------|------|-----------------|---------| -| **Free** | $0 | 有限 | gpt-5-mini (0x), gpt-4.1 (0x) | -| **Pro** | $10 | 500/月 | 同上 | -| **Business** | $19/人 | 500/月 | 同上 | -| **Enterprise** | $39/人 | 1000/月 | 同上 | +| **Free** | 免费 | 有限 | gpt-5-mini (0x), gpt-4.1 (0x) | +| **Pro** | 个人订阅 | 500/月 | 同上 | +| **Business** | 团队订阅 | 500/月 | 同上 | +| **Enterprise** | 企业订阅 | 1000/月 | 同上 | 模型倍率: @@ -61,9 +65,9 @@ API 按量价格(参考): | Agent | 模型 | 估算 token | 估算费用 | |------|------|-----------|---------| -| Claude Code (Pro) | Sonnet 4.6 | ~50K in + ~10K out | 包含在 $20/月 | +| Claude Code (Pro) | Sonnet 4.6 | ~50K in + ~10K out | 包含在订阅内 | | Claude Code (API) | Sonnet 4.6 | ~50K in + ~10K out | ~$0.30 | -| Copilot CLI (Pro) | claude-sonnet-4.5 | 1 premium request | 包含在 $10/月 | +| Copilot CLI (Pro) | claude-sonnet-4.5 | 1 premium request | 包含在订阅内 | | Codex CLI | gpt-5.1-codex | ~50K in + ~10K out | ~$0.20 | | Aider | claude-sonnet-4.6 | ~30K in + ~5K out | ~$0.17 | | Gemini CLI | gemini-2.5-pro | ~50K in + ~10K out | 免费层 | @@ -73,10 +77,10 @@ API 按量价格(参考): | Agent | 模型 | 估算 token | 估算费用 | |------|------|-----------|---------| -| Claude Code (Pro) | Sonnet 4.6 | ~500K in + ~100K out | 包含在 $20/月 | +| Claude Code (Pro) | Sonnet 4.6 | ~500K in + ~100K out | 包含在订阅内 | | Claude Code (API) | Sonnet 4.6 | ~500K in + ~100K out | ~$3.00 | | Claude Code (API) | Opus 4.6 | ~500K in + ~100K out | ~$15.00 | -| Copilot CLI (Pro) | claude-sonnet-4.5 | ~10 premium requests | 包含在 $10/月 | +| Copilot CLI (Pro) | claude-sonnet-4.5 | ~10 premium requests | 包含在订阅内 | | Codex CLI | gpt-5.1-codex | ~500K in + ~100K out | ~$2.00 | | Aider | claude-sonnet-4.6 | ~300K in + ~50K out | ~$1.65 | | Gemini CLI | gemini-2.5-pro | ~500K in + ~100K out | 免费层(可能触发限制) | @@ -85,7 +89,7 @@ API 按量价格(参考): | Agent | 模型 | 估算 token | 估算费用 | |------|------|-----------|---------| -| Claude Code (Max) | Opus 4.6 | ~2M in + ~500K out | 包含在 $100/月 | +| Claude Code (Max) | Opus 4.6 | ~2M in + ~500K out | 包含在高阶订阅内 | | Claude Code (API) | Opus 4.6 | ~2M in + ~500K out | ~$67.50 | | Copilot CLI (Pro) | claude-opus-4.5 | ~30 requests (3x) | 可能超出 500 配额 | | Codex CLI | gpt-5.1-codex-max | ~2M in + ~500K out | ~$15.00 | @@ -109,11 +113,13 @@ API 按量价格(参考): ## Qoder CLI 定价(信用制) -| 计划 | 月费 | 月信用 | +> 信用与月费均可能调整,请优先参考 `docs/data/agents-metadata.json`、`docs/data/CHANGELOG.md` 与官方页面。 + +| 计划 | 形态 | 月信用 | |------|------|--------| -| Free | $0 | 300 | -| Pro | $10(50% off) | 2,000 | -| Pro+ | $30(50% off) | 6,000 | -| Ultra | $100(50% off) | 20,000 | +| Free | 免费 | 300 | +| Pro | 订阅 | 2,000 | +| Pro+ | 高阶订阅 | 6,000 | +| Ultra | 高阶订阅 | 20,000 | -> 附加信用 $0.01/个,1000 起购。 +> 附加信用按量购买,具体单价与起购门槛以官方页面为准。 diff --git a/docs/data/agents-metadata.json b/docs/data/agents-metadata.json index 5aea711..c1b26fd 100644 --- a/docs/data/agents-metadata.json +++ b/docs/data/agents-metadata.json @@ -243,6 +243,30 @@ "last_verified": "2026-03-26" } }, + { + "id": "oh-my-openagent", + "name": "Oh My OpenAgent", + "category": "single-file", + "license": "SUL-1.0", + "developer": "code-yeongyu", + "implementation_language": "TypeScript", + "runtime": "OpenCode Harness / Node.js", + "package_ecosystem": "npm", + "stars": "~44k", + "downloads": { + "type": "unknown", + "value": "—", + "as_of": "2026-03-26" + }, + "pricing_summary": "取决于底层模型提供商", + "free_tier": "取决于底层提供商", + "evidence": { + "status": "single-file-only", + "source_type": "summary-analysis", + "evidence_path": "docs/tools/oh-my-openagent.md", + "last_verified": "2026-03-26" + } + }, { "id": "cline", "name": "Cline", diff --git a/docs/evidence-index.md b/docs/evidence-index.md index d6e8143..1da3233 100644 --- a/docs/evidence-index.md +++ b/docs/evidence-index.md @@ -35,6 +35,7 @@ | OpenCode | 多文件 | `complete` | `source-analysis` | `docs/tools/opencode/EVIDENCE.md` | 2026-03-26 | 月度 | | Goose | 多文件 | `complete` | `source-analysis` | `docs/tools/goose/EVIDENCE.md` | 2026-03-26 | 月度 | | Qoder CLI | 多文件 | `partial` | `binary-analysis` | `docs/tools/qoder-cli/EVIDENCE.md` | 2026-03-26 | 双周 | +| Oh My OpenAgent | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/oh-my-openagent.md` | 2026-03-26 | 季度 | | Cline | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/cline.md` | 2026-03-26 | 季度 | | Continue | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/continue.md` | 2026-03-26 | 季度 | | Cursor CLI | 单文件 | `single-file-only` | `summary-analysis` | `docs/tools/cursor-cli.md` | 2026-03-26 | 季度 | From c185a745d40cb9a290c882934830f2b158d6ffc8 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 30 Mar 2026 13:09:00 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=94=B6=E5=B0=BE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A=E7=BB=9F=E4=B8=80=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC=E5=B9=B6=E6=81=A2=E5=A4=8D=20Aider=20?= =?UTF-8?q?=E9=87=87=E6=A0=B7=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Qwen-Coder --- docs/comparison/pricing.md | 8 ++++---- docs/comparison/privacy-telemetry.md | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/comparison/pricing.md b/docs/comparison/pricing.md index 47b7381..4b3b075 100644 --- a/docs/comparison/pricing.md +++ b/docs/comparison/pricing.md @@ -4,7 +4,7 @@ ## 定价模式总览 -> 本表仅保留定价结构。具体月费、免费层额度等高频变化数字请查阅 `docs/data/agents-metadata.json`。 +> 本表仅保留定价结构。具体月费、免费层额度等高频变化数字请查阅 [`../data/agents-metadata.json`](../data/agents-metadata.json)。 | Agent | 模式 | 免费层形态 | 按量计费 | |------|------|-----------|---------| @@ -20,7 +20,7 @@ ## Claude Code 详细定价 -> 月费与计划细节属于高频变化信息,请优先以 `docs/data/agents-metadata.json`、`docs/data/CHANGELOG.md` 和官方定价页为准。此处仅保留计划结构与成本方法。 +> 月费与计划细节属于高频变化信息,请优先以 [`../data/agents-metadata.json`](../data/agents-metadata.json)、[`../data/CHANGELOG.md`](../data/CHANGELOG.md) 和官方定价页为准。此处仅保留计划结构与成本方法。 | 计划 | 形态 | 包含 | 模型 | Opus 用量 | |------|------|------|------|----------| @@ -39,7 +39,7 @@ API 按量价格(参考): ## Copilot CLI 详细定价 -> 订阅价格与配额可能频繁调整,请优先以 `docs/data/agents-metadata.json`、`docs/data/CHANGELOG.md` 和官方定价页为准。 +> 订阅价格与配额可能频繁调整,请优先以 [`../data/agents-metadata.json`](../data/agents-metadata.json)、[`../data/CHANGELOG.md`](../data/CHANGELOG.md) 和官方定价页为准。 | 计划 | 形态 | Premium Requests | 免费模型 | |------|------|-----------------|---------| @@ -113,7 +113,7 @@ API 按量价格(参考): ## Qoder CLI 定价(信用制) -> 信用与月费均可能调整,请优先参考 `docs/data/agents-metadata.json`、`docs/data/CHANGELOG.md` 与官方页面。 +> 信用与月费均可能调整,请优先参考 [`../data/agents-metadata.json`](../data/agents-metadata.json)、[`../data/CHANGELOG.md`](../data/CHANGELOG.md) 与官方页面。 | 计划 | 形态 | 月信用 | |------|------|--------| diff --git a/docs/comparison/privacy-telemetry.md b/docs/comparison/privacy-telemetry.md index a2492ca..0a7c244 100644 --- a/docs/comparison/privacy-telemetry.md +++ b/docs/comparison/privacy-telemetry.md @@ -11,7 +11,7 @@ | **Claude Code** | Anthropic Metrics + Datadog + Segment | 开启 | 是 | 是 | 否 | | **Copilot CLI** | GitHub/Microsoft 内部 | 开启 | 未确认 | 是 | 未确认 | | **Codex CLI** | OpenAI 内部 | 开启 | 未确认 | 是 | 未确认 | -| **Aider** | PostHog | 默认关闭(opt-in) | 是 | 是 | 否 | +| **Aider** | PostHog | 默认关闭(opt-in,10% 采样) | 是 | 是 | 否 | | **Gemini CLI** | OpenTelemetry + Google Clearcut | 开启 | 是 | 是 | 否 | | **Kimi CLI** | 无 | — | 否 | 否 | 否 | | **OpenCode** | 无 | — | 否 | 否 | 否 | @@ -54,6 +54,7 @@ ### Aider(源码 analytics.py) - 随机 UUID(非关联到账户) +- 默认关闭;用户 opt-in 后按 10% 采样发送遥测 - python_version, os_platform, os_release, machine, aider_version - model 名(未知模型自动 redact 为 `provider/REDACTED`)