chore: import upstream snapshot with attribution
Auto Release / auto-release (push) Failing after 1s
CI / lint (push) Failing after 0s
CI / screenshot-quality (push) Failing after 3s
CI / pr-policy (push) Has been skipped
CI / test (3.11) (push) Failing after 0s
CI / test (3.12) (push) Failing after 0s
CI / test (3.13) (push) Failing after 3s
CI / coverage (push) Failing after 1s
Legibility / legibility (push) Failing after 3s
@@ -0,0 +1,11 @@
|
||||
# Maintainer Docs
|
||||
|
||||
This directory contains maintainer and agent automation materials that are useful for project operations but are not part of the public user documentation.
|
||||
|
||||
- `standards/` contains required maintainer policies and validation rules.
|
||||
- `plans/` contains implementation plans and handoff notes.
|
||||
- `error-experience/` and `good-experience/` contain internal learning records.
|
||||
- `guides/` contains maintainer-facing workflow and integration playbooks.
|
||||
- `architecture/manifest.yaml` lists paths covered by the legibility check.
|
||||
|
||||
Keep public user documentation and README assets in `docs/`.
|
||||
@@ -0,0 +1,11 @@
|
||||
expected_paths:
|
||||
- AGENTS.md
|
||||
- .agents/docs/README.md
|
||||
- .agents/docs/standards/README.md
|
||||
- .agents/docs/standards/hard-rules.md
|
||||
- .agents/docs/standards/validation-and-gates.md
|
||||
- .agents/docs/standards/e2e-and-evidence.md
|
||||
- .agents/docs/standards/coding-and-runtime.md
|
||||
- .agents/docs/standards/workflow-and-review.md
|
||||
- scripts/check_legibility.py
|
||||
- .github/workflows/legibility.yml
|
||||
@@ -0,0 +1,36 @@
|
||||
# PR #1 过期基线分支导致 CI 失败
|
||||
|
||||
**日期:** 2026-02-25
|
||||
**严重级别:** 中
|
||||
**标签:** git, CI, uv.lock, rebase
|
||||
|
||||
## 发生了什么
|
||||
|
||||
PR #1(新增 `--tap-host` 功能)在 CI 中测试失败,原因是它基于过期的 `main` 分支。
|
||||
该分支从 `bc7d344` 分叉,而 `main` 已推进到 `bd7ec3f`。两个版本的 `uv.lock`
|
||||
不兼容,导致依赖解析失败,测试报错。
|
||||
|
||||
## 根因
|
||||
|
||||
在打开 PR 前,feature 分支没有 rebase 到最新 `main`。
|
||||
`uv.lock` 已产生分叉,过期版本无法解析正确依赖集。
|
||||
|
||||
## 影响
|
||||
|
||||
- 原本正确的 PR 在 CI 中失败
|
||||
- 需要额外一次 rebase 循环修复
|
||||
- 合并延迟了一个 review 回合
|
||||
|
||||
## 经验
|
||||
|
||||
**在打开或合并 PR 前,始终先 rebase 到最新 `main`。**
|
||||
|
||||
防止复发的 checklist:
|
||||
1. 开 PR 前:`git fetch origin && git rebase origin/main`
|
||||
2. 验证 `uv.lock` 最新:`uv lock --check`
|
||||
3. rebase 后本地运行完整测试:`uv run pytest tests/ -x --timeout=60`
|
||||
|
||||
## 相关
|
||||
|
||||
- PR:#1
|
||||
- Commits:bc7d344..bd7ec3f
|
||||
@@ -0,0 +1,28 @@
|
||||
# Codex Sandbox 无法执行 Git Commit
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**严重级别:** 中
|
||||
**标签:** codex, sandbox, git, environment
|
||||
|
||||
## 问题
|
||||
|
||||
Codex `--full-auto` sandbox 会阻止对 `.git/index.lock` 和
|
||||
`.git/FETCH_HEAD` 的写访问,导致 `git commit` 与 `git fetch` 无法完成。
|
||||
|
||||
## 影响
|
||||
|
||||
- Codex 可以通过 `git add` 暂存文件,但无法 commit 或 fetch。
|
||||
- 任何要求最后提交的工作流都必须转到外部环境执行。
|
||||
|
||||
## 变通方案
|
||||
|
||||
- 用 Codex 完成代码编辑、重构、测试和 lint 检查。
|
||||
- Codex 完成后,在 sandbox 外执行 `git add -A && git commit`
|
||||
(通过 OpenClaw exec 或本地 shell)。
|
||||
- 不要在 Codex 任务提示中要求 `git commit`;它会静默失败或直接报错。
|
||||
|
||||
## 经验
|
||||
|
||||
Codex sandbox 会限制 `.git/` 目录写入。把会产生文件变更的任务交给 Codex 时,
|
||||
始终规划一个 Codex 后置 commit 步骤。将流程拆分为:
|
||||
Codex 编辑 → 外部 git commit → push。
|
||||
@@ -0,0 +1,26 @@
|
||||
# Codex Sandbox 阻止 tmux Socket 创建
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**严重级别:** 中
|
||||
**标签:** codex, sandbox, tmux, environment
|
||||
|
||||
## 问题
|
||||
|
||||
在 Codex `--full-auto` 中运行基于 tmux 的 E2E 流程失败,因为 tmux 无法在
|
||||
`/private/tmp` 下创建或访问其 socket 路径(permission denied)。
|
||||
|
||||
## 影响
|
||||
|
||||
- Codex 可以更新代码和文档,但不能直接执行 tmux 交互测试。
|
||||
- 依赖 tmux 的端到端验证必须在 Codex sandbox 外完成。
|
||||
|
||||
## 变通方案
|
||||
|
||||
- 用 Codex 处理代码编辑、重构以及可静态/可测试逻辑。
|
||||
- 在 Codex 外执行依赖 tmux 的验证(例如 OpenClaw exec 或本地 shell)。
|
||||
- 外部验证后,再把结果回填到仓库文档/测试中。
|
||||
|
||||
## 经验
|
||||
|
||||
需要系统级终端复用器(`tmux`、`screen`)的任务,无法完整委托给受限的 Codex sandbox。
|
||||
必须明确划分 sandbox 安全工作与外部执行职责。
|
||||
@@ -0,0 +1,23 @@
|
||||
# CLI 中硬编码版本字符串
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**严重级别:** 中
|
||||
**标签:** versioning, cli, metadata, release
|
||||
|
||||
## 问题
|
||||
|
||||
`cli.py` 中的 `__version__` 被硬编码为 `"0.1.7"`,发布时从未更新。
|
||||
用户即使升级后,用 `-v` 看到的仍是错误版本。
|
||||
|
||||
## 根因
|
||||
|
||||
版本字符串是源码字面量,而不是从 package metadata 读取。
|
||||
|
||||
## 修复
|
||||
|
||||
将硬编码值替换为 `importlib.metadata.version("claude-tap")`,
|
||||
使其始终与 `pyproject.toml` 和 PyPI package metadata 一致。
|
||||
|
||||
## 经验
|
||||
|
||||
不要硬编码版本字符串。始终使用 `importlib.metadata`(或其他单一真相源,例如从 `pyproject.toml` 动态读取)。
|
||||
@@ -0,0 +1,68 @@
|
||||
# Python 3.13 SSL 要求证书包含 Authority Key Identifier
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**严重级别:** 高(CI 阻塞 - 连续 4 次失败)
|
||||
**标签:** ssl, certificates, python313, ci, certs.py
|
||||
|
||||
## 问题
|
||||
|
||||
`test_forward_proxy_connect` 在 Python 3.13 下失败,报错如下:
|
||||
|
||||
```
|
||||
SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED]
|
||||
certificate verify failed: Missing Authority Key Identifier (_ssl.c:1032)')
|
||||
```
|
||||
|
||||
CI 在 Python 3.11/3.12 通过,但在 3.13 持续失败。
|
||||
|
||||
## 根因
|
||||
|
||||
Python 3.13 收紧了 SSL 证书校验。它现在**要求**由 CA 签发的证书包含
|
||||
Authority Key Identifier(AKI)扩展。
|
||||
|
||||
我们的 `certs.py` 生成结果:
|
||||
- CA 证书:缺少 `SubjectKeyIdentifier`(SKI)
|
||||
- Host 证书:缺少 `AuthorityKeyIdentifier`(AKI)和 SKI
|
||||
|
||||
这些扩展在 X.509 中技术上可选,但 Python 3.13 的 SSL 实现会将缺失 AKI
|
||||
视为校验失败。
|
||||
|
||||
## 为什么本地未捕获
|
||||
|
||||
本地开发环境是 Python 3.11,不强制 AKI 存在。
|
||||
该问题只在 CI 的 Python 3.13 matrix 中暴露。
|
||||
|
||||
## 修复
|
||||
|
||||
在 `certs.py` 中为两类证书都加入正确的 X.509 扩展:
|
||||
|
||||
**CA 证书:**
|
||||
```python
|
||||
.add_extension(
|
||||
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
|
||||
critical=False,
|
||||
)
|
||||
```
|
||||
|
||||
**Host 证书:**
|
||||
```python
|
||||
.add_extension(
|
||||
x509.AuthorityKeyIdentifier.from_issuer_public_key(
|
||||
self._ca_key.public_key()
|
||||
),
|
||||
critical=False,
|
||||
)
|
||||
.add_extension(
|
||||
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
|
||||
critical=False,
|
||||
)
|
||||
```
|
||||
|
||||
## 经验
|
||||
|
||||
1. **在假定测试通过之前,务必检查 CI 的全部 Python 版本。**
|
||||
本地 3.11 绿灯不代表 3.13 也绿灯。
|
||||
2. **自签证书生成必须包含 SKI/AKI 扩展。**
|
||||
即使旧版 Python 容忍缺失,新版也可能不容忍。
|
||||
3. **当 CI 只在特定 Python 版本失败时,先看该版本 changelog 是否有更严格的安全要求**。
|
||||
SSL/TLS 校验是常见收紧区域。
|
||||
@@ -0,0 +1,33 @@
|
||||
# `rg`(ripgrep)并非所有环境都可用
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**严重级别:** 低
|
||||
**标签:** portability, shell, tooling
|
||||
|
||||
## 问题
|
||||
|
||||
Shell 脚本 `run_real_e2e_tmux.sh` 使用 `rg`(ripgrep)做 JSONL 断言。
|
||||
在未安装 ripgrep 或不在 `$PATH` 的环境里,脚本会静默失败或给出误导结果。
|
||||
|
||||
## 根因
|
||||
|
||||
`rg` 是 Rust 工具,不属于 POSIX,也不是 macOS 默认安装。
|
||||
CI runner、Codex sandbox 和新初始化机器都可能缺少它。
|
||||
|
||||
## 修复
|
||||
|
||||
将全部 `rg` 调用替换为 `grep -F`(固定字符串匹配),后者是 POSIX 标准且普遍可用。
|
||||
|
||||
```bash
|
||||
# Before (fragile)
|
||||
rg '"tool_use"' "$JSONL_FILE"
|
||||
|
||||
# After (portable)
|
||||
grep -F '"tool_use"' "$JSONL_FILE"
|
||||
```
|
||||
|
||||
## 经验
|
||||
|
||||
**在 shell 脚本中优先使用 POSIX 标准工具**:`grep`、`sed`、`awk`、`find`、`cut`。
|
||||
`rg`、`fd`、`jq` 等工具应留给交互式使用,或明确声明为依赖。
|
||||
脚本必须能在裸环境运行。
|
||||
@@ -0,0 +1,30 @@
|
||||
# 子进程退出后因 SIGTTOU 被挂起
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**严重级别:** 高
|
||||
**标签:** signal, tty, process-group, tcsetpgrp, exit-path
|
||||
|
||||
## 问题
|
||||
|
||||
Claude Code 退出后,`claude-tap` 尝试打印摘要并生成 HTML 输出,
|
||||
却每次都触发 `suspended (tty output)`。
|
||||
|
||||
## 影响
|
||||
|
||||
高。这是用户感知最强的问题:进程在收尾路径完成前就被挂起,
|
||||
用户始终拿不到 HTML 输出。
|
||||
|
||||
## 根因
|
||||
|
||||
`claude-tap` 通过 `tcsetpgrp` 把终端前台控制权交给 Claude Code 子进程。
|
||||
当子进程退出时,`claude-tap` 仍处于后台进程组。任何终端写操作都会触发 `SIGTTOU`,
|
||||
导致进程被挂起,因此 `finally` 块(HTML 生成与摘要)无法运行。
|
||||
|
||||
## 修复
|
||||
|
||||
在调用 `tcsetpgrp` 夺回前台进程组前先忽略 `SIGTTOU`,
|
||||
之后恢复原始 signal handler。
|
||||
|
||||
## 经验
|
||||
|
||||
只要使用 `tcsetpgrp` 进行进程组切换,在切回父进程前台组时就必须处理 `SIGTTOU`。
|
||||
@@ -0,0 +1,35 @@
|
||||
# GitHub Raw URL 导致 PR 截图缓存陈旧
|
||||
|
||||
**日期:** 2026-02-27
|
||||
**标签:** github, pr, screenshot, cache, review
|
||||
|
||||
## 问题
|
||||
|
||||
向 PR 分支 push 更新后的截图后,PR 描述仍显示旧图,看起来像变更未生效。
|
||||
|
||||
## 根因
|
||||
|
||||
PR 描述引用了稳定不变文件名的 `raw.githubusercontent.com` 路径。
|
||||
GitHub/CDN 会在一段时间内继续缓存这些 URL 的旧内容。
|
||||
|
||||
## 影响
|
||||
|
||||
- review 混乱:reviewer 误以为 PR 证据未更新。
|
||||
- 增加沟通成本与反复手动刷新。
|
||||
|
||||
## 已实施修复
|
||||
|
||||
1. 生成带版本后缀的新图片文件名(`*-v2.png`)。
|
||||
2. 更新 PR 描述中的图片链接,指向新文件名。
|
||||
3. 确认 PR 引用已切换到版本化 URL。
|
||||
|
||||
## 预防规则
|
||||
|
||||
更新 PR 内嵌截图时,优先通过改文件名使用不可变图片 URL
|
||||
(如 `before-v2.png`、`after-v3.png`),不要复用旧文件名。
|
||||
|
||||
## 验证 Checklist
|
||||
|
||||
- 新文件出现在 `Files changed`。
|
||||
- PR markdown 链接指向带版本后缀的文件名。
|
||||
- reviewer 无需强制刷新也能看到更新图片。
|
||||
@@ -0,0 +1,35 @@
|
||||
# Codex Reverse Mode 可能遗漏 Responses 流量
|
||||
|
||||
**日期:** 2026-02-28
|
||||
**严重级别:** 高
|
||||
**标签:** codex, reverse-proxy, websocket, trace-capture
|
||||
|
||||
## 问题
|
||||
|
||||
在 `--tap-client codex` 的 reverse mode 中,部分运行只捕获到 `/v1/models`,
|
||||
viewer 中 token usage 为 0。后续对话流量未能稳定捕获为 `/v1/responses`。
|
||||
|
||||
## 根因
|
||||
|
||||
Codex 在交互/会话流中可能尝试基于 websocket 的 Responses 路径。
|
||||
当用户配置或特性开关启用 websocket 行为时,reverse-mode base URL 路由会出现
|
||||
trace 捕获不一致。
|
||||
|
||||
## 修复
|
||||
|
||||
- 在 Codex 的 reverse mode 下自动注入:
|
||||
- `--disable responses_websockets`
|
||||
- `--disable responses_websockets_v2`
|
||||
- 保留用户意图:若用户已通过 `--enable`、`--disable` 或 `-c/--config features.<name>=...`
|
||||
显式覆盖特性,不再强制覆盖。
|
||||
|
||||
## 验证
|
||||
|
||||
- 增加 E2E 断言:reverse-mode 启动包含 websocket-disable flags。
|
||||
- 增加 E2E 断言:尊重用户显式 feature override。
|
||||
- 运行完整 gate 检查(`ruff`、format check、`pytest tests/ -x --timeout=60`)。
|
||||
|
||||
## 经验
|
||||
|
||||
为了保证 proxy 捕获可靠性,不要假设 Codex 传输永远是 HTTP POST。
|
||||
在 reverse proxy 场景下,应显式将传输特性固定到可捕获路径,并在测试中让覆盖行为具备确定性。
|
||||
@@ -0,0 +1,36 @@
|
||||
# Codex WS 超时且已验证 HTTPS 回退(PR #22)
|
||||
|
||||
**日期:** 2026-03-03
|
||||
**标签:** codex, websocket, reverse-proxy, validation, fallback
|
||||
|
||||
## 背景
|
||||
|
||||
在用真实 Codex 运行验证 PR #22(`feat/ws-proxy`)时,我们需要对 WebSocket 传输行为和当前环境中的回退行为提供硬证据。
|
||||
|
||||
## 发生了什么
|
||||
|
||||
- 通过 `claude_tap --tap-client codex` 的常规 Codex 运行经由 HTTP/SSE(`POST /v1/responses`)成功。
|
||||
- 强制 WS 运行(`--enable responses_websockets` 与 `--enable responses_websockets_v2`)反复连接上游失败:
|
||||
- `502 Bad Gateway`
|
||||
- 连接 `wss://chatgpt.com/backend-api/codex/responses` 超时
|
||||
- 多次重试后,Codex 自动回退到 HTTPS 传输并成功完成该轮。
|
||||
|
||||
## 根因(观察)
|
||||
|
||||
观察到的问题是当前环境中的上游 WS 连接超时,而非 proxy 进程本地崩溃。proxy 正确记录了 WS 失败,client 的 fallback 路径恢复了运行。
|
||||
|
||||
## 证据
|
||||
|
||||
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v1-run.log`
|
||||
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v2-run.log`
|
||||
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v1-run/trace_20260303_180901.jsonl`
|
||||
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v2-run/trace_20260303_180901.jsonl`
|
||||
|
||||
## 经验
|
||||
|
||||
对传输敏感验证,需明确区分三类陈述:
|
||||
1. 已实现行为(代码 + 测试),
|
||||
2. 当前环境中的观察行为,
|
||||
3. 因网络/运行时约束尚未验证的行为。
|
||||
|
||||
这样能避免过度声明,同时仍可在已验证范围内推进合并决策。
|
||||
@@ -0,0 +1,57 @@
|
||||
# PR 截图质量失败案例(2026-03-03)
|
||||
|
||||
## 发生了什么
|
||||
|
||||
PR #22(WebSocket proxy 修复)需要截图证据。在产出可接受截图前,先后出现了三次质量失败:
|
||||
|
||||
### 失败 1:移动端 Viewport 布局
|
||||
- **症状**:Trace viewer 渲染为移动端布局,拥挤、单列、难以阅读
|
||||
- **根因**:OpenClaw 内置浏览器默认窄 viewport(约 750px),触发响应式移动端断点
|
||||
- **影响**:截图展示的是移动端 UI,和用户实际看到的不一致
|
||||
|
||||
### 失败 2:Unicode 箭头乱码
|
||||
- **症状**:日志中的箭头 `→` 与 `←` 在截图里显示为乱码 `鉞@`
|
||||
- **根因**:日志文件包含 Unicode 箭头。headless 环境中的浏览器或字体渲染破坏了多字节字符;原始 `.log` 被直接提供且未处理 charset。
|
||||
- **影响**:关键证据(WS 方向标记)不可读
|
||||
|
||||
### 失败 3:截图内容错误
|
||||
- **症状**:Trace viewer 显示的是 `/v1/models` 请求详情,而非 WebSocket `/v1/responses` 请求
|
||||
- **根因**:截图前没有先点击到正确 trace entry,误以为默认视图就是 WS 请求
|
||||
- **影响**:截图无法证明 PR 声称的内容
|
||||
|
||||
### 元失败:缺少提交前审查
|
||||
- 三张错误截图都在未先审查的情况下被 commit 并 push 到 PR
|
||||
- 用户不得不手动检查并逐个指出问题
|
||||
- 多轮往返才修复本应在 commit 前捕获的问题
|
||||
|
||||
## 如何修复
|
||||
|
||||
1. **Viewport**:截图前执行 `browser act resize width=1440 height=900`
|
||||
2. **Unicode**:创建自定义 HTML 卡片(`ws-log-clean.html`),使用 HTML entities(`>` `<` `->`)替代原始 Unicode 箭头
|
||||
3. **内容**:先导航到正确 trace entry(Turn 2 WEBSOCKET),确认内容后再截图
|
||||
4. **审查**:commit 前逐张肉眼检查截图
|
||||
|
||||
## 形成的标准
|
||||
|
||||
### 截图提交前 Checklist
|
||||
1. **Viewport**:截图前设为桌面宽度(≥1280px)
|
||||
2. **内容**:确认截图展示的内容与声明完全一致
|
||||
3. **编码**:检查是否有乱码/损坏字符,尤其是 Unicode 符号、CJK 文本、emoji
|
||||
4. **布局**:确认渲染为桌面布局(不是移动端响应断点)
|
||||
5. **可读性**:关键证据文本在 1x 缩放下可清晰阅读
|
||||
6. **审查**:`git add` 前查看实际 PNG 文件,禁止盲目提交
|
||||
|
||||
### 自动化机会
|
||||
- `scripts/check_screenshots.sh`:自动检查图像尺寸(拒绝宽度 <1000px,疑似移动端)、文件大小(拒绝 <10KB,疑似错误页)和基础合理性
|
||||
- PR 正文模板可以加入截图 checklist 章节
|
||||
- 可考虑通过固定 viewport 设置程序化生成证据截图
|
||||
|
||||
## 预防
|
||||
|
||||
- 已在 `.agents/docs/standards/e2e-and-evidence.md` 增加截图质量 gate
|
||||
- 已新增 `scripts/check_screenshots.sh` 用于 pre-commit 自动验证
|
||||
- AGENTS.md 应对任何包含视觉证据的 PR 引用该截图标准
|
||||
|
||||
## 关键结论
|
||||
|
||||
截图是证据。证据必须在提交前验证。“我截了图”不等于“我验证了这张图能证明我的结论”。
|
||||
@@ -0,0 +1,43 @@
|
||||
# PR #22 Screenshot Quality Regression (WebSocket Proxy Fix)
|
||||
|
||||
**Date:** 2026-03-03
|
||||
**Tags:** pr, screenshot, viewport, encoding, websocket, review
|
||||
|
||||
## What Happened
|
||||
|
||||
Timeline:
|
||||
|
||||
1. PR #22 (WebSocket proxy fix) required screenshot evidence for the trace viewer output.
|
||||
2. Screenshots were captured in an openclaw browser session that defaulted to a narrow viewport (~750px width).
|
||||
3. The rendered trace log contained raw Unicode arrows (`→`, `←`) and those characters displayed as garbled glyphs in the screenshot.
|
||||
4. The captured screenshot focused on a `GET` request row instead of the `101 WEBSOCKET` row that was the core proof for the fix.
|
||||
5. The screenshots were committed and pushed without a dedicated screenshot quality review step.
|
||||
|
||||
Specific failures:
|
||||
|
||||
- Desktop UI evidence looked like a mobile layout.
|
||||
- Important log symbols were corrupted.
|
||||
- The screenshot did not show the feature being validated.
|
||||
- No pre-commit evidence verification caught the mismatch.
|
||||
|
||||
## Root Causes
|
||||
|
||||
1. **Viewport control gap:** no enforced minimum viewport width for desktop evidence screenshots.
|
||||
2. **Encoding safety gap:** generated HTML used raw Unicode arrows in log rendering instead of ASCII-safe symbols or entities.
|
||||
3. **Content verification gap:** no explicit check that screenshot content matched the exact PR claim (`101 WEBSOCKET` for this fix).
|
||||
4. **Process gap:** no mandatory screenshot checklist before commit/push.
|
||||
|
||||
## Fix Applied
|
||||
|
||||
1. Defined screenshot standards with hard desktop viewport minimum (`>=1280px`).
|
||||
2. Added encoding rule to avoid raw Unicode arrows in generated HTML evidence content.
|
||||
3. Added content verification requirements to confirm screenshots display the specific feature/fix target.
|
||||
4. Added automated validation script (`scripts/check_screenshots.py`) for baseline image quality checks.
|
||||
5. Wired screenshot validation into CI for PR evidence images.
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. Screenshot evidence needs the same validation rigor as tests and linting.
|
||||
2. Visual proof must be scoped to the exact behavior under review, not adjacent activity.
|
||||
3. Encoding decisions in generated artifacts can silently degrade review quality.
|
||||
4. Lightweight automated checks plus a short human checklist prevent most screenshot regressions.
|
||||
@@ -0,0 +1,92 @@
|
||||
# WS Proxy Debugging Failure — Agent Blind Spot
|
||||
|
||||
**Date:** 2026-03-03
|
||||
**Severity:** High (hours wasted, multiple agent iterations, cron loops)
|
||||
**PR:** #22 (WebSocket proxy support)
|
||||
|
||||
## What Happened
|
||||
|
||||
PR #22 added WebSocket proxy support. After implementation, real Codex WS
|
||||
connections kept timing out. Multiple Codex agents were spawned across many
|
||||
iterations — a cron monitoring job ran continuously, spawning more agents,
|
||||
checking status, retrying — but **none** identified the root cause.
|
||||
|
||||
The human user stepped in, asked to check the WS connect code path, and
|
||||
within minutes pinpointed the exact problem.
|
||||
|
||||
## Root Cause (of the Bug)
|
||||
|
||||
`claude_tap/proxy.py` line 379 had `proxy=None` hardcoded in
|
||||
`session.ws_connect()`. This explicitly bypassed the Clash Verge proxy
|
||||
(`http://127.0.0.1:7897`) that all HTTP requests used successfully.
|
||||
|
||||
Direct WSS connections to `wss://chatgpt.com` timed out because the machine
|
||||
required a proxy for external access.
|
||||
|
||||
## Root Cause (of the Debugging Failure)
|
||||
|
||||
### 1. Agents never read the actual code path
|
||||
|
||||
The `ws_connect()` call had `proxy=None` in plain sight. Any agent reading
|
||||
that single line should have asked: "Why is proxy disabled for WS when HTTP
|
||||
requests work fine through the proxy?"
|
||||
|
||||
### 2. Over-reliance on black-box testing
|
||||
|
||||
Agents kept re-running Codex with different flags (`--enable responses_websockets`,
|
||||
`--enable responses_websockets_v2`) and analyzing timeout logs. None traced
|
||||
the code path from the WS upgrade handler → upstream connect → `ws_connect()`
|
||||
call parameters.
|
||||
|
||||
### 3. Cron monitoring loops burned cycles without progress
|
||||
|
||||
A monitoring cron job kept cycling: check status → agent still stuck →
|
||||
spawn more agents → repeat. Many iterations, zero conceptual progress.
|
||||
The loop was busy, not productive.
|
||||
|
||||
### 4. No systematic debugging methodology
|
||||
|
||||
A simple comparison would have solved it in minutes:
|
||||
- **Does HTTP work?** → Yes (through proxy)
|
||||
- **Does WS work?** → No (timeout)
|
||||
- **What's different in the code path?** → HTTP uses session default proxy,
|
||||
WS has explicit `proxy=None`
|
||||
- **That's the bug.**
|
||||
|
||||
## The Fix
|
||||
|
||||
One line: remove `proxy=None` from `session.ws_connect()`. Let
|
||||
`trust_env=True` on the `ClientSession` handle proxy resolution naturally.
|
||||
|
||||
Commit: `fcb2982`
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. **Read the code before running tests.** When debugging proxy issues,
|
||||
trace the actual network call and check what proxy settings are passed.
|
||||
|
||||
2. **Compare working vs non-working paths.** HTTP worked, WS didn't. The
|
||||
diff between those two code paths is exactly where the bug lives.
|
||||
|
||||
3. **Stop the loop early.** If 2-3 iterations of black-box testing haven't
|
||||
found the issue, switch strategy: read code, add logging, trace the call.
|
||||
|
||||
4. **Simple mental models beat brute force.** "Proxy works for A but not B
|
||||
→ B must handle proxy differently" is a 30-second insight. No amount of
|
||||
re-running tests with different flags will discover a hardcoded `proxy=None`.
|
||||
|
||||
5. **Cron loops are not debugging.** Automated retry loops are for monitoring
|
||||
known-good processes, not for solving unknown bugs. When stuck, stop the
|
||||
loop and think.
|
||||
|
||||
## Standards (Added)
|
||||
|
||||
See `.agents/docs/standards/debugging-standards.md` for the full checklist.
|
||||
|
||||
Key additions:
|
||||
- Before spawning agents for debugging: spend 5 minutes reading the relevant
|
||||
code path yourself
|
||||
- When proxy/network issues: always check what `proxy=` parameter is passed
|
||||
in the actual connect call
|
||||
- After 2 failed iterations of the same approach: STOP and change approach
|
||||
- Document "working vs broken" comparison explicitly before diving into fixes
|
||||
@@ -0,0 +1,61 @@
|
||||
# Codex strip_path_prefix URL mismatch
|
||||
|
||||
Date: 2026-03-10
|
||||
|
||||
## What broke
|
||||
|
||||
Codex CLI through claude-tap proxy returned 404 (HTTP) and 502 (WebSocket) for all requests.
|
||||
|
||||
## Root cause
|
||||
|
||||
`strip_path_prefix="/v1"` was set for the codex client, but `default_target` was `https://api.openai.com` (without `/v1`). This caused:
|
||||
|
||||
- Codex sends `/v1/responses`
|
||||
- Proxy strips `/v1` → path becomes `/responses`
|
||||
- Upstream URL: `https://api.openai.com/responses` ← wrong, should be `/v1/responses`
|
||||
|
||||
## What we tried
|
||||
|
||||
### Attempt 1: Remove strip_path_prefix entirely
|
||||
|
||||
Set `strip_path_prefix=""` for all codex clients. This fixed `api.openai.com` (API Key auth) but broke `chatgpt.com/backend-api/codex` (OAuth auth), because that backend expects `/responses` without `/v1`.
|
||||
|
||||
**Why it failed:** Only considered one authentication mode. Codex has two different backends with different URL structures:
|
||||
|
||||
| Backend | Expected path | Needs strip? |
|
||||
|---------|-------------|-------------|
|
||||
| `api.openai.com` | `/v1/responses` | No |
|
||||
| `chatgpt.com/backend-api/codex` | `/responses` | Yes |
|
||||
|
||||
### Attempt 2: Conditional strip based on target URL
|
||||
|
||||
```python
|
||||
"strip_path_prefix": "/v1" if args.client == "codex" and "api.openai.com" not in args.target else ""
|
||||
```
|
||||
|
||||
This correctly handles both backends.
|
||||
|
||||
## What actually fixed it
|
||||
|
||||
The conditional strip (attempt 2), combined with tmux-based real E2E verification against the live chatgpt.com backend.
|
||||
|
||||
## Why the bug wasn't caught originally
|
||||
|
||||
1. **E2E test validated internal consistency, not external correctness.** The fake upstream handler asserted `request.path == "/messages"` (the stripped path), which confirmed stripping worked — but never checked if the constructed upstream URL matched the real API.
|
||||
|
||||
2. **Failure was misattributed to environment.** WS_VERIFY_REPORT.md documented WS 502 failures but concluded "environment/network dependent WS reachability" instead of investigating the URL construction.
|
||||
|
||||
3. **No real-endpoint URL assertion.** No test verified that `target + stripped_path` produces a valid real API URL.
|
||||
|
||||
## Lessons
|
||||
|
||||
1. **For proxy code, test the final upstream URL, not just the forwarded path.** Add assertions like:
|
||||
```python
|
||||
assert upstream_url == "https://api.openai.com/v1/responses"
|
||||
```
|
||||
|
||||
2. **When a client supports multiple backends, enumerate all (client × auth × target) combinations** before modifying URL handling logic. Draw a matrix.
|
||||
|
||||
3. **Don't attribute failures to "environment" without first verifying the code path.** Print/log the actual upstream URL to confirm correctness before blaming network.
|
||||
|
||||
4. **Always run real E2E after proxy changes.** Unit tests with fake upstreams give false confidence for networking code. Use `tmux` to run a real request through the proxy.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Mock E2E 测试模式:Fake Upstream + Fake Claude
|
||||
|
||||
**日期:** 2026-02-25
|
||||
**标签:** testing, E2E, mock, best-practice
|
||||
|
||||
## 模式描述
|
||||
|
||||
现有 E2E 测试套件(`tests/test_e2e.py`)采用了全 mock 方案,
|
||||
可在无外部依赖的情况下测试整个 claude-tap pipeline:
|
||||
|
||||
1. **Fake upstream server** - 在后台线程运行 aiohttp server,
|
||||
模拟 Anthropic API,同时返回 non-streaming 与 streaming(SSE)响应。
|
||||
|
||||
2. **Fake Claude script** - 在 PATH 中放置临时 Python 脚本作为 `claude` CLI。
|
||||
它向 `ANTHROPIC_BASE_URL`(由 claude-tap 设置为 proxy 地址)发送 HTTP 请求并打印结果。
|
||||
|
||||
3. **真实 claude-tap** - 以子进程方式运行真实 `claude_tap` module,
|
||||
并通过 `--tap-target` 指向 fake upstream。
|
||||
|
||||
## 为什么效果好
|
||||
|
||||
- **无外部依赖**:测试可离线运行,无需 API keys
|
||||
- **可确定性**:相同输入始终产生相同输出
|
||||
- **速度快**:无网络时延,无 rate limit
|
||||
- **覆盖完整**:测试全链路,包括 proxy 启动、请求转发、SSE 重组、JSONL 记录、HTML viewer 生成、API key 脱敏
|
||||
- **边界情况稳健**:覆盖上游错误(500)、畸形 SSE、大 payload(100KB+)
|
||||
|
||||
## 关键实现细节
|
||||
|
||||
- `run_fake_upstream_in_thread()` 使用 `threading.Event` 做同步
|
||||
- Fake Claude 脚本由 `_create_fake_claude()` 创建并设为可执行
|
||||
- 临时 bin 目录会 prepend 到 `PATH`,让 claude-tap 找到 fake `claude`
|
||||
- 端口硬编码(19199、19200 等)保持测试隔离
|
||||
- Trace 文件写入 `tempfile.mkdtemp()`,断言后清理
|
||||
|
||||
## 何时使用此模式
|
||||
|
||||
适用于:
|
||||
- 测试 proxy 行为(转发、记录、SSE 处理)
|
||||
- 测试 HTML viewer 生成
|
||||
- 测试 header 脱敏与安全能力
|
||||
- 在无 Claude API 访问的 CI 中运行
|
||||
|
||||
## 互补模式
|
||||
|
||||
若要测试真实 Claude 集成(实际 API 响应、tool use、多轮对话),
|
||||
请参考 `tests/e2e/` 下的 real E2E 测试。它们需要可用的 `claude` CLI 安装,
|
||||
且在 CI 中默认跳过。
|
||||
@@ -0,0 +1,43 @@
|
||||
# 面向 Proxy 的 E2E 与 CI 强化
|
||||
|
||||
**日期:** 2026-02-25
|
||||
**标签:** e2e, proxy, oauth, ci, reliability
|
||||
|
||||
## 背景
|
||||
|
||||
真实 E2E 覆盖曾被环境相关行为阻塞:
|
||||
|
||||
- forward-proxy 运行中的 OAuth 流程出现 `403 Request not allowed`。
|
||||
- CI 在 Python 3.13 下因 forward proxy 测试中的 TLS 证书校验更严格而失败。
|
||||
- 测试预期误以为首个 API 调用总是 `/v1/messages`,但 OAuth 预检并非如此。
|
||||
|
||||
## 有效做法
|
||||
|
||||
1. **上游转发尊重系统 proxy env**
|
||||
- 在 claude-tap 上游转发使用的 aiohttp session 中设置 `trust_env=True`。
|
||||
- 使流量能遵循本地 proxy/VPN 工具(如 Clash),而不是绕过它。
|
||||
|
||||
2. **提升 forward/OAuth 断言鲁棒性**
|
||||
- 不假设首条 trace 请求是 `/v1/messages`。
|
||||
- 在记录中搜索至少一条 `/v1/messages` 调用,并在该处验证响应内容。
|
||||
|
||||
3. **为 Python 3.13 强化测试证书**
|
||||
- 在自签测试证书中同时添加 `SubjectKeyIdentifier` 与 `AuthorityKeyIdentifier` 扩展。
|
||||
- 解决 CI 中 `CERTIFICATE_VERIFY_FAILED: Missing Authority Key Identifier`。
|
||||
|
||||
4. **修复 trace 统计的空值安全**
|
||||
- 在收集 model usage 指标时防护 `request.body is None`。
|
||||
|
||||
## 为什么重要
|
||||
|
||||
- 真实 E2E 可在 proxy 重环境的开发机中通过,避免隐藏的网络路径不一致。
|
||||
- CI 在不同 Python 版本间更稳定。
|
||||
- 测试验证的是行为本身,而不是偶然的请求顺序。
|
||||
|
||||
## 运行说明
|
||||
|
||||
- 真实 E2E 仍通过 `--run-real-e2e` opt-in。
|
||||
- Browser integration 测试需要安装 Playwright 与浏览器二进制。
|
||||
- 调试 proxy 问题时,两个链路都要验证:
|
||||
- Claude CLI -> claude-tap
|
||||
- claude-tap -> upstream(必要时必须遵守 proxy env)
|
||||
@@ -0,0 +1,40 @@
|
||||
# tmux 真实 E2E 成功模式
|
||||
|
||||
**日期:** 2026-02-26
|
||||
**标签:** e2e, tmux, testing, verification
|
||||
|
||||
## 问题
|
||||
|
||||
tmux `send-keys` 在真实交互式 E2E 运行中,无法稳定向 Claude Code TUI 提交 prompt。
|
||||
|
||||
## 根因
|
||||
|
||||
- 部分流程假设 `rg` 可用于断言,但在一些环境中并不存在。
|
||||
- 对 Claude Code TUI 在 tmux 下的提交行为建模错误;正确提交键是 `Enter`。
|
||||
|
||||
## 解决方案
|
||||
|
||||
- 将脆弱的 `rg` 断言替换为可移植的 `grep -F`。
|
||||
- 将默认提交行为统一为 `Enter`。
|
||||
- 加入“未命中重试提交”逻辑,降低瞬时输入时序失败。
|
||||
|
||||
## 验证
|
||||
|
||||
通过 JSONL 断言验证:
|
||||
|
||||
1. 两个 prompt 都出现在 trace 数据中。
|
||||
2. `/v1/messages` 调用至少为 2 次。
|
||||
3. 至少一个响应内容块为 `tool_use`。
|
||||
4. 生成了 HTML viewer 产物。
|
||||
|
||||
## 结果
|
||||
|
||||
- pytest real E2E 用例 `7/7` 通过。
|
||||
- tmux 交互式 E2E 通过,且确认捕获到 `tool_use`。
|
||||
- 生成了 asciinema 录制。
|
||||
- 已截取生成的 HTML viewer browser 截图。
|
||||
|
||||
## 经验
|
||||
|
||||
在 tmux 下做真实 Claude TUI 自动化时,可移植性与输入语义比“巧工具”更重要:
|
||||
优先 `grep -F`,使用 `Enter`,并通过 trace 产物验证。
|
||||
@@ -0,0 +1,25 @@
|
||||
# UI 修复交付模式:Sticky Header + 证据优先验证
|
||||
|
||||
**日期:** 2026-02-27
|
||||
**标签:** ui, viewer, testing, workflow, pr
|
||||
|
||||
## 背景
|
||||
|
||||
一个小型 UI 行为修复(viewer detail panel 的操作控件 sticky)需要以可靠证据交付,便于 review。
|
||||
|
||||
## 有效做法
|
||||
|
||||
1. 先应用最小化、仅 CSS 的修复,不改动 JS 行为。
|
||||
2. 提前运行快速质量 gate(`ruff` + `pytest`)以快速发现回归。
|
||||
3. 产出可视化证据(滚动前/后状态)供 PR review。
|
||||
4. 在 `AGENTS.md` 添加明确流程要求,确保未来 UI 变更始终附带 E2E 验证与截图。
|
||||
|
||||
## 为什么这个模式好
|
||||
|
||||
- 低风险:改动面很小。
|
||||
- review 清晰度高:截图可直接证明行为。
|
||||
- 流程可复用:同样流程可应用于后续 UI 变更。
|
||||
|
||||
## 经验
|
||||
|
||||
对 UI 行为修复,证据是交付物的一部分。好的 PR 不只是“代码 + 测试”,还包括“可视化证明 + 明确验证步骤”。
|
||||
@@ -0,0 +1,130 @@
|
||||
# 工程实践指南
|
||||
|
||||
本文档对 claude-tap 项目的工程标准进行规范化。
|
||||
|
||||
## Python 代码风格
|
||||
|
||||
- **Linter/formatter:** [ruff](https://docs.astral.sh/ruff/)
|
||||
- **行长限制:** 120 字符
|
||||
- **目标版本:** Python 3.11+
|
||||
- **Lint 规则:** `E`(errors)、`F`(pyflakes)、`W`(warnings)、`I`(import sorting)
|
||||
- **忽略规则:** `E501`(行长,由 formatter 而非 lint 强制)
|
||||
|
||||
本地运行:
|
||||
```bash
|
||||
uv run ruff check . # Lint
|
||||
uv run ruff format --check . # Check formatting
|
||||
uv run ruff format . # Auto-fix formatting
|
||||
```
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 测试分层
|
||||
|
||||
| Layer | Location | 测试内容 | 在 CI 中运行 | 外部依赖 |
|
||||
|-------|----------|----------|--------------|----------|
|
||||
| **Unit** | `tests/test_diff_matching.py` | 纯逻辑(diff matching、parsing) | 是 | 无 |
|
||||
| **Mock E2E** | `tests/test_e2e.py` | fake upstream + fake Claude 的完整 pipeline | 是 | 无 |
|
||||
| **Browser integration** | `tests/test_nav_browser.py` | HTML viewer JavaScript 逻辑 | 是(使用 Playwright) | Playwright |
|
||||
| **Real E2E** | `tests/e2e/` | 真实 Claude CLI 集成 | 否(opt-in) | Claude CLI、API key |
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# Full CI suite (unit + mock E2E)
|
||||
uv run pytest tests/ -x --timeout=60
|
||||
|
||||
# Specific test file
|
||||
uv run pytest tests/test_e2e.py -x --timeout=120
|
||||
|
||||
# Real E2E tests (requires claude CLI)
|
||||
uv run pytest tests/e2e/ --run-real-e2e --timeout=300
|
||||
|
||||
# All tests including real E2E
|
||||
uv run pytest tests/ --run-real-e2e --timeout=300
|
||||
```
|
||||
|
||||
### 编写新测试
|
||||
|
||||
- 使用 `pytest` fixtures 做 setup/teardown(见 `conftest.py`)
|
||||
- 用 `tempfile.mkdtemp()` 创建临时目录,并始终清理
|
||||
- 对 async 测试,使用 `pytest-asyncio`(配置为 `asyncio_mode = "auto"`)
|
||||
- 慢测试标记为 `@pytest.mark.slow`
|
||||
- integration 测试标记为 `@pytest.mark.integration`
|
||||
|
||||
## Commit 约定
|
||||
|
||||
- commit message 使用英文
|
||||
- 使用祈使语气:“add feature” 而非 “added feature”
|
||||
- 使用类型前缀:`feat:`、`fix:`、`refactor:`、`test:`、`docs:`、`chore:`
|
||||
- subject 行保持在 72 字符以内
|
||||
- 对非简单变更,在 body 解释“为什么”
|
||||
|
||||
示例:
|
||||
```
|
||||
feat: add --tap-host flag for custom bind address
|
||||
fix: handle malformed SSE events without crashing
|
||||
test: add real E2E tests with Claude CLI integration
|
||||
docs: update engineering practices guide
|
||||
```
|
||||
|
||||
## Pre-Work Checklist
|
||||
|
||||
进行任何代码变更之前:
|
||||
|
||||
1. **检查仓库状态:**
|
||||
```bash
|
||||
git diff --stat
|
||||
git log --oneline -10
|
||||
```
|
||||
2. **确保工作树干净**,或先 stash 变更
|
||||
3. **拉取最新 main:** `git fetch origin && git rebase origin/main`
|
||||
4. **确认测试通过:** `uv run pytest tests/ -x --timeout=60`
|
||||
|
||||
## Feature 的 Worktree 工作流
|
||||
|
||||
使用 git worktree 进行隔离的 feature 开发:
|
||||
|
||||
```bash
|
||||
# Create worktree for new feature
|
||||
git worktree add -b feat/my-feature /tmp/claude-tap-my-feature main
|
||||
|
||||
# Work in the worktree
|
||||
cd /tmp/claude-tap-my-feature
|
||||
# ... make changes, run tests ...
|
||||
|
||||
# Merge back (fast-forward only)
|
||||
cd /path/to/claude-tap
|
||||
git merge --ff-only feat/my-feature
|
||||
|
||||
# Clean up
|
||||
git worktree remove /tmp/claude-tap-my-feature
|
||||
git branch -d feat/my-feature
|
||||
```
|
||||
|
||||
收益:
|
||||
- 主 worktree 保持干净
|
||||
- 无需 stash 即可在多个 feature 间切换
|
||||
- 自然隔离可防止相互污染
|
||||
|
||||
## Code Review 流程
|
||||
|
||||
commit 前:
|
||||
|
||||
1. **Lint:** `uv run ruff check .`
|
||||
2. **Format:** `uv run ruff format --check .`
|
||||
3. **Test:** `uv run pytest tests/ -x --timeout=60`
|
||||
4. **Review diff:** `git diff`,逐行阅读每个变更
|
||||
5. **验证范围:** 仅修改了与任务相关的文件吗?
|
||||
|
||||
合并 PR 前:
|
||||
|
||||
1. 所有 CI 检查通过
|
||||
2. 无未解决 review 评论
|
||||
3. 分支已 rebase 到最新 `main`
|
||||
4. `uv.lock` 保持一致
|
||||
|
||||
## 语言
|
||||
|
||||
代码、注释、commit message 和 skill 文件必须使用英文。
|
||||
对外文档必须中英文双份:`README.md` 对应 `README_zh.md`,`docs/guides/*.md` 对应同目录的 `*.zh.md`。
|
||||
@@ -0,0 +1,190 @@
|
||||
# Playbook:为 claude-tap 添加新的 LLM Client
|
||||
|
||||
提炼自 Codex 集成(PR #12,2026-02-28)。将其作为可复用框架,用于为任意新的 LLM client(如 Gemini CLI、Grok CLI 等)添加支持。
|
||||
|
||||
---
|
||||
|
||||
## 第 1 阶段:侦察 - 理解 Client 的 Wire Protocol
|
||||
|
||||
在写代码前,先回答以下问题:
|
||||
|
||||
1. **Client 调用的是哪个 API endpoint?**(例如 `api.openai.com/v1/responses`、
|
||||
`api.anthropic.com/v1/messages`)
|
||||
2. **它是否有备用 endpoint?**(例如 Codex 对 ChatGPT Plus 用户使用 `chatgpt.com/backend-api/codex`,而不是 `api.openai.com`)
|
||||
3. **它使用什么传输方式?** HTTP POST?WebSocket?gRPC?
|
||||
- **Codex 经验**:Codex v0.106.0 在无提示情况下把 `/v1/responses` 从 HTTP 切到 WebSocket。HTTP proxy 看不到任何内容。务必验证实际 wire transport,而不是只看文档。
|
||||
4. **哪个 env var 控制 base URL?**(`OPENAI_BASE_URL`、`ANTHROPIC_BASE_URL` 等)
|
||||
5. **子进程是否真的继承了该 env var?**(Codex 有 Rust 子进程,可能会或可能不会遵循 Node.js 父进程环境)
|
||||
6. **它使用何种编码/压缩?**(Codex 会发送 zstd 压缩体)
|
||||
|
||||
### 如何调查
|
||||
|
||||
```bash
|
||||
# Watch actual network traffic
|
||||
lsof -i -P | grep <process_name>
|
||||
|
||||
# Check what the process sees
|
||||
ps -p <pid> -E | tr ' ' '\n' | grep BASE_URL
|
||||
|
||||
# Intercept with mitmproxy for full visibility
|
||||
mitmproxy --mode reverse:https://api.example.com --listen-port 8080
|
||||
```
|
||||
|
||||
**关键原则**:不要盲信文档。观察真实行为。
|
||||
|
||||
---
|
||||
|
||||
## 第 2 阶段:Proxy Wiring - 让每个请求都可见
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] 设置正确的 env var,将流量重定向到 claude-tap 本地 proxy
|
||||
- [ ] 处理路径映射(client 发送 `/v1/responses`,upstream 期望 `/responses`)
|
||||
- [ ] 处理请求体编码(zstd、gzip 等)
|
||||
- [ ] 处理响应流(SSE events、chunked transfer)
|
||||
- [ ] **若使用 reverse proxy,将传输固定为 HTTP** - 禁用会绕过 HTTP proxy 的 WebSocket/gRPC 特性
|
||||
- [ ] 用真实流量验证(不仅是 unit tests)
|
||||
|
||||
### 验证检查点
|
||||
|
||||
```bash
|
||||
# Run the client through claude-tap
|
||||
uv run python -m claude_tap --tap-client <name> -- <simple prompt>
|
||||
|
||||
# Check trace file has actual API calls (not just /models)
|
||||
wc -l .traces/trace_*.jsonl # Should be > 1
|
||||
python3 -c "
|
||||
import json
|
||||
with open('.traces/trace_<latest>.jsonl') as f:
|
||||
for line in f:
|
||||
d = json.loads(line)
|
||||
print(d['request']['path'], d['response']['status'])
|
||||
"
|
||||
```
|
||||
|
||||
**如果 trace 只有 1 行(models/health check),说明真实 API 调用绕过了你的 proxy。** 立即停止并排查传输路径。
|
||||
|
||||
---
|
||||
|
||||
## 第 3 阶段:Viewer 兼容性 - 每种 API 格式都不同
|
||||
|
||||
每个 LLM provider 的响应格式都不同。需要映射这些字段:
|
||||
|
||||
| Concept | Claude (Chat Completions) | OpenAI (Responses API) | Your Client |
|
||||
|---------|--------------------------|----------------------|-------------|
|
||||
| System prompt | `body.system` | `body.instructions` | ? |
|
||||
| Messages | `body.messages[]` | `body.input[]` | ? |
|
||||
| Message content | `{type: "text", text}` | `{type: "input_text", text}` | ? |
|
||||
| Token usage | `response.body.usage` | SSE `response.completed` event | ? |
|
||||
| Response output | `response.body.content` | SSE `response.output_text.delta` | ? |
|
||||
| Tools | `body.tools[]` | `body.tools[]` | ? |
|
||||
|
||||
### Viewer 修复模式
|
||||
|
||||
1. 找出 viewer 读取 Claude 特定字段的所有位置
|
||||
2. 新增 normalize 函数,把新格式映射到通用结构
|
||||
3. 保持向后兼容,旧 trace 仍要能正确渲染
|
||||
|
||||
### 验证检查点
|
||||
|
||||
用真实 trace 打开 HTML viewer 并验证:
|
||||
- [ ] System prompt 已显示
|
||||
- [ ] Messages 以正确 role 渲染
|
||||
- [ ] Token 计数非零
|
||||
- [ ] Turn 间 diff 可用
|
||||
- [ ] Response output 已显示
|
||||
|
||||
---
|
||||
|
||||
## 第 4 阶段:录制 - 证据是交付物的一部分
|
||||
|
||||
每次新增 client 集成都需要:
|
||||
|
||||
1. **终端录制**:展示 client 通过 claude-tap 运行的真实 E2E 会话
|
||||
- 工具:`asciinema rec` → `agg`(GIF)→ `ffmpeg`(MP4)
|
||||
- 必须展示:启动 banner、至少 2-3 轮对话、如适用的 tool calls
|
||||
|
||||
2. **Viewer 录制**:通过 Playwright 自动化演示 HTML viewer
|
||||
- 工具:Playwright `record_video_dir` → `.webm` → `ffmpeg`(MP4)
|
||||
- 必须展示:system prompt、messages、tokens、diff
|
||||
|
||||
3. **截图**:用于 PR review 的静态证据
|
||||
- 至少包括:overview、messages、diff、token stats
|
||||
|
||||
**使用真实 trace 数据,不要用 mock。** reviewer 一眼就能看出来。
|
||||
|
||||
### PR 截图陷阱
|
||||
|
||||
PR 描述中使用绝对 URL(`raw.githubusercontent.com/...`),不要用相对路径。
|
||||
GitHub PR 正文不会解析相对图片路径。
|
||||
|
||||
---
|
||||
|
||||
## 第 5 阶段:防御式设计 - 下一次会坏在哪里?
|
||||
|
||||
集成跑通后,提前预判未来的破坏点:
|
||||
|
||||
- **Client 更新传输方式**:显式固定传输,不要假设永远是 HTTP。
|
||||
记录 workaround,并为原生支持创建 TODO。
|
||||
- **Client 修改 API 格式**:将 normalize 函数隔离,便于后续更新。
|
||||
- **Client 新增 auth 复杂性**:在 plan 文档记录所需 scopes/permissions。
|
||||
|
||||
### 始终留下这些资产
|
||||
|
||||
- [ ] 遇到非显而易见问题时,写 error experience 文档
|
||||
- [ ] 已知限制写 TODO/plan 文档(例如 WebSocket 原生支持)
|
||||
- [ ] 补能覆盖你发现的特定失败模式的测试
|
||||
|
||||
---
|
||||
|
||||
## 反模式(来自 Codex 集成)
|
||||
|
||||
| Anti-pattern | 发生了什么 | 经验 |
|
||||
|-------------|-----------|------|
|
||||
| 相信“看起来明显”的 API endpoint | 以为是 `api.openai.com`,实际是 `chatgpt.com/backend-api/codex` | 必须追踪真实网络调用 |
|
||||
| 假设传输一定是 HTTP | Codex 默认使用 WebSocket,proxy 什么都看不到 | 验证 wire transport,必要时固定 |
|
||||
| 在数据修复前录制演示 | 首版录制中 token 为 0(trace 中有 403 错误) | 先修数据链路,最后再录制 |
|
||||
| 截图使用旧 trace 数据 | 截图仍显示修复前 trace 的错误 | 修复后必须重新录制 |
|
||||
| PR 正文使用相对图片路径 | GitHub 上所有图片都坏掉 | 使用 `raw.githubusercontent.com` 绝对 URL |
|
||||
|
||||
---
|
||||
|
||||
## 模板:新 Client Checklist
|
||||
|
||||
```markdown
|
||||
## Adding support for: <CLIENT_NAME>
|
||||
|
||||
### Recon
|
||||
- [ ] Identified API endpoint(s)
|
||||
- [ ] Identified transport (HTTP/WS/gRPC)
|
||||
- [ ] Identified env var for base URL
|
||||
- [ ] Verified child process inherits env var
|
||||
- [ ] Identified request encoding
|
||||
|
||||
### Proxy
|
||||
- [ ] Env var injection working
|
||||
- [ ] Path mapping correct
|
||||
- [ ] Request decompression working
|
||||
- [ ] Response streaming captured
|
||||
- [ ] Transport pinned to HTTP (if reverse proxy)
|
||||
- [ ] Trace file has real API calls (not just models)
|
||||
|
||||
### Viewer
|
||||
- [ ] System prompt displayed
|
||||
- [ ] Messages rendered
|
||||
- [ ] Token counts non-zero
|
||||
- [ ] Diff working
|
||||
- [ ] Response output shown
|
||||
- [ ] Old Claude traces still work
|
||||
|
||||
### Evidence
|
||||
- [ ] Terminal recording (≥3 turns)
|
||||
- [ ] Viewer recording
|
||||
- [ ] Screenshots (≥5)
|
||||
- [ ] PR description with absolute image URLs
|
||||
|
||||
### Docs
|
||||
- [ ] Error experience (if applicable)
|
||||
- [ ] TODO for known limitations
|
||||
- [ ] Checklist template used
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
# Viewer UX 设计依据
|
||||
|
||||
本指南记录了稳定的设计决策,这些决策此前分散在一次性的实现规格中。
|
||||
|
||||
## 导航顺序
|
||||
|
||||
键盘与触控导航必须遵循侧边栏展示的同一视觉顺序。
|
||||
|
||||
- 侧边栏的分组与排序定义了用户预期的导航顺序。
|
||||
- `j/k` 与方向键导航应按 DOM 顺序在可见项间移动。
|
||||
- 移动端上一条/下一条控件应与桌面键盘导航保持相同顺序。
|
||||
|
||||
## Mobile-First 约束
|
||||
|
||||
viewer 在窄屏下必须保持可用,且不能出现水平溢出。
|
||||
|
||||
- 移动端布局应优先一次只显示一个主 pane。
|
||||
- detail view 的操作区需要触控友好的控件和清晰边界。
|
||||
- diff 与内容密集视图在移动端应切换为堆叠布局。
|
||||
|
||||
## Diff 匹配语义
|
||||
|
||||
diff 质量取决于是否比较了相关 turn。
|
||||
|
||||
- 优先使用带历史感知的匹配(共享消息前缀或等价线程信号)。
|
||||
- 如果 fallback 匹配是近似的,应在 UI 中明确给出警告。
|
||||
- 允许手动选择目标,使用户可以覆盖自动匹配结果。
|
||||
|
||||
## 国际化
|
||||
|
||||
新增面向用户的 UI 文案必须保持一致的本地化策略。
|
||||
|
||||
- 所有字符串都要经过翻译层。
|
||||
- 增加新 key 时保持 language pack 完整。
|
||||
|
||||
## 测试与范围纪律
|
||||
|
||||
viewer UX 相关工作应保持聚焦且可验证。
|
||||
|
||||
- 可行时优先在 `claude_tap/viewer.html` 中做受限改动。
|
||||
- 在改进移动端 UX 的同时保持桌面行为稳定。
|
||||
- 通过测试与手动 trace-viewer 验证来确认行为。
|
||||
@@ -0,0 +1,243 @@
|
||||
---
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Codex Support Handoff (Detailed)
|
||||
|
||||
Date: 2026-02-27
|
||||
Branch: `feat/codex-client-support`
|
||||
Repository: `liaohch3/claude-tap`
|
||||
|
||||
## 1. Goal, Scope, and Current Status
|
||||
|
||||
### Original Goal
|
||||
|
||||
Add support for Codex in addition to Claude, while preserving existing Claude behavior.
|
||||
|
||||
### Acceptance Constraint From User
|
||||
|
||||
Real Codex validation must be successful end-to-end. Any `403` is considered a hard failure.
|
||||
|
||||
### Current Status
|
||||
|
||||
- Core implementation for `--tap-client codex` is completed.
|
||||
- Mock/unit/integration tests are passing locally.
|
||||
- Real Codex run is still blocked by account/API permissions (`Missing scopes: api.model.read`) and upstream behavior in this environment.
|
||||
- Work is committed on branch `feat/codex-client-support`.
|
||||
|
||||
## 2. What Was Done (and Why)
|
||||
|
||||
### A. CLI and Runtime Behavior
|
||||
|
||||
#### File: `claude_tap/cli.py`
|
||||
|
||||
Changes made:
|
||||
|
||||
- Added client selection support:
|
||||
- New flag `--tap-client` with values `claude|codex`, default `claude`.
|
||||
- Extended launch path so we can run either `claude` or `codex`.
|
||||
- Reverse proxy env injection is now client-specific:
|
||||
- Claude: `ANTHROPIC_BASE_URL=http://127.0.0.1:<port>`
|
||||
- Codex: `OPENAI_BASE_URL=http://127.0.0.1:<port>/v1`
|
||||
- `--tap-target` default is now derived from client when omitted:
|
||||
- Claude -> `https://api.anthropic.com`
|
||||
- Codex -> `https://api.openai.com`
|
||||
- Updated user-facing startup/shutdown messages to reflect selected client.
|
||||
- Preserved existing Claude forward-mode behavior, including `--settings` injection path only for Claude.
|
||||
|
||||
Reason:
|
||||
|
||||
- Existing implementation was Claude-only and hardcoded around Anthropic variables.
|
||||
- Codex requires OpenAI-style base URL behavior in reverse mode.
|
||||
- Keeping default `claude` preserves backward compatibility.
|
||||
|
||||
### B. Trace Model Enhancement for Viewer
|
||||
|
||||
#### File: `claude_tap/proxy.py`
|
||||
|
||||
Changes made:
|
||||
|
||||
- Added `upstream_base_url` into each trace record via `_build_record(...)`.
|
||||
- Threaded `upstream_base_url` through streaming and non-streaming handlers.
|
||||
- Simplified upstream encoding behavior by forcing `Accept-Encoding: identity` to avoid zstd-related client incompatibilities in this environment.
|
||||
|
||||
Reason:
|
||||
|
||||
- Viewer copy-curl was hardcoded to Anthropic domain; needed source-specific upstream reconstruction.
|
||||
- Codex and some responses exhibited zstd decode failures in environment; identity encoding reduces this proxy-side compatibility risk.
|
||||
|
||||
### C. Forward Proxy Consistency
|
||||
|
||||
#### File: `claude_tap/forward_proxy.py`
|
||||
|
||||
Changes made:
|
||||
|
||||
- Also force `Accept-Encoding: identity` when forwarding upstream requests.
|
||||
|
||||
Reason:
|
||||
|
||||
- Keep behavior consistent with reverse proxy path and reduce compression-related failures.
|
||||
|
||||
### D. Viewer Behavior
|
||||
|
||||
#### File: `claude_tap/viewer.html`
|
||||
|
||||
Changes made:
|
||||
|
||||
- `copyCurl(...)` now uses `entry.upstream_base_url` when available.
|
||||
- Falls back to `https://api.anthropic.com` for legacy traces.
|
||||
|
||||
Reason:
|
||||
|
||||
- Makes generated curl command accurate for Codex/OpenAI traces.
|
||||
- Maintains backward compatibility for existing old trace files.
|
||||
|
||||
### E. Test Coverage Updates
|
||||
|
||||
#### File: `tests/test_e2e.py`
|
||||
|
||||
Changes made:
|
||||
|
||||
- Extended `_run_claude_tap(...)` helper with `tap_client` argument.
|
||||
- `test_parse_args` now validates Codex defaults:
|
||||
- `--tap-client codex`
|
||||
- default target -> `https://api.openai.com`
|
||||
- Added `test_codex_client_reverse_proxy` with fake `codex` executable:
|
||||
- fake codex uses `OPENAI_BASE_URL`
|
||||
- fake upstream expects `/v1/messages`
|
||||
- asserts trace contains expected path/model
|
||||
- asserts `upstream_base_url` is recorded
|
||||
- asserts startup output includes `OPENAI_BASE_URL=...`
|
||||
|
||||
Reason:
|
||||
|
||||
- Validate new client behavior without depending on real external credentials.
|
||||
- Ensure no regressions in argument parsing and runtime wiring.
|
||||
|
||||
### F. Planning Docs
|
||||
|
||||
#### File: `.agents/docs/plans/2026-02-27-codex-support-plan.md`
|
||||
|
||||
Changes made:
|
||||
|
||||
- Added a scoped implementation plan and explicit acceptance/risk notes.
|
||||
|
||||
Reason:
|
||||
|
||||
- Follow repository guidance for plan documentation and clear handoff context.
|
||||
|
||||
## 3. What Was Not Done (or Not Fully Done)
|
||||
|
||||
### A. Real Codex E2E Success
|
||||
|
||||
Not achieved yet due environment/account limitations.
|
||||
|
||||
Observed blocker:
|
||||
|
||||
- Codex model refresh request failed:
|
||||
- `GET /v1/models?client_version=...`
|
||||
- `403 Forbidden`
|
||||
- message includes `Missing scopes: api.model.read`
|
||||
|
||||
Implication:
|
||||
|
||||
- Per user requirement, this means real validation is not complete.
|
||||
|
||||
### B. Dedicated `tests/e2e/` Real Codex Suite
|
||||
|
||||
Not added yet.
|
||||
|
||||
Reason:
|
||||
|
||||
- Given hard requirement that real run must succeed, adding real tests now would produce consistent failures in this environment until permissions are fixed.
|
||||
|
||||
### C. README / README_zh User-Facing Docs
|
||||
|
||||
Not updated in this round.
|
||||
|
||||
Reason:
|
||||
|
||||
- Priority was code path and correctness first.
|
||||
- Follow-up should document new `--tap-client` behavior and examples.
|
||||
|
||||
## 4. Exact Test Execution and Results
|
||||
|
||||
The following were run locally and passed:
|
||||
|
||||
- `uv run ruff check .`
|
||||
- `uv run ruff format --check .`
|
||||
- `uv run pytest tests/ -x --timeout=60`
|
||||
- Result: `48 passed, 18 skipped`
|
||||
|
||||
Additional targeted tests run:
|
||||
|
||||
- `uv run pytest tests/test_e2e.py -k "test_parse_args or test_codex_client_reverse_proxy" -x --timeout=120`
|
||||
- Passed
|
||||
- `uv run pytest tests/test_e2e.py -k "test_e2e or test_forward_proxy_connect or test_codex_client_reverse_proxy" -x --timeout=120`
|
||||
- Passed
|
||||
|
||||
Real Codex smoke run attempted and failed (as expected in this env):
|
||||
|
||||
- Command pattern:
|
||||
- `uv run python -m claude_tap --tap-client codex --tap-target https://api.openai.com --tap-no-update-check --tap-no-open -- exec "Reply with exactly: CODEX_REAL_OK" --skip-git-repo-check --json`
|
||||
- Failure indicators:
|
||||
- `403 Forbidden` with missing `api.model.read`
|
||||
- Codex exits non-zero
|
||||
|
||||
## 5. Working Tree Hygiene / Commit Scope
|
||||
|
||||
Important context:
|
||||
|
||||
- There was a pre-existing `uv.lock` modification before this work.
|
||||
- `log/` contains runtime artifacts and is untracked.
|
||||
- These were intentionally excluded from commit scope.
|
||||
|
||||
Files intended for this task commit:
|
||||
|
||||
- `claude_tap/cli.py`
|
||||
- `claude_tap/proxy.py`
|
||||
- `claude_tap/forward_proxy.py`
|
||||
- `claude_tap/viewer.html`
|
||||
- `tests/test_e2e.py`
|
||||
- `.agents/docs/plans/2026-02-27-codex-support-plan.md`
|
||||
- `.agents/docs/plans/2026-02-27-codex-support-handoff.md` (this file)
|
||||
|
||||
## 6. Recommended Next Actions for the Next Codex Process
|
||||
|
||||
### 1) Resolve Real Credential/Scope Blocker First
|
||||
|
||||
- Ensure API key/project/org has `api.model.read` (and required response scopes).
|
||||
- Re-run real Codex smoke command to confirm non-403.
|
||||
|
||||
### 2) Add Real Codex E2E Coverage
|
||||
|
||||
Suggested additions:
|
||||
|
||||
- New test module under `tests/e2e/` for Codex real runs.
|
||||
- Cover at least:
|
||||
- single turn success
|
||||
- multi-turn continuity
|
||||
- trace generation and path assertions
|
||||
- Keep `403/401` as hard fail per user rule.
|
||||
|
||||
### 3) Update User Docs
|
||||
|
||||
- Update `README.md` and optionally `README_zh.md` with:
|
||||
- `--tap-client codex`
|
||||
- reverse mode example for Codex
|
||||
- known prerequisite: proper OpenAI scopes
|
||||
|
||||
### 4) Optional: Investigate zstd error path deeper
|
||||
|
||||
- Even after identity preference from proxy to upstream, Codex may still log zstd decode issues in failure scenarios.
|
||||
- Verify whether this is tied to non-proxied traffic, fallback channels, or specific Codex internal endpoints.
|
||||
|
||||
## 7. Quick Technical Summary for Next Agent Prompt
|
||||
|
||||
If you need an abbreviated bootstrap prompt for another Codex process:
|
||||
|
||||
- Branch: `feat/codex-client-support`
|
||||
- Core feature done: `--tap-client codex` + OpenAI reverse mode env wiring.
|
||||
- Tests pass locally (`48 passed, 18 skipped`) for `tests/`.
|
||||
- Real Codex still blocked by `403 missing scopes: api.model.read`.
|
||||
- Continue by fixing credentials/scopes and adding real Codex E2E tests + README updates.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Codex Support Plan
|
||||
|
||||
Date: 2026-02-27
|
||||
|
||||
## Goal
|
||||
|
||||
Add first-class Codex support to `claude-tap` while keeping Claude behavior fully backward compatible.
|
||||
|
||||
## Scope
|
||||
|
||||
- Add client selection: `--tap-client {claude,codex}`.
|
||||
- Keep default client as `claude`.
|
||||
- For `codex` in reverse proxy mode, inject `OPENAI_BASE_URL=http://127.0.0.1:<port>/v1`.
|
||||
- Keep existing Claude behavior: `ANTHROPIC_BASE_URL=http://127.0.0.1:<port>`.
|
||||
- Set default upstream target by client when `--tap-target` is omitted:
|
||||
- `claude` -> `https://api.anthropic.com`
|
||||
- `codex` -> `https://api.openai.com`
|
||||
- Preserve existing forward proxy behavior.
|
||||
- Add trace metadata `upstream_base_url` to improve viewer cURL reconstruction.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Rename package/project branding (`claude-tap`) in this change.
|
||||
- Add broad provider abstractions beyond Claude/Codex.
|
||||
- Guarantee Codex ChatGPT login flow under forward proxy mode.
|
||||
|
||||
## Test Strategy
|
||||
|
||||
- Unit / mock E2E regression:
|
||||
- `uv run pytest tests/test_e2e.py -x --timeout=120`
|
||||
- New Codex mock E2E:
|
||||
- fake `codex` binary + fake upstream API
|
||||
- assert reverse-mode request path and `upstream_base_url` trace field
|
||||
- Repo gate checks:
|
||||
- `uv run ruff check .`
|
||||
- `uv run ruff format --check .`
|
||||
- `uv run pytest tests/ -x --timeout=60`
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Claude default workflow unchanged.
|
||||
- `--tap-client codex` launches `codex` and writes valid trace output.
|
||||
- Viewer copy-cURL uses `upstream_base_url` if present.
|
||||
- No regressions in existing test suite.
|
||||
|
||||
## Real E2E Acceptance Rule
|
||||
|
||||
For Codex real E2E validation, HTTP `403/401` is treated as a hard failure.
|
||||
A run is considered successful only when the end-to-end request completes with successful upstream responses.
|
||||
|
||||
## Risks
|
||||
|
||||
- Codex account scopes may block model-list or response APIs (`403`) even if proxy wiring is correct.
|
||||
- Codex ChatGPT-web route traffic may not be fully covered by current forward mode assumptions.
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Viewer Sticky Action Bar + 验证工作流计划
|
||||
|
||||
## 问题
|
||||
|
||||
`claude_tap/viewer.html` 中 detail pane 的操作按钮行在用户向下滚动时会消失。
|
||||
这会降低重复操作(`Request JSON`、`cURL`、`Diff with Prev`)的效率。
|
||||
|
||||
## 范围
|
||||
|
||||
- 在滚动 detail 内容时保持 action bar 可见。
|
||||
- 更新仓库工作流指引,确保未来 PR 包含:
|
||||
- 真实 E2E 验证期望
|
||||
- 对影响 UI 变更的截图要求
|
||||
- 产出 review 证据(测试输出 + 截图)。
|
||||
|
||||
## 执行顺序
|
||||
|
||||
1. 定义目标行为与范围。
|
||||
2. 实施最小化 UI 变更。
|
||||
3. 运行受影响行为的定向测试。
|
||||
4. 运行 E2E/browser 验证并收集截图。
|
||||
5. 运行完整项目质量 gate。
|
||||
6. 准备包含证据的 PR。
|
||||
7. 记录经验教训。
|
||||
|
||||
## 变更摘要
|
||||
|
||||
- `claude_tap/viewer.html`
|
||||
- 让 `.action-bar` 在 detail 滚动容器中保持 sticky。
|
||||
- `AGENTS.md`
|
||||
- 新增 `E2E Validation Requirements` 章节。
|
||||
- 新增 `PR Requirements for UI Changes` 章节。
|
||||
|
||||
## 验证
|
||||
|
||||
- Unit/integration 测试:`uv run pytest tests/ -x --timeout=60`
|
||||
- Lint/format 检查:
|
||||
- `uv run ruff check .`
|
||||
- `uv run ruff format --check .`
|
||||
- UI 证据:
|
||||
- 顶部状态截图
|
||||
- 滚动状态截图,确认 sticky action bar 仍可见
|
||||
|
||||
## 范围外
|
||||
|
||||
- Viewer 布局的功能重设计。
|
||||
- 非相关依赖更新。
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
status: active
|
||||
---
|
||||
|
||||
# TODO:支持 /v1/responses 的 Codex WebSocket 传输
|
||||
|
||||
**日期:** 2026-02-28
|
||||
**优先级:** 中
|
||||
**状态:** 计划中
|
||||
|
||||
## 背景
|
||||
|
||||
Codex CLI v0.106.0+ 默认对 `/v1/responses` API 调用使用 WebSocket 传输
|
||||
(`responses_websockets` 和 `responses_websockets_v2` 特性)。当前 claude-tap
|
||||
通过自动注入 `--disable responses_websockets` 的方式绕过该行为并强制使用 HTTP,
|
||||
从而使现有 HTTP reverse proxy 能捕获全部请求。
|
||||
|
||||
这个方案可用,但不理想。WebSocket 传输可能更快,且未来 Codex 版本很可能会
|
||||
成为默认/唯一路径。
|
||||
|
||||
## 目标
|
||||
|
||||
在 claude-tap 的 reverse proxy mode 中原生支持 WebSocket 拦截,使 Codex
|
||||
可使用默认 WebSocket 传输,同时 claude-tap 仍能捕获全部 API 调用。
|
||||
|
||||
## 方案选项
|
||||
|
||||
### 方案 A:WebSocket MITM proxy
|
||||
- 拦截到 `/v1/responses` 的 WebSocket upgrade 请求
|
||||
- 代理 WebSocket 连接并记录全部 frame
|
||||
- 将 frame 重组为相同 trace 格式(请求体 + SSE 等价事件)
|
||||
- 优点:对 Codex 透明,无需注入 CLI flag
|
||||
- 缺点:实现更复杂,需要处理 WS frame 重组
|
||||
|
||||
### 方案 B:带 CONNECT 隧道的 forward proxy
|
||||
- 使用 forward proxy mode(HTTP_PROXY/HTTPS_PROXY)并做 TLS 拦截
|
||||
- 在 TLS 层同时拦截 HTTP 与 WebSocket 流量
|
||||
- 优点:适用于所有传输类型
|
||||
- 缺点:需要注入 TLS 证书,系统组成更复杂
|
||||
|
||||
### 方案 C:混合方案(检测并自适应)
|
||||
- 检测 Codex 是否使用 WebSocket(检查 Upgrade headers)
|
||||
- 若是 WebSocket:代理 WS 连接并记录 frame
|
||||
- 若是 HTTP:使用现有 streaming proxy 路径
|
||||
- 优点:向后兼容,可覆盖任意 Codex 版本
|
||||
- 缺点:需要维护两条代码路径
|
||||
|
||||
## 实现说明
|
||||
|
||||
- Responses API 的 WebSocket frame 很可能遵循类似 SSE 的事件结构
|
||||
- 需要进一步确认 Codex 使用的具体 WebSocket 消息格式
|
||||
- `aiohttp`(已是依赖)支持 WebSocket proxying
|
||||
- `--disable` flag 绕过方案应保留为 fallback 选项
|
||||
|
||||
## 参考
|
||||
|
||||
- 修复 commit:`a0e00e2`(在 reverse mode 下禁用 websocket 传输)
|
||||
- 错误经验:`.agents/docs/error-experience/entries/2026-02-28-codex-reverse-websocket-capture-gap.md`
|
||||
- Codex CLI flags:`--enable/--disable responses_websockets[_v2]`
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-06
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# 标准元数据
|
||||
|
||||
`.agents/docs/standards/*.md` 下所有文件都必须包含 frontmatter,字段包括:
|
||||
|
||||
- `owner`:负责更新的团队或维护者。
|
||||
- `last_reviewed`:最近一次策略审查的 ISO 日期 `YYYY-MM-DD`。
|
||||
- `source_of_truth`:规范策略来源引用。
|
||||
|
||||
# 维护工作流
|
||||
|
||||
1. 更新受影响的标准文件,并刷新 `last_reviewed`。
|
||||
2. 保持 `AGENTS.md` 为简洁索引,并链接到更新后的文件。
|
||||
3. 本地运行 `python scripts/check_legibility.py`。
|
||||
4. 若策略行为发生变化,在 PR 描述中记录理由。
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-06
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# 编码标准
|
||||
|
||||
## 应做
|
||||
|
||||
- 删除无用代码。
|
||||
- 修复测试失败的根因。
|
||||
- 使用现有模式,并将范围限制在相关文件。
|
||||
- 信任类型不变量,避免对已类型化值进行冗余运行时检查。
|
||||
- 保持函数聚焦单一职责。
|
||||
- 在脚本中优先使用 POSIX shell 工具。
|
||||
- 在脚本中使用 `grep -F` 做固定字符串匹配。
|
||||
- 从 metadata 读取 package version,而不是硬编码字符串。
|
||||
|
||||
## 禁止
|
||||
|
||||
- 保留注释掉的代码。
|
||||
- 添加猜测性的抽象。
|
||||
- 无理由抑制 linter 警告。
|
||||
- 提交生成文件。
|
||||
- 将 refactor 与 feature 工作混在一起。
|
||||
- 为未使用代码添加兼容性 shim。
|
||||
- 在未做检查时依赖不可移植工具(`rg`、`jq`、`fd` 可能不存在)。
|
||||
|
||||
# 运行时安全规则
|
||||
|
||||
- 如果使用 `tcsetpgrp` 的前台控制权切换,在将父进程组切回前台时要处理 `SIGTTOU`。
|
||||
- 将 CI 最高 Python 版本(当前为 3.13)视为运行时敏感行为的兼容性上限。
|
||||
- TLS 测试/运行时的证书生成必须包含 SKI/AKI 扩展,以兼容 Python 3.13。
|
||||
- 涉及证书/proxy/安全敏感变更时,在可用条件下本地以 Python 3.13 验证。
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-06
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# Debugging Standards
|
||||
|
||||
## Pre-Debug Checklist
|
||||
|
||||
Before spawning agents or running automated retry loops:
|
||||
|
||||
1. **Read the code path** (5 minutes max). Trace from the entry point to the
|
||||
failing operation. Look for hardcoded values, skipped parameters, or
|
||||
divergent paths.
|
||||
|
||||
2. **Compare working vs broken.** If feature A works but feature B doesn't,
|
||||
write down the differences in their code paths. The bug is in the diff.
|
||||
|
||||
3. **Check the obvious.** Proxy settings, environment variables, port numbers,
|
||||
feature flags. Most bugs are configuration, not logic.
|
||||
|
||||
## During Debugging
|
||||
|
||||
4. **2-strike rule.** If the same approach (re-run tests, try different flags)
|
||||
fails twice, STOP. Switch to a different strategy:
|
||||
- Add targeted logging/print statements
|
||||
- Read the source code of the failing library
|
||||
- Reduce to minimal reproduction
|
||||
- Ask: "What assumption am I making that could be wrong?"
|
||||
|
||||
5. **No infinite loops.** Cron monitoring is for watching known-good processes.
|
||||
It is not a debugging tool. If a cron loop hasn't produced progress in
|
||||
2 cycles, disable it and debug manually.
|
||||
|
||||
6. **Log your hypotheses.** Before each attempt, write down:
|
||||
- What you think the problem is
|
||||
- What evidence would confirm/deny it
|
||||
- What you'll try
|
||||
This prevents circular reasoning and repeated attempts.
|
||||
|
||||
## Network/Proxy Debugging Specifically
|
||||
|
||||
7. **Always check the actual connect call parameters.** When debugging proxy
|
||||
issues, find the line where the connection is made and verify:
|
||||
- Is `proxy=` set? To what?
|
||||
- Is `trust_env=` True?
|
||||
- Are environment variables (`HTTP_PROXY`, `HTTPS_PROXY`) being read?
|
||||
|
||||
8. **Use the simplest possible test.** Before complex E2E:
|
||||
```bash
|
||||
# Can we reach the host through proxy?
|
||||
curl -x http://127.0.0.1:7897 https://target-host/
|
||||
# Can we reach it directly?
|
||||
curl https://target-host/
|
||||
```
|
||||
|
||||
9. **Check for proxy bypass.** Libraries often have `NO_PROXY`, `proxy=None`,
|
||||
or per-request proxy overrides that silently skip the system proxy.
|
||||
|
||||
## Proxy URL Construction Verification
|
||||
|
||||
10. **Verify the final upstream URL, not just the forwarded path.** When
|
||||
modifying path stripping, target URL, or route logic, add an assertion or
|
||||
log line that prints the fully constructed upstream URL. Confirm it matches
|
||||
the real API endpoint. Fake upstreams in unit tests only verify internal
|
||||
consistency — they cannot catch URL mismatches with real APIs.
|
||||
|
||||
11. **Enumerate all (client × auth × target) combinations.** Before changing
|
||||
URL handling, draw a matrix of every supported configuration:
|
||||
|
||||
```
|
||||
api.openai.com chatgpt.com/backend-api/codex
|
||||
strip /v1 ✗ 404 ✓
|
||||
no strip ✓ ✗ wrong path
|
||||
conditional ✓ ✓ ← correct
|
||||
```
|
||||
|
||||
Every cell must be verified — either by automated test or real E2E run.
|
||||
|
||||
12. **Run real E2E after any proxy/routing change.** Unit tests with fake
|
||||
upstreams are necessary but not sufficient. After proxy changes, run at
|
||||
least one real request through the proxy using tmux:
|
||||
|
||||
```bash
|
||||
# Example: verify Codex through proxy
|
||||
tmux new-session -d -s verify \
|
||||
"uv run python -m claude_tap --tap-client codex --tap-target TARGET --tap-no-launch --tap-port 0"
|
||||
# Then launch client in another window and send a test message
|
||||
```
|
||||
|
||||
13. **Don't attribute failures to "environment" without evidence.** When a
|
||||
request fails through the proxy, first print/log the constructed upstream
|
||||
URL. Only blame network/environment after confirming the URL is correct.
|
||||
See: `.agents/docs/error-experience/entries/2026-03-10-codex-strip-prefix-url-mismatch.md`
|
||||
|
||||
## Post-Debug
|
||||
|
||||
14. **Write the experience doc.** Every non-trivial debugging session produces
|
||||
an entry in `.agents/docs/error-experience/entries/`. Include:
|
||||
- What broke
|
||||
- What you tried (and why it didn't work)
|
||||
- What actually fixed it
|
||||
- The lesson for future debugging
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-29
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# E2E 验证要求
|
||||
|
||||
如果变更影响 proxying、trace 捕获、CLI session 流程、auth 处理或其他端到端行为,在开 PR 前必须运行真实 E2E 验证。
|
||||
|
||||
优先命令:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/e2e/ --run-real-e2e --timeout=300
|
||||
uv run pytest tests/e2e/test_real_proxy.py::TestRealProxy::test_single_turn --run-real-e2e --timeout=180
|
||||
```
|
||||
|
||||
手动替代方案:
|
||||
|
||||
```bash
|
||||
scripts/run_real_e2e.sh
|
||||
scripts/run_real_e2e_tmux.sh
|
||||
```
|
||||
|
||||
如果无法运行真实 E2E(例如缺少 auth/token),在 PR 正文中记录原因与剩余风险。
|
||||
|
||||
# 代理/路由变更的额外要求
|
||||
|
||||
如果变更影响 URL 构造、路径 strip、target 选择或代理转发逻辑:
|
||||
|
||||
1. 参照 `docs/support-matrix.md` 中的支持矩阵,确认所有组合的 URL 构造正确。
|
||||
2. 运行 `test_codex_upstream_url_construction` 验证 URL 断言。
|
||||
3. 至少对一种真实后端做 tmux E2E 验证(如果 auth 可用)。
|
||||
4. 不要将 proxy 请求失败归因为"环境问题",除非已确认构造的 upstream URL 正确。
|
||||
|
||||
# E2E 对话规则
|
||||
|
||||
每次 E2E 运行必须至少包含一次完整的多轮对话。
|
||||
对于对话验证和截图证据,请使用 tmux 交互流程(`scripts/run_real_e2e_tmux.sh`)。
|
||||
不要使用 `claude -p` 的单次运行作为对话完整性的证明。
|
||||
|
||||
# UI 证据要求
|
||||
|
||||
对于会改变 UI 布局、样式、交互流程或渲染内容的 PR:
|
||||
|
||||
- 每个变更的页面/状态至少提供一张截图。
|
||||
- 当视觉差异重要时,提供变更前后截图。
|
||||
- 当移动端行为受影响时,提供移动端截图。
|
||||
- 使用来自 `.traces/trace_*.jsonl` 或真实运行输出的真实 trace 产物。
|
||||
- 对于与 E2E 相关的 UI 变更,截图必须来自至少完成一次完整多轮对话的运行。
|
||||
- 对于 proxy、runtime、client 或 trace 捕获变更,截图必须展示真实 trace viewer/dashboard 中的具体 API entry 或 session 状态,不能使用手写 HTML、终端输出截图、pytest 输出截图或只展示验证摘要的图片替代。
|
||||
- PR 正文必须说明截图来源,例如真实 `claude-tap` 命令、导出的 `trace_*.jsonl`、viewer HTML 或 dashboard session。
|
||||
|
||||
# 截图质量门禁
|
||||
|
||||
作为 PR 证据提交的每张截图,在 `git add` 之前都必须通过以下检查:
|
||||
|
||||
## 强制检查
|
||||
1. **Viewport 宽度 ≥ 1280px** — Headless browser 常默认窄 viewport。截图前务必调整为桌面尺寸(1280x800 或 1440x900)。
|
||||
2. **内容与声明一致** — 如果 PR 写的是“已捕获 WS trace”,截图中必须清楚显示 WS trace,而不是其他请求或加载页。
|
||||
3. **无编码损坏** — Unicode 箭头(→←)、CJK 字符和 emoji 必须正确渲染。如有疑问,在生成证据页面时使用 ASCII 等价字符或 HTML entity。
|
||||
4. **无错误页面** — 404、ERR_EMPTY_RESPONSE、空白页或 “page not found” 不能作为证据。
|
||||
5. **最小分辨率** — 图像宽度必须 ≥ 1000px。更窄通常是移动端/平板截图。
|
||||
6. **文件大小合理** — 小于 10KB 的截图通常是空白页或错误页。典型 trace viewer 截图为 100KB–500KB。
|
||||
|
||||
## 最佳实践
|
||||
- 对日志/文本证据,优先渲染为有样式的 HTML(深色卡片、monospace、语法高亮),不要直接提供原始 `.log` 文件,以避免字体/编码问题。
|
||||
- 截取 trace viewer 时,先导航到指定 entry,再截图。
|
||||
- 使用 `scripts/check_screenshots.sh` 自动执行 pre-commit 验证。
|
||||
|
||||
## 反模式:盲目提交
|
||||
不要在未先打开并检查截图的情况下直接 `git add` + `git commit` + `git push`。这会浪费 reviewer 时间并削弱证据信任。
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-06
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# 硬性规则
|
||||
|
||||
以下规则是强制性的。若你无法遵守,请停止并说明原因。
|
||||
|
||||
1. 每次 commit 前执行 gate 检查:`ruff check`、`ruff format --check`、`pytest`。不得延期修复。
|
||||
2. UI 变更要求在 PR 正文中提供使用 `raw.githubusercontent.com` 绝对 URL 的截图。
|
||||
3. 每个 commit 只处理一个关注点。不要把 refactor 与 feature 或 bug fix 混在一起。
|
||||
4. 代码、注释、commit message 和 skill 文件仅使用英文。对外文档必须中英文双份,中文内容放在 `README_zh.md` 或对应的 `*.zh.md` 文件中。
|
||||
5. 截图、演示和测试证据必须使用 `.traces/` 中的真实 trace 数据,禁止 mock 或合成数据。
|
||||
6. 编码前必须完成 pre-work checklist;开 PR 或合并前必须完成 pre-PR checklist。
|
||||
7. 变更后必须执行 `git add`、`git commit` 和 `git push origin <branch>`。
|
||||
8. 必须使用 `gh pr create` 创建 GitHub PR;PR 未创建前,工作不算完成。
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-29
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# Screenshot Standards
|
||||
|
||||
These rules apply to PR evidence screenshots used to validate UI, viewer output, trace logs, and proxy behavior.
|
||||
|
||||
## Viewport Rules
|
||||
|
||||
1. Desktop screenshots must use a viewport width of at least `1280px`.
|
||||
2. If a change is desktop-only, do not submit mobile-layout screenshots as primary evidence.
|
||||
3. If a change intentionally affects mobile behavior, include separate mobile screenshots and label them clearly.
|
||||
|
||||
## Encoding Rules
|
||||
|
||||
1. Use ASCII-safe characters or HTML entities in generated HTML/log views for symbols that may render inconsistently.
|
||||
2. Do not use raw Unicode arrows (`→`, `←`) in generated HTML evidence content.
|
||||
3. Prefer explicit text alternatives (`->`, `<-`) or entities (`→`, `←`) to avoid garbled output.
|
||||
|
||||
## Content Verification Rules
|
||||
|
||||
1. Every screenshot must show the exact feature or fix claimed in the PR description.
|
||||
2. For protocol fixes, capture the specific row/event proving the behavior (example: `101 WEBSOCKET`, not unrelated `GET` rows).
|
||||
3. Runtime, proxy, viewer, dashboard, and client screenshots must come from a real trace viewer/dashboard or a real browser run generated by `claude-tap`, not from handcrafted HTML, terminal output, or test-output art.
|
||||
4. PR evidence links for runtime behavior must point to committed images under `.agents/evidence/pr/` and use filenames that identify the real surface, such as `trace-viewer`, `dashboard`, `session`, `e2e`, or `browser`.
|
||||
5. Before commit, verify the screenshot path and filename match the content shown in the PR markdown links.
|
||||
|
||||
## Screenshot Pre-commit Checklist
|
||||
|
||||
Run this checklist before `git commit` when screenshots are part of the PR:
|
||||
|
||||
1. Viewport width is `>=1280px` for desktop evidence.
|
||||
2. Screenshot contains the exact target state/line proving the fix.
|
||||
3. No garbled characters or encoding corruption in visible text.
|
||||
4. Images are legible, not mostly blank, and reasonably sized.
|
||||
5. Runtime evidence is backed by a real trace source, and the PR body states the source command or exported `trace_*.jsonl` / viewer HTML.
|
||||
6. Local automated check passes:
|
||||
|
||||
```bash
|
||||
python3 scripts/check_screenshots.py .agents/evidence/pr/
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-06
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# Pre-commit CI 检查
|
||||
|
||||
每次 commit 前运行:
|
||||
|
||||
```bash
|
||||
uv run ruff check .
|
||||
uv run ruff format --check .
|
||||
uv run pytest tests/ -x --timeout=60
|
||||
```
|
||||
|
||||
所有检查都必须通过。若格式检查失败,运行 `uv run ruff format .` 后重新检查。
|
||||
|
||||
# Coverage Targets
|
||||
|
||||
Coverage gates are configured in `pyproject.toml` under `[tool.claude_tap.coverage]`.
|
||||
|
||||
- Backend Python project coverage: at least `65%`.
|
||||
- Backend Python incremental coverage: at least `80%` of changed executable package lines.
|
||||
- Frontend viewer JavaScript function coverage: at least `50%` of V8-reported inline JS functions executed by the viewer contract suite.
|
||||
- Frontend viewer JavaScript incremental coverage: at least `80%` of changed `viewer.html` JavaScript functions exercised by V8 coverage.
|
||||
- Frontend viewer CSS selector coverage: at least `65%` of queryable CSS selectors must match real DOM states exercised by the viewer contract suite.
|
||||
- Frontend viewer CSS incremental coverage: at least `80%` of changed queryable `viewer.html` CSS selectors must match exercised DOM states.
|
||||
|
||||
Run the deterministic coverage gate with:
|
||||
|
||||
```bash
|
||||
python -m coverage run -m pytest tests/ -q
|
||||
python -m coverage json -o .coverage.json
|
||||
python scripts/check_coverage.py --python-coverage .coverage.json
|
||||
```
|
||||
|
||||
# Meaningful Test Requirements
|
||||
|
||||
Coverage percentage is a floor, not the goal. Tests added only to execute lines
|
||||
without proving behavior are not acceptable.
|
||||
|
||||
Every new or changed test must assert at least one meaningful contract:
|
||||
|
||||
- returned values, normalized data, status transitions, or error behavior for
|
||||
Python code;
|
||||
- rendered DOM sections, visible semantic text, user interactions, browser
|
||||
runtime errors, V8-executed viewer functions, or CSS-backed layout and theme
|
||||
states for `viewer.html`;
|
||||
- persisted files, trace records, or generated evidence when the change affects
|
||||
filesystem output.
|
||||
|
||||
Viewer tests must prefer generated HTML opened in Chromium for user-visible
|
||||
behavior. `Full JSON` may be asserted as a fallback section, but a viewer test
|
||||
must not treat `Full JSON` alone as successful semantic rendering.
|
||||
|
||||
Viewer style changes must prove more than screenshot existence. Tests should
|
||||
assert durable layout and visual contracts such as desktop/mobile widths,
|
||||
overflow bounds, expanded content dimensions, and light/dark theme differences.
|
||||
|
||||
# Pre-work Checklist
|
||||
|
||||
在进行任何代码变更之前:
|
||||
|
||||
```bash
|
||||
git diff --stat
|
||||
git log --oneline -10
|
||||
git fetch origin
|
||||
```
|
||||
|
||||
在打开或合并 PR 之前:
|
||||
|
||||
```bash
|
||||
git rebase origin/main
|
||||
uv lock --check
|
||||
uv run pytest tests/ -x --timeout=60
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
owner: claude-tap-maintainers
|
||||
last_reviewed: 2026-05-29
|
||||
source_of_truth: AGENTS.md
|
||||
---
|
||||
|
||||
# Worktree 工作流
|
||||
|
||||
使用 git worktree 进行隔离的 feature 开发:
|
||||
|
||||
```bash
|
||||
git worktree add -b feat/<name> /tmp/claude-tap-<name> main
|
||||
cd /tmp/claude-tap-<name>
|
||||
uv run pytest tests/ -x --timeout=60
|
||||
cd /path/to/claude-tap
|
||||
git merge --ff-only feat/<name>
|
||||
git worktree remove /tmp/claude-tap-<name>
|
||||
git branch -d feat/<name>
|
||||
```
|
||||
|
||||
# Code Review Checklist
|
||||
|
||||
每次 commit 前:
|
||||
|
||||
1. `uv run ruff check .`
|
||||
2. `uv run ruff format --check .`
|
||||
3. `uv run pytest tests/ -x --timeout=60`
|
||||
4. `git diff` 并检查每一行变更。
|
||||
5. 确认只改动了相关文件。
|
||||
|
||||
# PR 正文策略门禁
|
||||
|
||||
`scripts/check_pr_policy.py` 是 PR 正文和 evidence 规则的统一机器检查入口。开 PR 前和 review preflight 都必须让它通过。
|
||||
|
||||
该门禁检查:
|
||||
|
||||
1. PR 正文包含 Summary/Problem/Goal 等摘要段落。
|
||||
2. PR 正文包含 Validation/Test plan/Results 等验证段落。
|
||||
3. 改动 runtime、viewer、client、proxy 或 UI 行为时,PR 正文包含 `raw.githubusercontent.com` 截图证据链接。
|
||||
4. PR 正文中的图片链接使用 `raw.githubusercontent.com` 绝对 URL。
|
||||
5. Runtime 行为截图必须链接到 `.agents/evidence/pr/` 下提交的 trace/viewer/dashboard/session/browser 证据图,并在 PR 正文说明真实 trace/viewer 来源。
|
||||
6. PR 不包含 raw trace、生成的 trace viewer、日志、secret-like 文件或明显 token。
|
||||
|
||||
本地检查:
|
||||
|
||||
```bash
|
||||
scripts/check_pr.sh <pr-number> --no-tests
|
||||
```
|
||||
|
||||
CI 检查:
|
||||
|
||||
```bash
|
||||
python scripts/check_pr_policy.py --event-path "$GITHUB_EVENT_PATH" --changed-files-file /tmp/pr-changed-files.txt
|
||||
```
|
||||
|
||||
CI 同时在独立的 `pr-policy` job 和现有 required `lint` job 中运行该检查,避免新增 required status check 之前出现可绕过窗口。
|
||||
|
||||
# 复利式工程实践
|
||||
|
||||
记录经验教训:
|
||||
|
||||
- 错误经验:`.agents/docs/error-experience/entries/YYYY-MM-DD-<slug>.md`
|
||||
- 正向经验:`.agents/docs/good-experience/entries/YYYY-MM-DD-<slug>.md`
|
||||
- 汇总:`.agents/docs/error-experience/summary/entries/` 与 `.agents/docs/good-experience/summary/entries/`
|
||||
- 计划:`.agents/docs/plans/`
|
||||
- 指南:`docs/guides/`
|
||||
|
||||
在出现重大 bug、CI 失败或发现有价值模式后,创建一条条目并记录根因与经验。
|
||||
|
||||
# Brain + Hands 协议
|
||||
|
||||
- Claude Code (Opus):规划大脑,负责架构/API/模式/review 决策。
|
||||
- Codex:执行双手,负责样板代码/命令/机械性编辑。
|
||||
|
||||
不要把架构决策委托给执行工具。
|
||||
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 395 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 287 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,33 @@
|
||||
# PR #276 double-serialized request body evidence
|
||||
|
||||
`double-serialized-viewer.png` was captured from a real `python -m claude_tap`
|
||||
reverse-proxy run against a local fake Claude Code binary and a local fake
|
||||
upstream server.
|
||||
|
||||
The fake Claude binary sent a double-serialized request body:
|
||||
|
||||
```python
|
||||
inner = {"model": "claude-test", "messages": [{"role": "user", "content": "hi"}]}
|
||||
payload = json.dumps(json.dumps(inner)).encode()
|
||||
```
|
||||
|
||||
The run confirmed both required behaviors:
|
||||
|
||||
- The fake upstream still received the original one-pass parsed JSON string, so
|
||||
claude-tap did not mutate the request bytes sent upstream.
|
||||
- The stored trace request body was a dict, allowing the viewer to render the
|
||||
user message normally instead of showing an opaque escaped JSON string.
|
||||
|
||||
Validation commands:
|
||||
|
||||
```bash
|
||||
uv run ruff check claude_tap/forward_proxy.py claude_tap/proxy.py tests/test_e2e.py tests/test_request_body_parsing.py
|
||||
uv run ruff format --check claude_tap/forward_proxy.py claude_tap/proxy.py tests/test_e2e.py tests/test_request_body_parsing.py
|
||||
uv run pytest tests/test_request_body_parsing.py tests/test_e2e.py::test_double_serialized_request_body -q
|
||||
python scripts/check_screenshots.py .agents/evidence/pr/276-double-serialized-body
|
||||
```
|
||||
|
||||
The temporary SQLite database and exported HTML used to capture the screenshot
|
||||
were local-only and are not committed. No API keys, auth tokens, private
|
||||
prompts, raw trace files, or generated HTML viewers are present in this
|
||||
evidence directory.
|
||||
|
After Width: | Height: | Size: 55 KiB |
@@ -0,0 +1,81 @@
|
||||
# PR 286 Kimi Code CLI Evidence
|
||||
|
||||
Real validation for `--tap-client kimi-code` (not fake upstream).
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
cd /Users/civa/code/kimi-ws2/kimi-code-demo
|
||||
claude-tap --tap-client kimi-code --tap-no-open --tap-no-update-check -- -p "Reply with exactly: KIMI_CODE_E2E_OK"
|
||||
```
|
||||
|
||||
Direct control (no claude-tap):
|
||||
|
||||
```bash
|
||||
kimi -p "Reply with exactly: KIMI_DIRECT_OK"
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
- `claude-tap --tap-client kimi-code`: exit 0, assistant text `KIMI_CODE_E2E_OK`, 1 API call captured
|
||||
- Sandbox env: `KIMI_CODE_HOME` temp dir with patched `config.toml` `base_url` → `http://127.0.0.1:<port>`
|
||||
- OAuth: `credentials/` and `oauth/` symlinked from real `~/.kimi-code`
|
||||
- Migration prompt suppressed via `.skip-migration-from-kimi-cli` when `~/.kimi/.migrated-to-kimi-code` targets `~/.kimi-code`
|
||||
|
||||
## Trace source
|
||||
|
||||
- SQLite: `~/.local/share/claude-tap/traces.sqlite3`
|
||||
- Example session: `3a09233f-f616-4f90-847a-d35178b7fa36` (`client=kimi-code`, `record_count=1`)
|
||||
- Request path: `/chat/completions`, status `200`, upstream `https://api.kimi.com/coding/v1`
|
||||
|
||||
## Automated tests
|
||||
|
||||
```bash
|
||||
uv run pytest tests/test_kimi_code_launch.py tests/test_e2e.py::test_kimi_code_client_reverse_proxy -x --timeout=60
|
||||
```
|
||||
|
||||
## Maintainer validation
|
||||
|
||||
Conflict resolution and maintainer-side validation were run from a Linux worktree after rebasing the PR branch onto the latest `main`.
|
||||
|
||||
```bash
|
||||
uv run --with ruff ruff check .
|
||||
uv run --with ruff ruff format --check .
|
||||
uv run --with pytest --with pytest-asyncio --with pytest-timeout pytest tests/ -x --timeout=60
|
||||
uv run --with pytest --with pytest-asyncio pytest tests/test_client_config_framework.py tests/test_kimi_code_launch.py -q
|
||||
env -u MOONSHOT_BASE_URL -u OPENAI_BASE_URL -u OPENROUTER_BASE_URL \
|
||||
uv run --with pytest --with pytest-asyncio pytest tests/test_kimi_launch.py tests/test_e2e.py -q -k 'kimi'
|
||||
uv run --with pytest --with pytest-asyncio pytest tests/test_opencode_launch.py -q
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- Full test suite: `696 passed, 25 skipped`
|
||||
- Focused Kimi Code/client framework suite: `65 passed`
|
||||
- Focused legacy Kimi suite: `9 passed`
|
||||
- OpenCode launch suite: `8 passed`
|
||||
|
||||
Local real trace viewer evidence:
|
||||
|
||||

|
||||
|
||||
This screenshot was generated from the local claude-tap SQLite session `0bb44d26-a814-4e59-a65d-9bf16b41c8aa`, a real Kimi CLI reverse-proxy capture with two `/chat/completions` records. It validates that the existing Kimi capture/export path still renders real Kimi request details after the Kimi Code client changes. The real Kimi Code upstream capture remains the contributor-side session documented above.
|
||||
|
||||
Latest published Kimi Code CLI smoke check:
|
||||
|
||||
```bash
|
||||
npm view @moonshot-ai/kimi-code version dist-tags.latest
|
||||
npm exec --yes --package @moonshot-ai/kimi-code@latest -- kimi --version
|
||||
PATH=/tmp/claude-tap-pr287-kimi-code-latest/node_modules/.bin:$PATH \
|
||||
KIMI_CODE_HOME=/tmp/claude-tap-pr287-kimi-smoke/home \
|
||||
uv run python -m claude_tap --tap-client kimi-code \
|
||||
--tap-output-dir /tmp/claude-tap-pr287-kimi-smoke/traces \
|
||||
--tap-no-live --tap-no-open -- --help
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- Latest npm package: `@moonshot-ai/kimi-code@0.11.0`
|
||||
- Latest `kimi --version`: `0.11.0`
|
||||
- `claude-tap --tap-client kimi-code -- --help`: exit 0, with `KIMI_CODE_HOME` and `KIMI_CODE_BASE_URL` injected through the reverse-proxy sandbox
|
||||
- A real prompt was attempted with the latest CLI, but the local Linux test environment has no Kimi Code OAuth/API key configured, so the CLI stopped before sending an upstream request. This validates launch/config wiring, while the real upstream request capture is covered by the contributor evidence above.
|
||||
|
After Width: | Height: | Size: 180 KiB |
@@ -0,0 +1,132 @@
|
||||
# PR 287 Kimi Code Deep E2E Evidence
|
||||
|
||||
Real validation for `--tap-client kimi-code` using the latest `@moonshot-ai/kimi-code` package and a logged-in local Kimi Code profile.
|
||||
|
||||
## Scenario
|
||||
|
||||
The test used one real Kimi Code conversation resumed across three non-interactive turns. Each turn required file or shell tool use inside an isolated workspace:
|
||||
|
||||
- workspace: `/tmp/claude-tap-kimi-code-deep-287-workspace`
|
||||
- trace DB: `/tmp/claude-tap-kimi-code-deep-287.sqlite3`
|
||||
- Kimi resume id: `276aae75-89fb-4093-a6d7-5861c1acc4ef`
|
||||
|
||||
The workspace started with `seed.txt`. Kimi Code then created and modified:
|
||||
|
||||
- `turn1_notes.md`
|
||||
- `turn2_report.json`
|
||||
- `turn3_verification.txt`
|
||||
|
||||
## Commands
|
||||
|
||||
Turn 1:
|
||||
|
||||
```bash
|
||||
CLOUDTAP_DB=/tmp/claude-tap-kimi-code-deep-287.sqlite3 \
|
||||
npm exec --yes --package @moonshot-ai/kimi-code@latest -- bash -lc \
|
||||
'/tmp/claude-tap-pr287/.venv/bin/claude-tap --tap-client kimi-code --tap-no-open --tap-no-live --tap-no-update-check -- --print --final-message-only -p "Work only inside the current directory. Use your file or shell tools, not just reasoning. First inspect the current directory. Then create turn1_notes.md with two bullet lines, one containing KIMI_DEEP_TURN1. Then run a command that reports the file line count and sha256. Then read the file back. Reply with exactly one line starting with KIMI_DEEP_TURN1_OK and include line_count and sha256."'
|
||||
```
|
||||
|
||||
Turn 2:
|
||||
|
||||
```bash
|
||||
CLOUDTAP_DB=/tmp/claude-tap-kimi-code-deep-287.sqlite3 \
|
||||
npm exec --yes --package @moonshot-ai/kimi-code@latest -- bash -lc \
|
||||
'/tmp/claude-tap-pr287/.venv/bin/claude-tap --tap-client kimi-code --tap-no-open --tap-no-live --tap-no-update-check -- -r 276aae75-89fb-4093-a6d7-5861c1acc4ef --print --final-message-only -p "Continue the existing session. Use your file or shell tools again. Inspect turn1_notes.md, append a third bullet line containing KIMI_DEEP_TURN2, create turn2_report.json containing the final line count and sha256, then read that JSON file back. Reply with exactly one line starting with KIMI_DEEP_TURN2_OK and include line_count and sha256."'
|
||||
```
|
||||
|
||||
Turn 3:
|
||||
|
||||
```bash
|
||||
CLOUDTAP_DB=/tmp/claude-tap-kimi-code-deep-287.sqlite3 \
|
||||
npm exec --yes --package @moonshot-ai/kimi-code@latest -- bash -lc \
|
||||
'/tmp/claude-tap-pr287/.venv/bin/claude-tap --tap-client kimi-code --tap-no-open --tap-no-live --tap-no-update-check -- -r 276aae75-89fb-4093-a6d7-5861c1acc4ef --print --final-message-only -p "Continue the same session for a third turn. Use file or shell tools again. Read turn2_report.json, list the workspace files, run a Python command that verifies the sha256 of turn1_notes.md matches the report, and write turn3_verification.txt containing KIMI_DEEP_TURN3 plus the verification result. Then read turn3_verification.txt back. Reply with exactly one line starting with KIMI_DEEP_TURN3_OK and include verified=true."'
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
| Turn | claude-tap session | API calls | Status | Final response |
|
||||
| --- | --- | ---: | --- | --- |
|
||||
| 1 | `1ee9799d-d48f-404b-99c4-f7182fc825dc` | 5 | `complete` | `KIMI_DEEP_TURN1_OK line_count=2 sha256=0473e9a2b20bd4f62ad857f28ae8e4323859f23cfc9cffd6bc6d345b0ea99fd9` |
|
||||
| 2 | `412db154-5fe6-4b91-9093-3f51f83f7e4c` | 6 | `complete` | `KIMI_DEEP_TURN2_OK line_count=3 sha256=c027e93c45bfbb4194e08d6f5d523949ff8bdf267a77a4f05d66bbf4b3b59367` |
|
||||
| 3 | `0380294c-9875-4316-9564-809d94a7a4a9` | 5 | `complete` | `KIMI_DEEP_TURN3_OK verified=true` |
|
||||
|
||||
Workspace verification:
|
||||
|
||||
```text
|
||||
turn1_notes.md:
|
||||
- This is turn1_notes.md
|
||||
- KIMI_DEEP_TURN1
|
||||
- KIMI_DEEP_TURN2
|
||||
|
||||
turn2_report.json:
|
||||
{"line_count":3,"sha256":"c027e93c45bfbb4194e08d6f5d523949ff8bdf267a77a4f05d66bbf4b3b59367"}
|
||||
|
||||
turn3_verification.txt:
|
||||
KIMI_DEEP_TURN3 verified=True
|
||||
```
|
||||
|
||||
## Interactive tmux E2E
|
||||
|
||||
I also ran Kimi Code as a real interactive TUI process under tmux and sent three consecutive prompts with `tmux paste-buffer` / `tmux send-keys`. This validates a single running Kimi Code process, terminal input handling, tool approval prompts, and continuous conversation state without relying on `--print` or `-r` for each turn.
|
||||
|
||||
- tmux session: `ctap-kimi-287`
|
||||
- workspace: `/tmp/claude-tap-kimi-code-tmux-287-workspace`
|
||||
- trace DB: `/tmp/claude-tap-kimi-code-tmux-287.sqlite3`
|
||||
- claude-tap session: `b00ecd17-4c6c-40a6-829a-6f5fdbfc5719`
|
||||
- Kimi Code session: `81668bcd-c11e-455f-ab55-280dc4da9031`
|
||||
|
||||
Interactive prompts sent:
|
||||
|
||||
1. Create and read back `tmux_turn1.txt`, then reply `KIMI_TMUX_TURN1_OK`.
|
||||
2. Continue in the same TUI session, append `KIMI_TMUX_TURN2`, run `wc -l` and `sha256sum`, then reply `KIMI_TMUX_TURN2_OK`.
|
||||
3. Continue in the same TUI session, list files, run Python verification, write/read `tmux_turn3_check.txt`, then reply `KIMI_TMUX_TURN3_OK`.
|
||||
|
||||
The trace captured:
|
||||
|
||||
- 12 total API records
|
||||
- `client=kimi-code`, `proxy_mode=reverse`, `status=complete`
|
||||
- final response: `KIMI_TMUX_TURN3_OK`
|
||||
- tool evidence for `Shell`, `WriteFile`, and `ReadFile`
|
||||
- real tool approval flow in the interactive TUI
|
||||
|
||||
Workspace verification:
|
||||
|
||||
```text
|
||||
tmux_turn1.txt:
|
||||
KIMI_TMUX_TURN1 KIMI_TMUX_287
|
||||
KIMI_TMUX_TURN2
|
||||
|
||||
tmux_turn3_check.txt:
|
||||
KIMI_TMUX_TURN3 verified=true
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
All screenshots were taken from real exported viewer HTML generated from the SQLite trace DB. The scroll screenshots intentionally capture different detail-pane positions rather than only the first viewport.
|
||||
The committed filenames include `viewer` so the PR evidence policy can identify them as runtime trace viewer screenshots.
|
||||
|
||||
- `kimi-code-deep-viewer-turn3-overview.png` - third-turn viewer overview with Kimi Code sidebar entries and tool labels.
|
||||
- `kimi-code-deep-viewer-turn3-mid-scroll.png` - scrolled detail pane showing conversation history and tool-call context.
|
||||
- `kimi-code-deep-viewer-turn3-tool-scroll.png` - deeper scroll showing Shell and ReadFile tool results plus resumed user input.
|
||||
- `kimi-code-deep-viewer-turn3-final-response-scroll.png` - scrolled third-turn tool sequence before the final verification.
|
||||
- `kimi-code-deep-viewer-turn3-bottom-final-response.png` - bottom scroll showing `turn3_verification.txt` readback and `KIMI_DEEP_TURN3_OK verified=true`.
|
||||
- `kimi-code-deep-viewer-turn2-overview.png` - second-turn viewer overview.
|
||||
- `kimi-code-deep-viewer-turn2-tool-scroll.png` - second-turn scrolled tool call/result evidence.
|
||||
- `kimi-code-tmux-viewer-overview.png` - interactive tmux run overview with 12 records and tool labels.
|
||||
- `kimi-code-tmux-viewer-turn1-tools-scroll.png` - first tmux prompt scrolled tool evidence.
|
||||
- `kimi-code-tmux-viewer-turn2-history-scroll.png` - second tmux prompt scrolled history/tool evidence.
|
||||
- `kimi-code-tmux-viewer-turn3-tools-scroll.png` - third tmux prompt scrolled tool evidence.
|
||||
- `kimi-code-tmux-viewer-bottom-final-response.png` - bottom scroll showing `KIMI_TMUX_TURN3_OK`.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
uv run python scripts/check_screenshots.py .agents/evidence/pr/287
|
||||
uv run python scripts/verify_screenshots.py /tmp/claude-tap-kimi-code-deep-287-turn2.html /tmp/claude-tap-kimi-code-deep-287-turn3.html
|
||||
uv run python scripts/verify_screenshots.py /tmp/claude-tap-kimi-code-tmux-287.html
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- screenshot quality: `PASS=12 WARN=0 FAIL=0`
|
||||
- viewer HTML render verification: all exported HTML files passed
|
||||
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 150 KiB |