Repository navigation
chore(ocr): 评审脚本移植 Windows/Git Bash 适配 #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,27 @@ | ||
| #!/bin/bash | ||
| # ocr 评审包装脚本(zcode-advisor) | ||
| # | ||
| # 平台适配(Windows / Git Bash 与新机,2026-10-08): | ||
| # ① 公共 git 目录定位(worktree 下 .git 是文件,写死会 ENOTDIR)② GNU timeout 优先 | ||
| # ③ python 优先于 WindowsApps 的 python3 壳 ④ Windows npm 全局路径补 PATH | ||
| # ⑤ timeout 退出码 124 计入「被强制终止」判定。 | ||
| # 这几项是**脚本级能力**:必须落在模板里,否则下次跑 ocr-sync-reviewer.sh 时 | ||
| # 会被整文件覆盖掉(standard 模式 = 模板内容直写)。 | ||
| # | ||
| # 设计(与 ~/.dsh/AGENTS.md 一致): | ||
| # - 评审工具**不进 CI**:模型网关是内网地址(192.168.50.139),GitHub runner 够不着; | ||
| # 拦截靠 CI 的确定性检查,评审只作为 advisory 关卡。 | ||
| # - 也**不定时跑**:只在每个 PR 合并前调用一次。 | ||
| # | ||
| # 模型降级链(provider|model,与 ~/.opencodereview/config.json 的 custom_providers 对应): | ||
| # ⚠️ 链的**唯一真源**是 ~/.dsh/templates/ocr-review/chain-public(运行时直接读它)。 | ||
| # 本文件里那段 CHAIN 数组只是读不到链文件时的**兜底快照**,别在这里找当前顺序—— | ||
| # 它必然滞后(改链不必改脚本,也就没人会记得同步它)。 | ||
| # 改链:编辑 chain-public 再跑 bash ~/.dsh/bin/ocr-sync-reviewer.sh 刷快照。 | ||
| # 这里不再逐条列举模型名与顺序:链外置的全部意义就是「枚举会过期」,再抄一份 | ||
| # 只会制造第二处会撒谎的注释(2026-10-06 实测:注释写着主棒 deepseek,运行时首跳 | ||
| # 其实是 chain-public 的 minimax-m3)。各模型的可用性/耗时/成本结论写在链文件里, | ||
| # 那才是它们生效的地方。 | ||
| # 【2026-10-04】隐私铁律暂缓(用户拍板):私有仓也可用本链(私有副本用 ocr-review.sh.private | ||
| # 变体,链走 chain-private,同样允许 cloud 条目)。 | ||
| # ⚠️ kimi-k3 不支持工具调用(三次探针无 tool_calls)→ 不能当 ocr 模型,勿加回。 | ||
| # 模型降级链:**唯一真源是 ~/.dsh/templates/ocr-review/chain-public**(本脚本下方的 | ||
| # CHAIN=(...) 只是读不到链文件时的兜底快照,由 ocr-sync-reviewer.sh 生成,勿手改)。 | ||
| # 此处不再逐条列举模型名与顺序——枚举必然过期(2026-10-06 实测:本注释曾写"主棒 | ||
| # deepseek",而运行时首跳早已是链文件里的另一个模型)。要改链请改链文件。 | ||
| # 【2026-10-04】隐私铁律暂缓(用户拍板):私有仓也可用云端条目(私有副本用 | ||
| # ocr-review.sh.private 变体,链走 chain-private,同样允许 cloud 条目)。 | ||
| # ⚠️ 同站 kimi-k3 不支持工具调用(2026-10-04 三次探针无 tool_calls)→ 不能当 ocr 模型,勿加回。 | ||
| # ⚠️ lucky / lucky-gem 需在 config.json 配 user_agent + headers.User-Agent,否则被 | ||
| # Cloudflare 按 UA 拦截返回 403(不是服务失效)。 | ||
| # 用法: | ||
| # ./scripts/ocr-review.sh # 默认基准 origin/dev(不存在则回退 main) | ||
| # ./scripts/ocr-review.sh v1.8.8 # 对比 tag / 分支 | ||
|
|
@@ -28,6 +32,13 @@ set -uo pipefail | |
| REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
| cd "$REPO_ROOT" || exit 1 | ||
|
|
||
| # 公共 git 目录:worktree 下 .git 是文件不是目录,写死会失败(AGENTS.md §11 第 11 条)。 | ||
| GIT_COMMON_DIR="$(git rev-parse --git-common-dir 2>/dev/null || echo .git)" | ||
| case "$GIT_COMMON_DIR" in | ||
| /*|?:*) ;; # 已是绝对路径(worktree 常见) | ||
| *) GIT_COMMON_DIR="$REPO_ROOT/$GIT_COMMON_DIR" ;; | ||
| esac | ||
|
|
||
| # ── 并发锁(实测教训)── | ||
| # pre-push hook 每次推送都会起一个评审,而完整评审要 5–15 分钟。 | ||
| # 连续推送(或 tag + main 分开推)会让多个评审同时跑:既争抢上游配额, | ||
|
|
@@ -38,7 +49,7 @@ cd "$REPO_ROOT" || exit 1 | |
| # 锁内含 pid + 起始时间;发现锁时: | ||
| # - 持锁进程仍活着 → 提示并退出(不重复评审) | ||
| # - 持锁进程已死(崩溃/被 kill)→ 视为陈旧锁,接管 | ||
| LOCK_DIR="$REPO_ROOT/.git/ocr-review.lock" | ||
| LOCK_DIR="$GIT_COMMON_DIR/ocr-review.lock" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| LOCK_INFO="$LOCK_DIR/info" | ||
| # 只有**自己拿到锁**才允许在退出时删锁。OCR_REVIEW_FORCE=1 整段跳过、根本不取锁, | ||
| # 若无条件删就会把另一个正在跑的评审的锁连根拔掉——而并行评审正是 FORCE 的用途, | ||
|
|
@@ -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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| 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" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| echo "[ocr-review] 如需强制并行,设 OCR_REVIEW_FORCE=1" | ||
| exit 0 | ||
| fi | ||
|
|
@@ -81,7 +92,10 @@ if ! command -v ocr >/dev/null 2>&1; then | |
| fi | ||
| # 再按版本号倒序扫(字典序会把 v10 排在 v20 之前,可能命中陈旧的全局安装) | ||
| if ! command -v ocr >/dev/null 2>&1; then | ||
| for candidate in $(ls -1d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -Vr) /usr/local/bin /opt/homebrew/bin; do | ||
| for candidate in \ | ||
| $(ls -1d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -Vr) \ | ||
| /usr/local/bin /opt/homebrew/bin \ | ||
| "${APPDATA:-$HOME/AppData/Roaming}/npm" "$HOME/AppData/Roaming/npm"; do | ||
| if [ -x "$candidate/ocr" ]; then | ||
| PATH="$candidate:$PATH" | ||
| break | ||
|
|
@@ -97,6 +111,19 @@ fi | |
|
|
||
| AGENT_MODEL_FILE="$HOME/.opencodereview/agent-model" | ||
|
|
||
| # python 解释器:Windows 上 python3 常是 WindowsApps 应用执行别名(跑起来只会弹应用商店), | ||
| # 故优先真实安装的 python;但内嵌脚本用了 f-string,**只接受 Python 3** —— 若 python 指向 | ||
| # Python 2 就跳过它继续找 python3,不能靠"命令存在"就认定可用。 | ||
| PY="" | ||
| for _cand in python python3; do | ||
| _p="$(command -v "$_cand" 2>/dev/null || true)" | ||
| [ -n "$_p" ] || continue | ||
| if "$_p" -c 'import sys; sys.exit(0 if sys.version_info[0] == 3 else 1)' 2>/dev/null; then | ||
| PY="$_p"; break | ||
| fi | ||
| done | ||
| [ -n "$PY" ] || { echo "错误:找不到可用的 Python 3 解释器(渲染评审结果需要)" >&2; exit 1; } | ||
|
|
||
| # 校验 ref 是否真实存在(避免把非法参数带进 ocr) | ||
| require_ref() { | ||
| local ref="$1" | ||
|
|
@@ -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=( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| "nas-hy4|deepseek-v4.1-flash" | ||
| "runanytime|z-ai/glm-5.2" | ||
| "nas-octopus|glm-5.3-flash" | ||
| "nas-hy4|hy3" | ||
| "nas-hy4|hy4-preview-f" | ||
| "nas-hy4|space-bunny" | ||
| "lucky-gem|gemini-3.6-flash" | ||
| "x666|ministral-14b-latest" | ||
|
Comment on lines
+174
to
176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| "runanytime-astra|gpt-6-astra" | ||
| "runanytime|z-ai/glm-5.3" | ||
| "lucky|stealth/space-bunny-alpha" | ||
| "lucky|step-5-preview" | ||
| "daigua|gpt-6-sol" | ||
| "nas-hy4|gemini-3.8-flash" | ||
| "nas-octopus|gemini-3.7-flash" | ||
| "nas-hy4|deepseek-v4.1-flash" | ||
| "nas-hy4|deepseek-v4.1-flash-sg" | ||
|
Comment on lines
+182
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| "nas-octopus|deepseek-v4-flash-0731" | ||
| "runanytime|z-ai/glm-5.2" | ||
| "runanytime|z-ai/glm-5.3" | ||
| "nas-hy4|glm-5.3-flash" | ||
| "nas-hy4|hy3" | ||
| "nas-hy4|hy4-preview-f" | ||
| ) | ||
| # 运行时优先读链文件:读不到 / 解析出 0 条 → 保留上面的快照(脚本被单独拷走也跑得起来)。 | ||
| _chain_found=() | ||
|
|
@@ -215,16 +247,9 @@ OUT_JSON="$(mktemp "${TMPDIR_OCR%/}/ocr-review-XXXXXX" 2>/dev/null)" || { | |
| } | ||
| [ -n "$OUT_JSON" ] || { echo "错误:mktemp 未返回路径" >&2; exit 1; } | ||
| ATTEMPT_LOG="" | ||
| # 注意:这个 trap 会覆盖前面的锁清理 trap,所以 rm -rf "$LOCK_DIR" 必须在这里再做一次, | ||
| # 否则锁目录永不消失,之后每次运行都误报"陈旧锁"(虽然能接管,但看着像坏了)。 | ||
| # OCR_REVIEW_RAW_OUT(可选,由调用方提供,例如 pre-push hook):把 ocr 的**原始 JSON** | ||
| # 另存一份。它是权威产物(下面的渲染表由它生成),而 OUT_JSON 只是 mktemp 临时文件、 | ||
| # 退出时被本函数删掉 —— 此前评审成功后原始 JSON 一并消失,事后无法复核或重渲染。 | ||
| # | ||
| # 两道守卫,缺一不可: | ||
| # - `$CHOSEN` 非空 = 确有一跳产出完整评审。**不能**只看 OUT_JSON 非空:链全失败时 | ||
| # OUT_JSON 里留着最后一跳的 partial/无效输出,照拷会把上一份好结论覆盖成半成品。 | ||
| # - 命中锁而提前退出的进程根本走不到这里(锁检查在 OUT_JSON 创建之前)。 | ||
| # 注意:这个 trap 会覆盖前面的锁清理 trap,所以锁回收必须在这里再做一次。 | ||
| # 但只在**自己持锁**时回收(LOCK_ACQUIRED)——FORCE=1 根本没取锁,无条件删会把 | ||
| # 并行评审的锁拆掉。 | ||
| cleanup() { | ||
| if [ -n "${OCR_REVIEW_RAW_OUT:-}" ] && [ -n "${CHOSEN:-}" ]; then | ||
|
Comment on lines
253
to
254
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| # 这里不再检查 OUT_JSON 非空:$CHOSEN 只在 valid_output 通过后赋值,而 valid_output | ||
|
|
@@ -289,7 +314,7 @@ trap cleanup EXIT INT TERM | |
| # 误拒完整结果、触发无谓重跑。覆盖完整性以 status 为准。 | ||
| valid_output() { | ||
| [ -s "$OUT_JSON" ] || return 1 | ||
| python3 - "$OUT_JSON" <<'PYCHECK' >/dev/null 2>&1 | ||
| "$PY" - "$OUT_JSON" <<'PYCHECK' >/dev/null 2>&1 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| import json, sys | ||
| try: | ||
| with open(sys.argv[1], encoding="utf-8") as fh: | ||
|
|
@@ -323,6 +348,17 @@ run_with_deadline() { | |
| local secs="$1"; shift | ||
| case "$secs" in ''|*[!0-9]*) "$@"; return $? ;; esac | ||
| [ "$secs" -gt 0 ] || { "$@"; return $?; } | ||
| # 新机(Windows Git Bash / 装了 coreutils 的机器)有 GNU timeout,优先用它: | ||
| # 语义清晰(超时退出码 124),且 MSYS 会一并带走子进程树。 | ||
| # | ||
| # ⚠️ 必须用 `timeout --version` 探测而不是 `command -v timeout`:Windows 自带 | ||
| # C:/Windows/System32/timeout.exe 是同名异物(等 N 秒的延时命令,不包装别的命令), | ||
| # 命中它会把 `timeout 900 ocr review …` 当成非法参数静默跑偏。 | ||
|
Comment on lines
+354
to
+356
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| # GNU 版有 --version,Windows 版没有(实测其退出码与输出均不同)。 | ||
| if timeout --version >/dev/null 2>&1; then | ||
| timeout "$secs" "$@" | ||
| return $? | ||
| fi | ||
|
Comment on lines
+351
to
+361
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+358
to
+361
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| set -m | ||
| "$@" & | ||
| local cmd_pid=$! | ||
|
|
@@ -391,7 +427,7 @@ for entry in "${CHAIN[@]}"; do | |
| break | ||
| fi | ||
| case "$HOP_RC" in | ||
| 137|143) echo " ⚠ 本跳到 ${HOP_LIMIT}s 上限被强制终止(退出码 ${HOP_RC}),降级到下一个" ;; | ||
| 124|137|143) echo " ⚠ 本跳到 ${HOP_LIMIT}s 上限被强制终止(退出码 ${HOP_RC}),降级到下一个" ;; | ||
|
Comment on lines
429
to
+430
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| esac | ||
| # 保留该次失败输出尾部,供全失败时排查(此前一律丢进 /dev/null) | ||
| if [ -n "$ATTEMPT_LOG" ]; then | ||
|
|
@@ -426,7 +462,7 @@ fi | |
| # { status, llm, message, summary, tool_calls, comments[], groups[], session_id, manifest, retry_report } | ||
| # comments[] 元素键:path, content, suggestion_code, existing_code, start_line, end_line, thinking, category, severity | ||
| # 渲染失败必须让脚本非零退出(set -e 未开启,故显式传递)。 | ||
| if ! python3 - "$OUT_JSON" "$CHOSEN" <<'PY' | ||
| if ! "$PY" - "$OUT_JSON" "$CHOSEN" <<'PY' | ||
| import json, sys | ||
|
|
||
| path, chosen = sys.argv[1], sys.argv[2] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在 Windows 环境下,
$REPO_ROOT与路径拼接时,可能存在反斜杠转义问题。例如,$REPO_ROOT/subdir在 Windows 中应为$REPO_ROOT\subdir。建议在拼接路径时使用cygpath工具或显式替换/为 `) 以确保兼容性。