Repository navigation
chore(ocr): 评审脚本移植 Windows/Git Bash 适配 - #14
Conversation
保留成熟版全部机制(锁所有权、原子发布、JSON 状态白名单、ref 校验),仅改平台差异: - 公共 git 目录定位(git rev-parse --git-common-dir):worktree 下 .git 是文件, 写死 .git 会让锁与报告路径失效(指南 §11 第 11 条) - GNU timeout 优先:Git Bash/coreutils 环境语义更清晰(超时码 124 并带走子进程树); 无 timeout 时仍走原进程组方案(macOS 兼容不变) - python 优先于 python3:Windows 的 python3 常是 WindowsApps 应用执行别名 - PATH 补 Windows npm 全局目录(%APPDATA%/npm) - 超时判定加入退出码 124
|
🔍 OpenCodeReview found 8 issue(s) in this PR.
📄
|
| GIT_COMMON_DIR="$(git rev-parse --git-common-dir 2>/dev/null || echo .git)" | ||
| case "$GIT_COMMON_DIR" in | ||
| /*|?:*) ;; | ||
| *) GIT_COMMON_DIR="$REPO_ROOT/$GIT_COMMON_DIR" ;; | ||
| esac |
| case "$GIT_COMMON_DIR" in | ||
| /*|?:*) ;; |
| # - 持锁进程仍活着 → 提示并退出(不重复评审) | ||
| # - 持锁进程已死(崩溃/被 kill)→ 视为陈旧锁,接管 | ||
| LOCK_DIR="$REPO_ROOT/.git/ocr-review.lock" | ||
| LOCK_DIR="$GIT_COMMON_DIR/ocr-review.lock" |
| @@ -55,7 +66,7 @@ if [ -z "${OCR_REVIEW_FORCE:-}" ]; then | |||
| [ -f "$LOCK_INFO" ] && HOLD_PID="$(sed -n 's/^pid=//p' "$LOCK_INFO" 2>/dev/null)" | |||
| if [ -n "$HOLD_PID" ] && kill -0 "$HOLD_PID" 2>/dev/null; then | |||
|
|
||
| # python 解释器:Windows 上 python3 常是 WindowsApps 应用执行别名(会打 shebang 警告), | ||
| # 优先用真实安装的 python;两者都没有时 python3 兜底(macOS 无 python 只有 python3)。 | ||
| PY="$(command -v python || command -v python3 || true)" |
| PY="$(command -v python || command -v python3 || true)" | ||
| [ -n "$PY" ] || { echo "错误:找不到 python 解释器(渲染评审结果需要)" >&2; exit 1; } |
| valid_output() { | ||
| [ -s "$OUT_JSON" ] || return 1 | ||
| python3 - "$OUT_JSON" <<'PYCHECK' >/dev/null 2>&1 | ||
| "$PY" - "$OUT_JSON" <<'PYCHECK' >/dev/null 2>&1 |
| # 新机(Windows Git Bash / 装了 coreutils 的机器)有 GNU timeout,优先用它: | ||
| # 语义清晰(超时退出码 124),且 MSYS 会一并带走子进程树。 | ||
| # 没有 timeout 时才走下面的进程组方案(macOS 自带 bash 3.2 的原始实现)。 | ||
| if command -v timeout >/dev/null 2>&1; then | ||
| timeout "$secs" "$@" | ||
| return $? | ||
| fi |
| case "$HOP_RC" in | ||
| 137|143) echo " ⚠ 本跳到 ${HOP_LIMIT}s 上限被强制终止(退出码 ${HOP_RC}),降级到下一个" ;; | ||
| 124|137|143) echo " ⚠ 本跳到 ${HOP_LIMIT}s 上限被强制终止(退出码 ${HOP_RC}),降级到下一个" ;; |
CI 评审(PR #14)指出 Windows 兼容性风险,逐条核实后确认为真问题: Windows 自带 C:/Windows/System32/timeout.exe 是同名异物(等 N 秒的延时命令, 不包装别的命令)。原判定用 `command -v timeout`,若 PATH 解析命中该 exe, `timeout 900 ocr review …` 会被它当成非法参数静默跑偏(实测:报「指定的超时(/T)值无效」 但退出码为 0,脚本无从察觉)。 改为 `timeout --version` 探测:GNU 版有该选项,Windows 版没有(实测两者行为可区分)。 其余 8 条 CI 意见经核实为误报: - 路径拼接/cygpath(3 条):Git Bash 下 MSYS 自动转换 / 与 \,实测锁与报告路径均正常 - kill -0 不可靠:实测对存活/已死进程判定均正确 - PY 变量注入/校验:command -v 无注入面;python 3.12 实测可用 - 超时码 124/137/143 不一致:已按平台分别覆盖,属设计意图
| if [ -n "$HOLD_PID" ] && kill -0 "$HOLD_PID" 2>/dev/null; then | ||
| echo "[ocr-review] 已有评审在进行(pid ${HOLD_PID}),本次跳过以免重复消耗。" | ||
| echo "[ocr-review] 已有报告:$REPO_ROOT/.git/ocr-review-last.txt" | ||
| echo "[ocr-review] 已有报告:$GIT_COMMON_DIR/ocr-review-last.txt" |
| "nas-hy4|space-bunny" | ||
| "x666|ministral-14b-latest" |
| "nas-hy4|deepseek-v4.1-flash" | ||
| "nas-hy4|deepseek-v4.1-flash-sg" |
| "nas-hy4|glm-5.3" | ||
| "nas-hy4|gpt-6-astra" | ||
| "nas-hy4|gemini-3.8-flash" | ||
| "nas-hy4|kimi-k2.8-preview" |
| # ⚠️ 必须用 `timeout --version` 探测而不是 `command -v timeout`:Windows 自带 | ||
| # C:/Windows/System32/timeout.exe 是同名异物(等 N 秒的延时命令,不包装别的命令), | ||
| # 命中它会把 `timeout 900 ocr review …` 当成非法参数静默跑偏。 |
| if timeout --version >/dev/null 2>&1; then | ||
| timeout "$secs" "$@" | ||
| return $? | ||
| fi |
原两次提交把适配写成对 main 的分叉副本,带来三个问题: - CHAIN 快照被手工重排为 14 条,与唯一真源 chain-public(16 条)不一致, 且含链文件标注已 failed / 必然超时的条目; - 缺 main 已有的 mktemp 防符号链接写穿修复、record_use 的 skip 判定; - 第 95 行用了字面 \n 而非换行续行。 本提交把该文件重建为「origin/main 原文 + 平台适配」,并同步进模板 (~/.dsh/templates/ocr-review/ocr-review.sh 与 .private,模板才是 standard 模式的复制源,改副本不落模板会在下次同步时被整文件覆盖): - 公共 git 目录定位(worktree 下 .git 是文件),锁与报告路径改走 GIT_COMMON_DIR; - PATH 候选补 Windows npm 全局目录,续行改为真换行; - Python 解释器探测带版本校验(拒绝 Python 2,内嵌脚本用 f-string); - GNU timeout 优先(用 --version 探测,避开 Windows 同名 timeout.exe); - 超时退出码 124 计入强制终止判定; - 链快照回到与 chain-public 一致的 16 条。
| @@ -144,17 +171,22 @@ AGENT_MODEL="$(tr -d '[:space:]' < "$AGENT_MODEL_FILE" 2>/dev/null || true)" | |||
| CHAIN_FILE="${OCR_REVIEW_CHAIN_FILE:-$HOME/.dsh/templates/ocr-review/chain-public}" | |||
| CHAIN_SCOPE="public" # public 副本:cloud 条目合法,但仍以链文件为唯一真源 | |||
| CHAIN=( | |||
| cleanup() { | ||
| if [ -n "${OCR_REVIEW_RAW_OUT:-}" ] && [ -n "${CHOSEN:-}" ]; then |
* fix(test): 超时退出码断言补 124,修 main 上的单元测试红灯 PR #14 给 run_with_deadline 加了「有 GNU timeout 就优先用它」的分支,脚本这边 case 已经改成 124|137|143,但测试的断言只写了 137|143: - macOS(本机开发环境)没有 timeout,走进程组回退,返回 143 → 本地全绿; - ubuntu-latest 有 GNU timeout,到点自杀返回 124 → main 上这个用例红。 实测复现:把 GNU timeout 的行为(自成进程组 + 整组 TERM→KILL + 返回 124)做成 PATH 上的替身,再用旧断言跑,报的错与 CI 一字不差: actual: ' ⚠ 本跳到 2s 上限被强制终止(退出码 124),降级到下一个\n…' expected: /上限被强制终止(退出码 (137|143))/ error: '超时必须明说并带上退出码' 脚本的分类没错(124 确实是 GNU timeout 到点自杀的码),错的是断言把平台的 具体退出码写死了。改成只要求「是脚本认定为被强制终止的那组码」,并补注释说明 两条实现路径各返回什么。两条路径本机都实测通过(原 143 路径 28/28;注入 GNU timeout 替身后 28/28,含孙进程回收断言)。 * ci: PR 也跑单元测试(Linux 差异此前只在合并后才暴露) build-installers 原先只在 push main / tag / 手动触发时跑,PR 上只有 ocr-review 的 advisory 评审,不跑 npm test —— 所以「本机全绿、Linux 红」这类差异会一路 合进 main 才亮灯。PR #14 和 #15 就是这么各红了一次(两次都是同一个用例)。 构建类 job 都带 `startsWith(github.ref,'refs/tags/v') || event_name == 'workflow_dispatch'` 守卫,加 pull_request 后 PR 上只剩 test 这一个 job。 顺带修掉文件头一句不实的注释(原先写「此前只有 tag 与 PR 触发测试」,实际从 来没有过 pull_request 触发,git log 三个版本都没有)。
变更
在成熟版(origin/main)基础上仅移植平台适配,不改任何评审逻辑:
git rev-parse --git-common-dir定位锁与报告.git是文件不是目录,写死会失效(AGENTS.md §11-11)%APPDATA%/npm保留的成熟机制:锁所有权(LOCK_ACQUIRED)、原子发布(mktemp + 同目录 mv)、JSON status 白名单(只认 complete)、ref 类型校验(
^{commit})、空树兜底、防符号链接写穿。复审后重做(2b548b2)
首版把适配写成对 main 的分叉副本,带来三个问题,已在本提交修正:
chain-public(16 条)不一致,且含链文件已标注 failed / 必然超时的条目。→ 快照回到与链文件一致的 16 条(该区块由ocr-sync-reviewer.sh生成,勿手改)。cleanup()的 mktemp 防符号链接写穿、record_use的skip判定(避免"今天没评审"被统计成"provider 全挂")。→ 本文件现在是「main 原文 + 适配」,是 main 的超集而非分叉。\n而非换行续行,循环会多吐出伪候选词n。→ 改为真换行续行。Python 探测也比首版更严:不是"命令存在就用",而是逐个候选跑
sys.version_info[0] == 3校验——内嵌脚本用了 f-string,指向 Python 2 会直接崩。落点:模板才是源头
scripts/ocr-review.sh在本仓是ocr-sync-reviewer.shstandard 模式的再生副本(整文件由模板覆盖)。所以适配不能只改仓库副本,否则下次同步就被冲掉。本 PR 的改动同时写入:~/.dsh/templates/ocr-review/ocr-review.sh(public)~/.dsh/templates/ocr-review/ocr-review.sh.private(private)已实跑同步器验证幂等:第二次同步报「已是最新」。
实测(本机 macOS)
bash -n通过;node --test test/ocr-review.test.js→ 28/28 passnpm test→ 594 tests / 593 pass / 1 skipped / 0 failbash scripts/ocr-review.sh HEAD~1 HEAD→ 首跳 nas-hy4/space-bunny,44s 成功,0 条意见e09729333e8e7865…)说明
评审 advisory、不拦门。CI 的
code-review是 advisory 关卡,main无分支保护。