@routerhub/agent-rules 1.5.222 → 1.5.223

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.base.md CHANGED
@@ -385,7 +385,7 @@ agent-rules 生成的规则文件分两层,行为与归属不同,评审/发
385
385
  - ⚠️ **PR 必须附效果截图作为可视化证据,且逐条满足以下硬性要求(缺一不可,禁止跳过)**:
386
386
  - **全页图**:用浏览器真实视口(`window.innerWidth` 即截图宽度,4K 屏自然宽 3840)`fullPage` 截完整页面,禁止只截视口一屏。⚠️ **禁止强制把视口硬拉成 3840 宽**——浏览器视口宽由屏幕实际分辨率决定,硬拉宽会让内容按比例缩小(看不清),且与截图像素发生缩放错位,是标注不准的根因之一。若要更高清晰度,用 `set viewport <W> <H> 2`(DPR 倍率)提高渲染精度(CSS 宽不变、像素更密),而不是拉宽 CSS 视口。
387
387
  - **全面多角度**:一张全页图 + 每个关键改动区域的局部放大图,多个改动点要逐个覆盖,确保 reviewer 不看代码就能看全本次全部改动。
388
- - **箭头标注(必须标注准)**:每张截图必须用醒目箭头 + 简短文字标签标注关键改动区域/验证点(修复前红框/红箭头、修复后绿框/绿箭头),标注放在不遮挡原内容的位置,禁止只贴裸图不标注。⚠️ **标注坐标必须来自浏览器 DOM 测量(`getBoundingClientRect()` + `scrollX/scrollY`)精确换算成截图像素,禁止肉眼看图估坐标**——全页拼接/缩放截图里 CSS 坐标 ≠ 截图像素,肉眼估位是标注不准的直接原因。标注统一走 `/screenshot-annotate` skill(坐标换算方法 + `annotate.js` 统一脚本)。
388
+ - **箭头标注(必须标注准)**:每张截图必须用醒目箭头 + 简短文字标签标注关键改动区域/验证点(修复前红框/红箭头、修复后绿框/绿箭头),标注放在不遮挡原内容的位置,禁止只贴裸图不标注。⚠️ **标注坐标必须来自浏览器 DOM 测量(`getBoundingClientRect()` + `scrollX/scrollY`)精确换算成截图像素,禁止肉眼看图估坐标**——全页拼接/缩放截图里 CSS 坐标 ≠ 截图像素,肉眼估位是标注不准的直接原因。标注统一走 `/screenshot-annotate` skill(坐标换算方法 + `annotate.cjs` 统一脚本)。
389
389
  - **URL 可见且能打开**:按「⚠️ 截图规范」把完整 URL 以文字叠进截图本身(`/screenshot-annotate --url`),且该 URL 必须是别人能直接打开的地址(测试环境 / 线上域名),**禁止 `localhost` / `127.0.0.1` / `file:///` 等本机地址**——reviewer 在自己机器上打不开,等于没给。截图旁再给出可点击的链接(PR 正文写成 `[打开页面](URL)`),reviewer 一眼复制、或直接点开亲自复核(呼应「⚠️ 验收环境铁律」)。
390
390
  - **前后对比**:必须同时展示修复前与修复后。
391
391
  - ⚠️ **截图必须直接内嵌在 PR Description 中,让 reviewer 打开 PR 就能看到效果图(`![](CDN_URL)` 方式渲染为可见图片),禁止只在文字里描述"改动了什么"而不放图,也禁止把截图只作为文件附件/提交到分支目录而不在 PR 正文中引用。** 原因:reviewer 看 PR 的第一眼就是看描述,如果看不到图、只能读文字,完全无法直观感知改动效果;截图不内嵌 = 等于没附。
@@ -640,7 +640,7 @@ agent-rules 生成的规则文件分两层,行为与归属不同,评审/发
640
640
  - ⚠️ **全页截图**:用 `screenshot --full`(agent-browser)或 `page.screenshot({ fullPage: true })`(Playwright)截完整页面,不要只截视口的一部分;用 agent-browser 截全页前先 `set viewport <视口宽> <合适高度>` 固定视口,确保截图宽度 = 视口宽度。
641
641
  - ⚠️ **每张截图都必须在图上写出「文字版」完整 URL——统一写,不得只靠地址栏**:用 `/screenshot-annotate` 的 `--url` 把 URL 叠进截图顶部标题条,让它成为截图自身的一部分,别人一眼能读到、能照着地址跳转。**禁止只依赖浏览器地址栏**:交付形态是 HTML / PDF 时,地址栏是静态像素——别人既点不了、也复制不走;裁切图 / 局部放大图(从大图里裁一块出来)压根没有地址栏;图缩放到文档版心宽度后,条带里的字往往已糊到认不出。**类比:把门牌号直接印在地图上,而不是让人对着地图回忆自己是从哪个路口拐进来的。**
642
642
  - ⚠️ **交付载体里必须附「可点击的跳转入口」,且该 URL 必须是别人能直接打开访问的地址(测试环境 / 线上域名)**:HTML 文档 / 报告把地址渲染成按钮或链接,写成 `<a target="_blank" rel="noopener" href="...">`(按「HTML 页面/文档中的外部链接默认用新标签页打开」实现)——点击即在新标签页打开,不打断当前文档;Markdown / PR 正文写成 `[打开页面](URL)`。**禁止用本机地址充当 URL**——`localhost`、`127.0.0.1`、内网 IP、`file:///...` 本机路径在别人机器上要么打不开、要么指向他自己的机器,等于没给 URL,做成按钮也只会点开一个打不开的页面(呼应「⚠️ 验收环境铁律」)。
643
- - ⚠️ **标注必须准确,坐标必须有浏览器测量来源**:箭头/框要指哪儿,用 `getBoundingClientRect()` + `scrollX/scrollY` 取元素在整页中的 CSS 坐标,再按截图实际宽度换算成截图像素,统一用 `/screenshot-annotate` skill(含 `annotate.js` 脚本)标注。禁止用肉眼看图估坐标,禁止在强制缩放造成坐标错位的前提下标注。
643
+ - ⚠️ **标注必须准确,坐标必须有浏览器测量来源**:箭头/框要指哪儿,用 `getBoundingClientRect()` + `scrollX/scrollY` 取元素在整页中的 CSS 坐标,再按截图实际宽度换算成截图像素,统一用 `/screenshot-annotate` skill(含 `annotate.cjs` 脚本)标注。禁止用肉眼看图估坐标,禁止在强制缩放造成坐标错位的前提下标注。
644
644
  - **保存到磁盘文件**(`page.screenshot({ path, fullPage: true })`),禁止只用内联展示的截图工具——内联的不落盘,用户无法在 Markdown / HTML 文档里查看。路径统一放 `screenshots/` 临时目录,文件名用「编号 + 英文描述」(如 `01-login-page.png`)。
645
645
 
646
646
  ### 非 UI / 后端 / 基础设施改动的效果截图获取方法
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routerhub/agent-rules",
3
- "version": "1.5.222",
3
+ "version": "1.5.223",
4
4
  "description": "Shared Copilot agent rules and guidelines for RouterHub projects",
5
5
  "main": "AGENTS.base.md",
6
6
  "bin": {
@@ -161,7 +161,7 @@ description: >-
161
161
  - 截图结果必须在图上写出文字版完整 URL(走 `/screenshot-annotate --url`,叠进截图标题条),不得只靠地址栏;且该 URL 必须是别人能直接打开的地址(测试环境 / 线上域名),禁止 `localhost` / `127.0.0.1` / `file:///` 等本机地址;截图一律在测试环境取证,本地效果不作交付证据
162
162
  - 文档里每张截图旁再给一个可点击的跳转入口(`<a target="_blank" rel="noopener" href="...">打开页面</a>` 渲染成按钮),让别人一点就在新标签页打开该页面亲自复核,不用从图里抄地址
163
163
  - ⚠️ **正文里出现的每一个地址,后面都要紧跟一个「在新标签页打开」按钮**——不只是截图旁那个页面入口:步骤里写的 GitHub 代码行链接(`.../postgres.go#L651-L660`)、要复核的后台页面地址、需求单 URL,**只要这句话是在让读者去打开它,就得跟一个按钮**(`<a class="open-btn" target="_blank" rel="noopener" href="...">在新标签页打开 ↗</a>`)。**禁止把地址当纯文本丢在正文里**——读者得选中、复制、切浏览器、粘贴,一步一断,还容易复制漏字符。(规则见 `AGENTS.base.md`「HTML 文档截图与 curl 命令规范」)
164
- - ⚠️ **截图版 HTML 的每张截图都必须加箭头(或红框)标注**,指向该图要说明的关键操作点或关键数据,禁止放无标注的「裸截图」;标注放在页面空白区域,不遮挡关键内容。⚠️ **标注坐标必须精确**:用 `/screenshot-annotate` skill(浏览器 DOM 测得的坐标 → `annotate.js` 换算到截图像素),禁止肉眼估位
164
+ - ⚠️ **截图版 HTML 的每张截图都必须加箭头(或红框)标注**,指向该图要说明的关键操作点或关键数据,禁止放无标注的「裸截图」;标注放在页面空白区域,不遮挡关键内容。⚠️ **标注坐标必须精确**:用 `/screenshot-annotate` skill(浏览器 DOM 测得的坐标 → `annotate.cjs` 换算到截图像素),禁止肉眼估位
165
165
  - 制作过程中产生的中间截图文件统一放到 `screenshots/` 目录
166
166
 
167
167
  ### 分步操作记录(每步必录)
@@ -85,7 +85,7 @@ Closes #issue编号
85
85
  2. **截修复前**:临时注释/回退改动代码 → 等 hot reload → 同位置截图 → 恢复代码
86
86
  - ⚠️ **若线上数据已变化,导致无法在真实页面复现"修复前"效果**:允许改用等价的纯逻辑对比代替(用新旧两版逻辑代码跑同样的输入数据,把输出结果差异渲染成对比图),但必须在 Description 里明确写清楚"为什么无法复现 + 用了什么替代方案",禁止因此省略截图或假装能复现。
87
87
  - ⚠️ **修 bug 的 PR:这张"修复前"截图必须来自真实复现,不是回退代码摆拍出来的。** 正确做法是先按「⚠️ 缺陷复现铁律」在**未改动的代码**上复现出坏现象、截下这一张(它同时就是「缺陷复现」栏目的证据),再去改代码;回退代码截图只是辅助,**唯一目的是把同一处界面截得位置对齐**,不能替代复现——否则截图证明的只是"代码改回去长这样",不是"用户当时遇到的就是这个"。无法在改动前复现的,按上一条写明原因,禁止默认走回退摆拍。
88
- 3. **生成对比图**:用 `annotate.js`(截图标注统一脚本,见 `/screenshot-annotate` skill)拼接并标注 → 上方红/绿色标注条(❌修复前 / ✅修复后)→ 下方左右并排截图 → 关键改动区域用坐标精确换算后的箭头 + 红/绿框圈选
88
+ 3. **生成对比图**:用 `annotate.cjs`(截图标注统一脚本,见 `/screenshot-annotate` skill)拼接并标注 → 上方红/绿色标注条(❌修复前 / ✅修复后)→ 下方左右并排截图 → 关键改动区域用坐标精确换算后的箭头 + 红/绿框圈选
89
89
  4. **上传 GitHub CDN**(在 PR Description 编辑区直接上传):
90
90
  - 打开目标 PR 页面 → 在 **Description 编辑区**直接上传图片(拖拽/粘贴,或定位 Description 编辑区的隐藏 `input[type=file]` 上传),GitHub 会自动把图片转成 `https://github.com/user-attachments/assets/...` 的 CDN 地址并插入 Description 正文,`![](CDN_URL)` 内嵌
91
91
  - ⚠️ **禁止走评论区上传再搬运 CDN URL**:评论区上传需要多一步「提交评论 → 复制 URL → 粘贴到 Description」,产生的临时图片评论会留在 PR 对话里干扰 reviewer 阅读,且多一步手动搬运容易出错
@@ -96,7 +96,7 @@ Closes #issue编号
96
96
  - ⚠️ **全页图用浏览器真实视口宽度(`window.innerWidth` 即截图宽,4K 屏自然宽 3840)**,用 agent-browser 时先 `set viewport <视口宽> <合适高度>` 固定视口,再 `screenshot --full` 截完整页面。⚠️ **禁止强制 `set viewport 3840 <h>` 把 CSS 视口拉宽到比屏幕还宽**(页面会缩小看不清、且 CSS 像素与截图像素缩放错位导致标注不准);需要更高清晰度时用 `set viewport <W> <H> 2`(DPR 倍率,CSS 宽不变、像素更密)。
97
97
  - ⚠️ **必须 `fullPage: true` 截完整页面**,不要只截视口的一部分。每个改动点都要覆盖到,禁止只截视口内一屏。
98
98
  - ⚠️ **截图必须全面(多图覆盖多个角度)**:一张全页图 + 每个关键改动区域的局部放大图。只截一处、截局部、漏掉改动点都不算全。整份 PR Description 的效果截图要使 reviewer 不看代码就能看全本次改动。
99
- - ⚠️ **每张截图必须用醒目箭头 + 简短文字标签标注关键改动区域/验证点**,让看的人一眼看懂这张图证明了什么;修复前用红框/红箭头,修复后用绿框/绿箭头,标注放在不遮挡原内容的位置。禁止只贴裸图不标注。⚠️ **标注坐标必须精确**:统一用 `/screenshot-annotate` skill(`getBoundingClientRect()` + `scrollX/scrollY` 换算到截图像素,用 `annotate.js` 脚本画箭头),禁止肉眼看图估坐标。
99
+ - ⚠️ **每张截图必须用醒目箭头 + 简短文字标签标注关键改动区域/验证点**,让看的人一眼看懂这张图证明了什么;修复前用红框/红箭头,修复后用绿框/绿箭头,标注放在不遮挡原内容的位置。禁止只贴裸图不标注。⚠️ **标注坐标必须精确**:统一用 `/screenshot-annotate` skill(`getBoundingClientRect()` + `scrollX/scrollY` 换算到截图像素,用 `annotate.cjs` 脚本画箭头),禁止肉眼看图估坐标。
100
100
  - ⚠️ **每张截图都必须在图上写出文字版完整 URL(走 `/screenshot-annotate --url` 叠进截图标题条),且该 URL 必须是别人能直接打开的地址(测试环境 / 线上域名)**——`localhost` / `127.0.0.1` / `file:///` 等本机地址在 reviewer 机器上打不开,等于没给。截图旁再以可点击链接写出这条 URL(PR 正文写成 `[打开页面](URL)`),方便一眼复制或直接点开复核。
101
101
  - ⚠️ **所有截图一律在测试环境取证**:先部署到测试环境(`/deploy-test`)再操作截图,禁止拿本地运行的效果当交付证据——本地依赖你本机的库/配置/未提交代码,别人照同样命令跑不出来(本地仅可用于开发中即时联调,不作证据)。
102
102
  - ⚠️ 截图保存到本地磁盘 `docs/` 对应子目录(文件名「编号 + 英文描述」,如 `01-before.png` / `02-after.png`),上传 CDN 后按规则清理临时文件,禁止提交到 Git 仓库。
@@ -142,7 +142,7 @@ For each comment, in severity order:
142
142
  - 宁可漏报一条真问题,不用十条口味问题去刷屏。
143
143
 
144
144
  **处理完的每条都必须有落点**:修复 commit / Won't fix 回复 / 误报说明。不回复 bot 下轮必重提。
145
- **同步落盘**:配合 `loop-review` 的 `save-review-decision.sh`,把判断结果写入 `.tmp-loop-review/decisions/`,跨会话去重(与 loop-review 共用同一收敛状态)。
145
+ **同步落盘**:配合 `loop-review` 的 `save-review-decision.sh`,把判断结果写入 `.git/.tmp-loop-review/decisions/`(共享 git 目录,跨 worktree 共享),跨会话去重(与 loop-review 共用同一收敛状态)。
146
146
 
147
147
  ### 4. Commit changes
148
148
 
@@ -252,7 +252,7 @@ Bots (Gemini, Codex, etc.) review every push. **Each push should go through revi
252
252
  - **ALWAYS** fix comments that cause actual bug / data loss / security vulnerability with a reproducible trigger path, or that violate AGENTS.base.md / AGENTS.private.md ⚠️ rules.
253
253
  - **ALWAYS** Won't fix style/refactor/defensive suggestions with no trigger path — do NOT fix them just because a bot flagged them.
254
254
  - **ALWAYS** give every processed comment a resolution: fix commit, Won't fix, or false-alarm reply. A silent skip means the bot re-asks next round.
255
- - **ALWAYS** record decisions to `.tmp-loop-review/decisions/` via `save-review-decision.sh` when doing loop-review; cross-session dedup depends on it.
255
+ - **ALWAYS** record decisions to `.git/.tmp-loop-review/decisions/` (shared git dir) via `save-review-decision.sh` when doing loop-review; cross-session dedup depends on it.
256
256
  - **NEVER** report an issue you cannot confirm from the diff. One real bug beats ten style suggestions.
257
257
 
258
258
  ## References
@@ -115,7 +115,7 @@ bash "$(git rev-parse --show-toplevel)/.claude/skills/loop-review/scripts/save-r
115
115
  }'
116
116
  ```
117
117
 
118
- 决策 JSON 落盘到 `.tmp-loop-review/decisions/pr-<PR>.decisions.json`,跨会话、跨上下文压缩都能复用。**判断表既要展示给人看(终端表格),也要落盘给机器看(decisions/ JSON)**,两者都做。
118
+ 决策 JSON 落盘到 `.git/.tmp-loop-review/decisions/pr-<PR>.decisions.json`(**共享 git 目录**下,脚本用 `git rev-parse --git-common-dir` 定位,主工作区与所有 linked worktree 解析到同一处,故换 worktree 也不丢去重记忆),跨会话、跨上下文压缩都能复用。**判断表既要展示给人看(终端表格),也要落盘给机器看(decisions/ JSON)**,两者都做。
119
119
 
120
120
  ⚠️ **落盘的原则**:每条建议都必须有一个 decision(fixed / wontfix / false_alarm / deferred)落盘,没有落盘的建议 = 没有收敛。
121
121
 
@@ -237,7 +237,7 @@ done
237
237
 
238
238
  - ⚠️ **每条建议必须读真实代码再判断,禁止盲从 review 文字、也禁止盲从严重度标签**。review 是 LLM 生成的,可能误报,严重度只是它的标注。
239
239
  - ⚠️ **循环收敛靠「值得修的都修完 + 不值得修的 Won't fix 切断」**,不是靠「严重度清零」。禁止用「无 Critical/Warning」当结束标准。
240
- - ⚠️ **落点 = 落盘到 `.tmp-loop-review/decisions/`(`save-review-decision.sh`)+ 本轮 PR 回复(`gh pr comment` / inline reply),缺一不可。** 落盘让「会话中断后下轮还能识别已处理」,PR 回复让「bot 不再重提」。缺落盘:pickup 时历史丢失,等价于没收敛;缺回复:bot 下轮原样重提,循环无限。
240
+ - ⚠️ **落点 = 落盘到 `.git/.tmp-loop-review/decisions/`(`save-review-decision.sh`,共享 git 目录,跨 worktree 共享)+ 本轮 PR 回复(`gh pr comment` / inline reply),缺一不可。** 落盘让「会话中断后下轮还能识别已处理」,PR 回复让「bot 不再重提」。缺落盘:pickup 时历史丢失,等价于没收敛;缺回复:bot 下轮原样重提,循环无限。
241
241
  - ⚠️ **每轮先查 `query-review-decision.sh`,命中「已处理过」的建议直接复用历史结论**,禁止重新判断一遍——那是循环回跳的根源。
242
242
  - ⚠️ **每条 review 建议必须有落点**(修复 commit / Won't fix / 误报回复),禁止静默跳过——不回复 bot 下轮必重提,循环无限。
243
243
  - ⚠️ **每轮先确认哪些是上一轮已处理过的**,一律不重复改,直接回复「已修复于 <hash>」。
@@ -20,7 +20,15 @@
20
20
 
21
21
  set -euo pipefail
22
22
 
23
- DECISIONS_DIR="$(git rev-parse --show-toplevel)/.tmp-loop-review/decisions"
23
+ # 台账目录锚定「共享 git 目录」而非 --show-toplevel
24
+ # --show-toplevel 在 linked worktree 里返回该 worktree 自身路径,而本项目规则要求
25
+ # 「一个代码任务 = 一个独立 worktree」——若按 toplevel 落盘,每个 worktree 各持一份台账,
26
+ # 换个 worktree 就丢失历史判断,bot 的建议被原样重提,循环 review 永远收敛不了。
27
+ # --git-common-dir 在主工作区与所有 linked worktree 下解析为同一路径(主工作区返回相对
28
+ # 路径 ".git",linked worktree 返回绝对路径,故统一 cd + pwd 归一化)。
29
+ # 落在 .git/ 下还顺带避免污染工作区:.git/ 内容永不被 git 跟踪,无需往 .gitignore 加条目。
30
+ GIT_COMMON_DIR="$(cd "$(git rev-parse --git-common-dir)" && pwd)"
31
+ DECISIONS_DIR="$GIT_COMMON_DIR/.tmp-loop-review/decisions"
24
32
 
25
33
  PR="${1:-}"
26
34
  FIELD="${2:-}"
@@ -48,7 +56,7 @@ case "$FIELD" in
48
56
  HIT=$(jq --arg s "$VALUE" '.decisions[] | select(.summary | contains($s))' "$DECISION_FILE" 2>/dev/null || true)
49
57
  ;;
50
58
  *)
51
- echo "错误: 未知查询字段 $FIELD(支持 id / summary / count)" >&2
59
+ echo "错误: 未知查询字段 ${FIELD}(支持 id / summary / count)" >&2
52
60
  exit 2
53
61
  ;;
54
62
  esac
@@ -6,8 +6,8 @@
6
6
  # 但判断表只存在于对话上下文里——会话一中断、上下文一压缩,上一轮的
7
7
  # 「这条已 Won't fix / 这条已修复于 <hash>」记忆就丢了。bot 下轮原样重提,
8
8
  # 于是又进入「改了又错、错了再改」的循环。
9
- # 本脚本把判断表落盘到 .tmp-loop-review/decisions/,下次启动先读历史判断
10
- # 去重,让「收敛」从「靠对话记忆」升级为「靠文件记忆」。
9
+ # 本脚本把判断表落盘到 .git/.tmp-loop-review/decisions/(共享 git 目录,跨 worktree 共享),
10
+ # 下次启动先读历史判断去重,让「收敛」从「靠对话记忆」升级为「靠文件记忆」。
11
11
  #
12
12
  # 用法:
13
13
  # bash save-review-decision.sh <决策JSON> # 写入一条决策
@@ -28,7 +28,15 @@
28
28
 
29
29
  set -euo pipefail
30
30
 
31
- DECISIONS_DIR="$(git rev-parse --show-toplevel)/.tmp-loop-review/decisions"
31
+ # 台账目录锚定「共享 git 目录」而非 --show-toplevel
32
+ # --show-toplevel 在 linked worktree 里返回该 worktree 自身路径,而本项目规则要求
33
+ # 「一个代码任务 = 一个独立 worktree」——若按 toplevel 落盘,每个 worktree 各持一份台账,
34
+ # 换个 worktree 就丢失历史判断,bot 的建议被原样重提,循环 review 永远收敛不了。
35
+ # --git-common-dir 在主工作区与所有 linked worktree 下解析为同一路径(主工作区返回相对
36
+ # 路径 ".git",linked worktree 返回绝对路径,故统一 cd + pwd 归一化)。
37
+ # 落在 .git/ 下还顺带避免污染工作区:.git/ 内容永不被 git 跟踪,无需往 .gitignore 加条目。
38
+ GIT_COMMON_DIR="$(cd "$(git rev-parse --git-common-dir)" && pwd)"
39
+ DECISIONS_DIR="$GIT_COMMON_DIR/.tmp-loop-review/decisions"
32
40
  mkdir -p "$DECISIONS_DIR"
33
41
 
34
42
  INPUT="${1:-}"
@@ -61,5 +69,11 @@ else
61
69
  MERGED="$(jq -n --argjson new "$INPUT" '{ pr: $new.pr, decisions: [$new] }')"
62
70
  fi
63
71
 
64
- printf '%s\n' "$MERGED" > "$DECISION_FILE"
72
+ # 先写同目录临时文件再 mv 覆盖(同文件系统内 mv 是原子替换):printf 直接重定向会先截断
73
+ # 目标文件,若进程在截断与写入之间被中断,台账会变成空文件/半截 JSON——已积累的去重记忆全丢。
74
+ # (注:这里只保证「单次写入不撕裂」,两个进程同时写同一 PR 的读-改-写仍非串行,需靠
75
+ # 「同一 PR 的循环 review 不并行开两个 worktree」的既有约定规避。)
76
+ TMP_FILE="$(mktemp "$DECISIONS_DIR/.pr-$PR.decisions.XXXXXX")"
77
+ printf '%s\n' "$MERGED" > "$TMP_FILE"
78
+ mv -f "$TMP_FILE" "$DECISION_FILE"
65
79
  echo "✅ 已写入 $DECISION_FILE"
@@ -31,23 +31,23 @@ description: >-
31
31
 
32
32
  fullPage 截图宽高 = 整页 CSS 宽高 × `devicePixelRatio`(DPR)。因此:
33
33
 
34
- - **DPR = 1**:整页 CSS 坐标 = 截图像素坐标,直接给 annotate.js 传 `x = rect.left + scrollX`、`y = rect.top + scrollY`,无需任何换算参数。
35
- - **DPR ≠ 1**(`set viewport <W> <H> 2` 提高了 DPR):必须传 `--fullpage --dpr N`(N = `window.devicePixelRatio`),annotate.js 按 DPR 等比缩放,`w/h` 一并缩放。
34
+ - **DPR = 1**:整页 CSS 坐标 = 截图像素坐标,直接给 annotate.cjs 传 `x = rect.left + scrollX`、`y = rect.top + scrollY`,无需任何换算参数。
35
+ - **DPR ≠ 1**(`set viewport <W> <H> 2` 提高了 DPR):必须传 `--fullpage --dpr N`(N = `window.devicePixelRatio`),annotate.cjs 按 DPR 等比缩放,`w/h` 一并缩放。
36
36
 
37
- ⚠️ **禁止**在 fullPage 场景用 `--viewport "cssW,cssH"` 让 annotate.js 按 `imageH / cssH` 算 Y 轴——`cssH` 是视口高不是整页高,页面越长标注越往下偏。
37
+ ⚠️ **禁止**在 fullPage 场景用 `--viewport "cssW,cssH"` 让 annotate.cjs 按 `imageH / cssH` 算 Y 轴——`cssH` 是视口高不是整页高,页面越长标注越往下偏。
38
38
 
39
- ⚠️ **坐标捕获命令会返回 `dpr`**:截图时记下 `set viewport` 是否用了 DPR>1,若用了必须把捕获到的 `dpr` 一并传给 annotate.js(`--fullpage --dpr <dpr>`),不要依赖记忆。
39
+ ⚠️ **坐标捕获命令会返回 `dpr`**:截图时记下 `set viewport` 是否用了 DPR>1,若用了必须把捕获到的 `dpr` 一并传给 annotate.cjs(`--fullpage --dpr <dpr>`),不要依赖记忆。
40
40
 
41
41
  ### 情形 B:设置了自定义 viewport 视口的截图(截图宽度 = viewport 宽)
42
42
 
43
- 浏览器把 viewport 设成 X×Y,截图宽高 = X×Y × DPR(`window.innerWidth` / `innerHeight` 是 CSS 宽高)。元素坐标直接用 `rect` 的值即可,但传给 annotate.js 时:
43
+ 浏览器把 viewport 设成 X×Y,截图宽高 = X×Y × DPR(`window.innerWidth` / `innerHeight` 是 CSS 宽高)。元素坐标直接用 `rect` 的值即可,但传给 annotate.cjs 时:
44
44
  - DPR = 1:直接传,无需参数;
45
- - DPR > 1:传 `--viewport "cssW,cssH"`(cssW/cssH = `window.innerWidth/Height`),annotate.js 按截图/视口换算。
45
+ - DPR > 1:传 `--viewport "cssW,cssH"`(cssW/cssH = `window.innerWidth/Height`),annotate.cjs 按截图/视口换算。
46
46
 
47
47
  ### 情形 C:一个普通(非拼接、无缩放)截图
48
48
 
49
49
  如果截图就是某视口的渲染结果,且截图宽 ≠ CSS 视口宽,说明有 `devicePixelRatio` 缩放。
50
- 此时标注坐标 = CSS 坐标 × (截图宽 / 视口 CSS 宽)(横向),(截图高 / 视口 CSS 高)(纵向)——等价于传 `--viewport "cssW,cssH"` 给 annotate.js 自动换算。
50
+ 此时标注坐标 = CSS 坐标 × (截图宽 / 视口 CSS 宽)(横向),(截图高 / 视口 CSS 高)(纵向)——等价于传 `--viewport "cssW,cssH"` 给 annotate.cjs 自动换算。
51
51
 
52
52
  ## 坐标捕获命令(在打开的浏览器标签页上执行)
53
53
 
@@ -72,9 +72,9 @@ fullPage 截图宽高 = 整页 CSS 宽高 × `devicePixelRatio`(DPR)。因
72
72
 
73
73
  上次截图若用了 `set viewport` 设视口,在 eval 里把 `window.innerWidth/Height` 作为 viewport 一并读出,确认截图宽高与之一致。
74
74
 
75
- ## 统一标注脚本 annotate.js
75
+ ## 统一标注脚本 annotate.cjs
76
76
 
77
- 标注由 `annotate.js`(本 skill 目录内的零依赖 Node 脚本)完成。它把截图 + 坐标转成自包含标注 HTML(截图 base64 内嵌、SVG 箭头/框/文字精确放在换算后的坐标上),再用无头浏览器渲染为 PNG。
77
+ 标注由 `annotate.cjs`(本 skill 目录内的零依赖 Node 脚本)完成。它把截图 + 坐标转成自包含标注 HTML(截图 base64 内嵌、SVG 箭头/框/文字精确放在换算后的坐标上),再用无头浏览器渲染为 PNG。
78
78
 
79
79
  脚本与 SKILL.md 位于同一 skill 目录(随 agent-rules 同步到各项目 `.claude/skills/screenshot-annotate/`)。调用时定位到该目录(在项目根目录下直接粘贴即可执行,禁止用 `BASH_SOURCE[0]`——那只在脚本文件内有效,粘贴到终端时为空):
80
80
 
@@ -82,7 +82,7 @@ fullPage 截图宽高 = 整页 CSS 宽高 × `devicePixelRatio`(DPR)。因
82
82
  # 从项目根目录定位 skill 目录(git rev-parse --show-toplevel 返回仓库根,与 loop-review 里 check-review.sh 的取法一致)
83
83
  ROOT="$(git rev-parse --show-toplevel)"
84
84
  SKILL_DIR="$ROOT/.claude/skills/screenshot-annotate"
85
- node "$SKILL_DIR/annotate.js" \
85
+ node "$SKILL_DIR/annotate.cjs" \
86
86
  shot.png shot-annot.html \
87
87
  --json '[{"x":412,"y":1240,"w":160,"h":48,"text":"新增的保存按钮","color":"#22c55e"}]' \
88
88
  --label "✅ 修复后" --labelColor "#f43f5e" --url "https://api-test.xxx/gateway/dashboard"
@@ -94,13 +94,13 @@ node "$SKILL_DIR/annotate.js" \
94
94
 
95
95
  ## 渲染标注 HTML 为 PNG(固定流程)
96
96
 
97
- 用 `SKILL_DIR/annotate.js` 生成标注 HTML 后,用无头浏览器渲染为 PNG。
97
+ 用 `SKILL_DIR/annotate.cjs` 生成标注 HTML 后,用无头浏览器渲染为 PNG。
98
98
 
99
99
  ```bash
100
100
  NS="claude-截图标注"
101
101
  # 1. 无头打开标注 HTML
102
102
  agent-browser --cdp 9226 --namespace "$NS" tab new "file:///<上一步生成的绝对路径>/shot-annot.html"
103
- # 2. 设视口为标注画布尺寸(annotate.js 会打印「画布尺寸 WxH」)
103
+ # 2. 设视口为标注画布尺寸(annotate.cjs 会打印「画布尺寸 WxH」)
104
104
  agent-browser --cdp 9226 --namespace "$NS" set viewport <W> <H>
105
105
  # 3. 全页截图
106
106
  agent-browser --cdp 9226 --namespace "$NS" screenshot --full shot-annot.png
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * annotate.js — 生成「标注 HTML」(零依赖,Node 内置模块实现)
3
+ * annotate.cjs — 生成「标注 HTML」(零依赖,Node 内置模块实现)
4
4
  *
5
5
  * 作用:把一张截图 + 若干标注坐标,生成一个自包含的 HTML 文件——
6
6
  * 截图 base64 内嵌为底图,SVG 在精确换算后的坐标上画
@@ -25,7 +25,7 @@
25
25
  * 把这两个和作为 x/y 传入(这就是整页里的 CSS 位置,配 --fullpage --dpr 使用)。
26
26
  *
27
27
  * 用法:
28
- * node annotate.js <image> <outHtml> \
28
+ * node annotate.cjs <image> <outHtml> \
29
29
  * --json '[{"x":412,"y":1240,"w":160,"h":48,"text":"新增的保存按钮","color":"#22c55e"}]' \
30
30
  * --label "✅ 修复后" [--labelColor "#f43f5e"] [--viewport "1440,900"] [--margin-frac 0.05]
31
31
  *
@@ -45,13 +45,13 @@
45
45
  * 文字版完整 URL(不得只靠地址栏),所以实际调用时都应传本参数
46
46
  *
47
47
  * 渲染为 PNG 的固定流程(配合 screenshot-annotate skill):
48
- * 1. 生成标注 HTML: node annotate.js shot.png shot-annot.html --json '...' --label '...'
48
+ * 1. 生成标注 HTML: node annotate.cjs shot.png shot-annot.html --json '...' --label '...'
49
49
  * 2. 无头打开并设视口为画布尺寸(画布比图片多四周留白):
50
50
  * AGENT_BROWSER --cdp 9226 --namespace <ns> tab new "file://<abs>/shot-annot.html"
51
51
  * AGENT_BROWSER --cdp 9226 --namespace <ns> set viewport <W> <H>
52
52
  * 3. 截图: AGENT_BROWSER --cdp 9226 --namespace <ns> screenshot --full shot-annot.png
53
53
  * 4. 关闭标签页: AGENT_BROWSER --cdp 9226 --namespace <ns> tab close <tabId>
54
- * (W/H = annotate.js 打印的「画布尺寸」,即图片宽/高 + 2×margin)
54
+ * (W/H = annotate.cjs 打印的「画布尺寸」,即图片宽/高 + 2×margin)
55
55
  * 本脚本与 SKILL.md 同目录(随 agent-rules 同步到各项目 .claude/skills/screenshot-annotate/)。
56
56
  */
57
57
  "use strict";
@@ -493,7 +493,7 @@ function main() {
493
493
  const [imagePath, outHtml] = positionals;
494
494
  if (!imagePath || !outHtml) {
495
495
  console.error(
496
- "用法: node annotate.js <image> <outHtml> [--json \"...\"] [--label \"...\"] [--labelColor \"#fff\"] [--viewport \"cssW,cssH\"] [--margin-frac N] [--url \"...\"]",
496
+ "用法: node annotate.cjs <image> <outHtml> [--json \"...\"] [--label \"...\"] [--labelColor \"#fff\"] [--viewport \"cssW,cssH\"] [--margin-frac N] [--url \"...\"]",
497
497
  );
498
498
  process.exit(1);
499
499
  }