review-deep
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese<!-- markdownlint-disable-file MD041 -->
STARTER_CHARACTER = 🕵️♂️
<!-- markdownlint-disable-file MD041 -->
STARTER_CHARACTER = 🕵️♂️
Code Review Command
代码审查命令
Review a codebase from 9 perspectives against 3 models. The repo is cut into components of at most 25 files, and each of the 27 perspective and model pairs reviews every component, so every file gets all 27 reviews. Each arm is its own process that works in its own copy of the reviewed commit, navigates it with Serena, and writes its own findings. A sweep per pair then looks for defects the component boundaries hid.
opencode runNothing is packed. There is no orchestrator process and no fan-out. A reviewer reads the files of the reviewed commit, so a citation points at a real line in a real file. No reviewer reads the live repository.
taskOne runs alongside them. It reviews every reviewable file in its own conversation with a generic checklist, which makes it an independent fourth source.
ocr scan使用 3 个模型从 9 个视角审查代码库。仓库按最多 25 个文件划分为组件,每个组件都会接受 27 个“视角与模型”组合的审查,因此每个文件都会得到全部 27 次审查。每个 arm 都是独立的 进程,在被审查 commit 自己的副本中工作,通过 Serena 导航,并写出自己的发现项。随后,每个组合都会执行一次 sweep,查找因组件边界而隐藏的缺陷。
opencode run不采用打包方式。没有 orchestrator 进程,也没有 fan-out。审查器读取的是被审查 commit 的文件,因此引用会指向真实文件中的真实行。没有任何审查器读取实时仓库。
task一个 与它们并行运行。它在独立对话中依据通用检查清单审查每个可审查文件,因此构成独立的第四个来源。
ocr scanArguments
参数
If the user provided a path with the invocation, treat it as the target directory relative to the repo root. Otherwise review the whole repo. That is the deliberate default, already accepted by whoever invoked this skill, not a decision to re-confirm.
This skill runs , dispatched as a subagent with no user present to answer a prompt. Never pause before Step 2 to confirm scope, cost, or model spend. Proceed straight through Step 1 into dispatch. If cost matters, that call was made before invocation, by passing a narrower path.
context: fork如果用户在调用时提供了路径,则将其视为相对于仓库根目录的目标目录。否则审查整个仓库。这是有意设定的默认行为,调用此 skill 的人已经接受该行为,因此无需再次确认范围。
此 skill 通过 运行,作为没有用户在场回答 prompt 的子代理进行调度。绝不要在第 2 步之前暂停以确认范围、成本或模型支出。直接从第 1 步进入调度。如果成本很重要,应在调用之前通过传入更窄的路径作出决定。
context: forkModels
模型
| Label | Model ID | Variant |
|---|---|---|
| openai | openai/gpt-6-sol | |
| gemini | google/gemini-3.8-flash | |
| claude | az-anthropic/claude-opus-5-5 | xhigh |
The ocr coverage pass uses the provider and model in .
~/.opencodereview/config.jsonEvery ID must exist in under the matching provider and in that provider's . An ID missing from either fails that arm with , which surfaces only as an event in the NDJSON.
~/.config/opencode/opencode.jsonwhitelistModel not founderrorbash
jq -r '.provider | to_entries[] | .key as $p | .value.models | keys[] | "\($p)/\(.)"' ~/.config/opencode/opencode.jsonThe gemini arm runs a Flash model on purpose. Google publishes no pro above , so the newest Google model available is , seven minor versions ahead of the newest pro. Measured on one security review of a 1070-file repo, flash returned 11 findings against pro's 6 on the same agent and prompt, at roughly twice the tokens. That is a single comparison, not a benchmark.
gemini-3.1-pro-previewgemini-3.8-flash| 标签 | 模型 ID | 变体 |
|---|---|---|
| openai | openai/gpt-6-sol | |
| gemini | google/gemini-3.8-flash | |
| claude | az-anthropic/claude-opus-5-5 | xhigh |
ocr 覆盖率检查使用 中配置的 provider 和 model。
~/.opencodereview/config.json每个 ID 都必须存在于 中对应 provider 下,并且必须位于该 provider 的 中。如果 ID 未在其中任一处出现,该 arm 会以 失败;这只会作为 NDJSON 中的 事件呈现。
~/.config/opencode/opencode.jsonwhitelistModel not founderrorbash
jq -r '.provider | to_entries[] | .key as $p | .value.models | keys[] | "\($p)/\(.)"' ~/.config/opencode/opencode.jsongemini arm 有意使用 Flash 模型。Google 没有发布高于 的 pro,因此可用的最新 Google 模型是 ,它比最新的 pro 高七个次版本。在一个包含 1070 个文件的仓库的一次安全审查中实测,flash 使用相同 agent 和 prompt 返回了 11 个发现项,而 pro 返回 6 个,token 消耗约为两倍。这只是一次对比,并非 benchmark。
gemini-3.1-pro-previewgemini-3.8-flashThe rule that cost two days to learn
花了两天才弄明白的规则
Redirect stdin from on every . Without it the process blocks before creating a session, writes zero bytes, logs nothing past , and exits only when something kills it. reads stdin to EOF before it does anything else, so any launcher that leaves stdin open hangs it, and a background job, a CI step, and an agent harness all do. This is anomalyco/opencode issue #38723, open against 1.18.25 and reproduced here on 1.18.20.
/dev/nullopencode runmessage=initopencode runThe hang looks intermittent and is not. Whether the launcher closes stdin decides it. Three reporters measured it deterministically across macOS arm64, Linux aarch64, and Windows, 5 of 5 hangs without the redirect against 5 to 10 of 10 successes with it.
An earlier version of this file banned wrapping in and blamed a 44 percent failure rate on the wrapper. That was wrong. The stall reproduces with no anywhere, and upstream's own reproduction script uses . Removing every bound is what turned a one-line stdin bug into a multi-day investigation, so Step 2b now checks each reviewer for a session row.
opencode runtimeouttimeouttimeout 120Impose no token cap and no turn cap on a reviewer. A review of a large repo takes as long as it takes.
Serena is the only MCP server the arms get. Step 1's starts it with , and points at the target so Serena's walk finds the project. Serena reads its settings from , by default, which holds its and downloaded language servers.
opencode_env.py--project-from-cwd--dirSERENA_HOME~/.serena-reviewerserena_config.yml每次运行 时,都必须将 stdin 从 重定向。否则,进程会在创建 session 前阻塞,不写入任何字节,不记录 之后的任何日志,并且只有在某个进程将其终止时才会退出。 在执行其他操作前会一直读取 stdin 直到 EOF,因此,任何保持 stdin 处于打开状态的启动器都会使它挂起,而后台作业、CI 步骤和 agent harness 都会出现这种情况。这是 anomalyco/opencode issue #38723;该 issue 针对 1.18.25 仍处于开放状态,并已在此处的 1.18.20 上复现。
opencode run/dev/nullmessage=initopencode run这种挂起看起来像是间歇性故障,但实际上并非如此。是否挂起完全取决于启动器是否关闭 stdin。三名报告者在 macOS arm64、Linux aarch64 和 Windows 上进行了确定性测试:不重定向时 5 次全部挂起,进行重定向后 5 到 10 次全部成功。
此文件的早期版本禁止将 包装在 中,并将 44% 的失败率归咎于该包装器。这一判断是错误的。即使任何地方都没有 ,阻塞问题仍会复现,而且上游自己的复现脚本也使用了 。移除所有时间限制后,一个单行 stdin 缺陷变成了持续数天的调查,因此第 2b 步现在会检查每个审查器是否拥有 session 记录。
opencode runtimeouttimeouttimeout 120不要对审查器施加 token 上限或轮次上限。审查大型仓库需要多长时间就应让它运行多长时间。
Serena 是 arm 唯一可用的 MCP server。第 1 步的 使用 启动它,而 指向目标目录,使 Serena 能够找到该项目。Serena 从 读取设置,默认位置为 ,其中保存 和已下载的语言服务器。
opencode_env.py--project-from-cwd--dirSERENA_HOME~/.serena-reviewerserena_config.ymlWhy researcher is disabled
禁用 researcher 的原因
The arms never load the operator's MCP servers, so cannot reach them. It is worth knowing why it once broke them: Google rejects its schemas.
researcherresearcher["null","array"]any_ofitemsany_of[0].items: missing fielderrorAny MCP server using that pattern breaks a Google arm the same way. If a gemini arm starts failing with , find the server owning the named properties and disable it.
field predicate failed: $type == Type.ARRAYarm 绝不会加载操作者的 MCP server,因此 无法访问它们。值得说明的是它过去为何会导致 arm 失败:Google 会拒绝其 schema。
researcherresearcher["null","array"]any_ofitemsany_of[0].items: missing fielderror任何使用这种模式的 MCP server 都会以相同方式破坏 Google arm。如果 gemini arm 开始因 而失败,请找到拥有相关命名属性的 server 并将其禁用。
field predicate failed: $type == Type.ARRAYProcedure
流程
Step 1: Resolve Target and Prepare
第 1 步:解析目标并进行准备
bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
PROJECT_ROOT=$(cd -P "$PROJECT_ROOT" && pwd -P)
TARGET_PATH="<user-provided path or empty>"
[ -z "$TARGET_PATH" ] && TARGET_PATH="$PROJECT_ROOT"
TARGET_PATH=$(cd -P "$TARGET_PATH" 2>/dev/null && pwd -P) || { echo "TARGET_PATH does not exist"; exit 1; }
case "$TARGET_PATH" in
"$PROJECT_ROOT"|"$PROJECT_ROOT"/*) ;;
*) echo "TARGET_PATH escapes PROJECT_ROOT"; exit 1 ;;
esac
TARGET_NAME=$(basename "$TARGET_PATH")
[ "$TARGET_PATH" = "$PROJECT_ROOT" ] && TARGET_NAME="repo"
S="${CLAUDE_SKILL_DIR}/scripts" # replace ${CLAUDE_SKILL_DIR} with this SKILL.md's own directory on a non-Claude harness
[ -f "$S/init_run.py" ] || { echo "scripts not found at $S"; exit 1; }
python3 "$S/preflight.py" --tier deep || exit 1
STATE_DIR="$PROJECT_ROOT/.llmtmp/review-deep"
mkdir -p "$STATE_DIR"
find "$STATE_DIR" -mindepth 1 -delete
mkdir -p "$STATE_DIR/components" "$STATE_DIR/coverage" "$STATE_DIR/parts"
INIT=$(python3 "$S/init_run.py" "$PROJECT_ROOT" "$TARGET_PATH" "$STATE_DIR") || exit 1
echo "$INIT"
RUN_DIR=$(printf '%s\n' "$INIT" | sed -n 's/^RUN_DIR=//p')
EXPORT=$(python3 "$S/export.py" "$RUN_DIR" | sed -n 's/^EXPORT=//p')
[ -d "$EXPORT" ] || { echo "export.py made no export"; exit 1; }
SCOPE=""
[ "$TARGET_PATH" != "$PROJECT_ROOT" ] && SCOPE="--path ${TARGET_PATH#"$PROJECT_ROOT"/}"
ocr scan --preview --format json --repo "$EXPORT" $SCOPE 2>/dev/null > "$STATE_DIR/ocr-preview.json"
jq -r '.files[] | select(.will_review) | .path' "$STATE_DIR/ocr-preview.json" > "$STATE_DIR/ledger.txt"
jq -r '.files[] | select(.will_review | not) | "\(.path)\t\(.exclude_reason)"' "$STATE_DIR/ocr-preview.json" > "$STATE_DIR/skipped.txt"
python3 "$S/partition.py" "$STATE_DIR"
python3 "$S/opencode_env.py" "$STATE_DIR" || exit 1
echo "PROJECT_ROOT=$PROJECT_ROOT"
echo "TARGET_PATH=$TARGET_PATH"
echo "TARGET_NAME=$TARGET_NAME"
echo "STATE_DIR=$STATE_DIR"
echo "EXPORT=$EXPORT"
echo "LEDGER=$(wc -l < "$STATE_DIR/ledger.txt" | tr -d ' ') reviewable, $(wc -l < "$STATE_DIR/skipped.txt" | tr -d ' ') skipped"preflight.py --tier deep$Sinit_run.py${XDG_CACHE_HOME:-~/.cache}/review-deep/<repo>-<commit12>RUN_DIRrun.jsonexport.pygit archiverun.jsonjudge_excludeCLAUDE.mdAGENTS.md.claude/.opencode/.llmdocs/cp -Rocr scan --previewledger.txtskipped.txtbinaryunsupported_extpartition.pyopencode_env.py$STATE_DIR/opencode-config/opencode/opencode.jsonproviderenabled_providers~/.config/opencode/opencode.jsonopencode/serena/agents/reviewer-<area>.md$STATE_DIR/parts$STATE_DIR/coveragefind -deleterm -fsafe-rmPATH-fbash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
PROJECT_ROOT=$(cd -P "$PROJECT_ROOT" && pwd -P)
TARGET_PATH="<user-provided path or empty>"
[ -z "$TARGET_PATH" ] && TARGET_PATH="$PROJECT_ROOT"
TARGET_PATH=$(cd -P "$TARGET_PATH" 2>/dev/null && pwd -P) || { echo "TARGET_PATH does not exist"; exit 1; }
case "$TARGET_PATH" in
"$PROJECT_ROOT"|"$PROJECT_ROOT"/*) ;;
*) echo "TARGET_PATH escapes PROJECT_ROOT"; exit 1 ;;
esac
TARGET_NAME=$(basename "$TARGET_PATH")
[ "$TARGET_PATH" = "$PROJECT_ROOT" ] && TARGET_NAME="repo"
S="${CLAUDE_SKILL_DIR}/scripts" # replace ${CLAUDE_SKILL_DIR} with this SKILL.md's own directory on a non-Claude harness
[ -f "$S/init_run.py" ] || { echo "scripts not found at $S"; exit 1; }
python3 "$S/preflight.py" --tier deep || exit 1
STATE_DIR="$PROJECT_ROOT/.llmtmp/review-deep"
mkdir -p "$STATE_DIR"
find "$STATE_DIR" -mindepth 1 -delete
mkdir -p "$STATE_DIR/components" "$STATE_DIR/coverage" "$STATE_DIR/parts"
INIT=$(python3 "$S/init_run.py" "$PROJECT_ROOT" "$TARGET_PATH" "$STATE_DIR") || exit 1
echo "$INIT"
RUN_DIR=$(printf '%s\n' "$INIT" | sed -n 's/^RUN_DIR=//p')
EXPORT=$(python3 "$S/export.py" "$RUN_DIR" | sed -n 's/^EXPORT=//p')
[ -d "$EXPORT" ] || { echo "export.py made no export"; exit 1; }
SCOPE=""
[ "$TARGET_PATH" != "$PROJECT_ROOT" ] && SCOPE="--path ${TARGET_PATH#"$PROJECT_ROOT"/}"
ocr scan --preview --format json --repo "$EXPORT" $SCOPE 2>/dev/null > "$STATE_DIR/ocr-preview.json"
jq -r '.files[] | select(.will_review) | .path' "$STATE_DIR/ocr-preview.json" > "$STATE_DIR/ledger.txt"
jq -r '.files[] | select(.will_review | not) | "\(.path)\t\(.exclude_reason)"' "$STATE_DIR/ocr-preview.json" > "$STATE_DIR/skipped.txt"
python3 "$S/partition.py" "$STATE_DIR"
python3 "$S/opencode_env.py" "$STATE_DIR" || exit 1
echo "PROJECT_ROOT=$PROJECT_ROOT"
echo "TARGET_PATH=$TARGET_PATH"
echo "TARGET_NAME=$TARGET_NAME"
echo "STATE_DIR=$STATE_DIR"
echo "EXPORT=$EXPORT"
echo "LEDGER=$(wc -l < "$STATE_DIR/ledger.txt" | tr -d ' ') reviewable, $(wc -l < "$STATE_DIR/skipped.txt" | tr -d ' ') skipped"preflight.py --tier deep$Sinit_run.py${XDG_CACHE_HOME:-~/.cache}/review-deep/<repo>-<commit12>RUN_DIRrun.jsonexport.pygit archiverun.jsonjudge_excludeCLAUDE.mdAGENTS.md.claude/.opencode/.llmdocs/cp -Rocr scan --previewledger.txtskipped.txtbinaryunsupported_extpartition.pyopencode_env.py$STATE_DIR/opencode-config/opencode/opencode.json~/.config/opencode/opencode.jsonproviderenabled_providersopencode/serena/agents/reviewer-<area>.md$STATE_DIR/parts$STATE_DIR/coverage使用 而不是 。 上的 shim 会将路径移至 Trash,并且在路径不存在时即使使用 也会返回非零状态,从而导致首次运行时无法完成清理。
find -deleterm -fPATHsafe-rm-fStep 2: Dispatch Component Reviewers and the Coverage Pass
第 2 步:调度组件审查器并执行覆盖率检查
Each of the 27 area and model pairs reviews every component, so a run has 27 arms per component. A worker pool runs them at a time, beside one ocr scan. Substitute the values Step 1 printed.
CONCURRENCYRun this block as one background Bash call, as written. The call stays open until the pool and the ocr scan exit, and its completion notification is the signal that Step 2 has finished. Never launch the block with , a trailing , or a wrapper script started in the background, because the call then returns at once and no notification ever comes. Never watch a log for the line. On a quartermaster run the agent detached the block and waited with . exited on the match, but exits only when it next writes, and is the last line the log gets. The call sat idle until its 60-minute killed .
nohup&done,tail -f | grep -m1 '^done,'greptaildone,timeouttailbash
export PROJECT_ROOT TARGET_PATH STATE_DIR EXPORT
AREAS="security architecture solid correctness testing ops performance quality data"
MODELS="openai:openai/gpt-6-sol gemini:google/gemini-3.8-flash claude:az-anthropic/claude-opus-5-5"
CONCURRENCY=27
cat > "$STATE_DIR/arm.sh" <<'ARM'
#!/bin/bash
label=$1 model=$2 area=$3 comp=$4
if [ ! -d "$STATE_DIR/parts" ] || [ ! -d "$TARGET_PATH" ] || [ ! -d "$PROJECT_ROOT" ] || [ ! -d "$EXPORT" ]; then
echo "arm.sh: STATE_DIR='$STATE_DIR' TARGET_PATH='$TARGET_PATH' PROJECT_ROOT='$PROJECT_ROOT' EXPORT='$EXPORT'. Export all four with the values Step 1 printed." >&2
exit 255
fi
VARIANT=""
[ "$label" = claude ] && VARIANT="--variant xhigh"
base="$label-$area-$comp"
arm_dir=$(mktemp -d "${TMPDIR:-/tmp}/review-deep-arm.XXXXXX")
trap 'rm -rf "$arm_dir"' EXIT
copy="$(cd -P "$arm_dir" && pwd -P)/repo"
cp -R "$EXPORT" "$copy"
work="$copy/.review-arm"
mkdir -p "$work" "$STATE_DIR/arms"
echo "$copy" > "$STATE_DIR/arms/$base.root"
target="$copy${TARGET_PATH#"$PROJECT_ROOT"}"
if [ "$comp" = sweep ]; then
cp "$STATE_DIR/ledger.txt" "$work/ledger.txt"
cp "$STATE_DIR/$label-$area.md" "$work/listed-findings.md"
scope="LEDGER_PATH: $work/ledger.txt
FINDINGS_PATH: $work/listed-findings.md
LEDGER_PATH lists every reviewable source file. A component-by-component
review already covered them, and its findings are in FINDINGS_PATH. Look only
for defects that are not already listed there, especially defects that span
components. Do not repeat a listed finding."
else
cp "$STATE_DIR/components/$comp.txt" "$work/ledger.txt"
scope="LEDGER_PATH: $work/ledger.txt
COVERAGE_PATH: $work/coverage.txt
LEDGER_PATH lists the source files assigned to you, one path per line,
relative to $copy. Read it first and review every file it lists. The rest of
the repository is context: follow calls into it, and report defects in the
listed files.
For every path in LEDGER_PATH, write one line to COVERAGE_PATH: the path, then
reviewed or skipped, then a short reason. Every listed path must appear
exactly once before you finish."
fi
prompt="Review the source files under $target that your ledger assigns.
Navigate with serena symbolic tools: get_symbols_overview to map a file,
find_symbol to read a definition, find_referencing_symbols to find callers,
find_declaration and find_implementations to resolve a usage. Read a whole
file only when symbolic navigation cannot answer the question. Everything
you need is under $copy.
Cite path:line with the path relative to $copy, and name the enclosing
function, method, or type. A line you saw counts as verified, and find_symbol
returns a symbol's line range. When you cannot verify the exact line, cite the
nearest line you saw and end the finding with \`line unconfirmed\`. Never drop
a real defect because its line number is uncertain.
$scope
OUTPUT_PATH: $work/findings.md
Write your findings to OUTPUT_PATH. Writing that file is mandatory and is
how your work is delivered. Do not return findings as your response."
raw="$STATE_DIR/parts/raw-$base.ndjson"
wrote_findings() {
[ -s "$work/findings.md" ] ||
jq -r 'select(.type=="text") | .part.text' "$raw" 2>/dev/null | grep -q '^## '
}
mkdir -p "$STATE_DIR/opencode-db" "$STATE_DIR/parts/attempts"
attempt=1
max_attempts=3
while :; do
XDG_CONFIG_HOME="$STATE_DIR/opencode-config" \
OPENCODE_PURE=1 \
OPENCODE_DISABLE_EXTERNAL_SKILLS=1 \
OPENCODE_DISABLE_CLAUDE_CODE=1 \
OPENCODE_DISABLE_PROJECT_CONFIG=1 \
OPENCODE_DISABLE_AUTOUPDATE=1 \
OPENCODE_DB="$STATE_DIR/opencode-db/$base.db" \
opencode run \
--agent "reviewer-$area" \
-m "$model" $VARIANT \
--format json \
--dir "$target" \
--title "Review $label $area $comp" \
"$prompt" \
< /dev/null \
> "$raw" 2> "$STATE_DIR/parts/$base.log"
if wrote_findings && ! grep -q '"Failed to execute statement"' "$raw" 2>/dev/null; then break; fi
[ "$attempt" -ge "$max_attempts" ] && break
mv "$raw" "$STATE_DIR/parts/attempts/raw-$base.attempt$attempt.ndjson"
sleep $((5 + RANDOM % 15))
attempt=$((attempt + 1))
done
[ -s "$work/findings.md" ] && sed "s|$copy/||g" "$work/findings.md" > "$STATE_DIR/parts/$base.md"
[ -s "$work/coverage.txt" ] && sed "s|$copy/||g" "$work/coverage.txt" > "$STATE_DIR/coverage/$base.txt"
wrote_findings || { echo "arm.sh: $base wrote no findings in $attempt attempts" >&2; exit 1; }
ARM
: > "$STATE_DIR/tasks.txt"
for comp in $(ls "$STATE_DIR/components" | sed 's/\.txt$//'); do
for entry in $MODELS; do
for area in $AREAS; do
echo "${entry%%:*} ${entry#*:} $area $comp" >> "$STATE_DIR/tasks.txt"
done
done
done
SCOPE=""
[ "$TARGET_PATH" != "$PROJECT_ROOT" ] && SCOPE="--path ${TARGET_PATH#"$PROJECT_ROOT"/}"
OCR_COPY="$(mktemp -d "${TMPDIR:-/tmp}/review-deep-ocr.XXXXXX")/repo"
cp -R "$EXPORT" "$OCR_COPY"
ocr scan --audience agent --repo "$OCR_COPY" $SCOPE --max-tokens 1000000 \
--format json --output "$STATE_DIR/ocr-scan.json" \
< /dev/null > /dev/null 2> "$STATE_DIR/ocr-scan.log" &
echo $! > "$STATE_DIR/ocr.pid"
echo "dispatching $(wc -l < "$STATE_DIR/tasks.txt" | tr -d ' ') arms, $CONCURRENCY at a time, plus 1 ocr scan"
xargs -P "$CONCURRENCY" -L 1 bash "$STATE_DIR/arm.sh" < "$STATE_DIR/tasks.txt"
wait
rm -rf "$(dirname "$OCR_COPY")"
echo "done, $(ls "$STATE_DIR"/parts/*.md 2>/dev/null | wc -l | tr -d ' ') part files written"xargs -Ptasks.txtparts/*.mdCONCURRENCY=27opencodeReviewers run as an appliance. points opencode at the config Step 1 generated, so none of the operator's opencode agents, skills, rules, plugins, or MCP servers load. Credentials still resolve, because opencode keeps under and providers read keys from environment variables. skips external plugins, skips the skill scans under and , skips and , and skips the reviewed repo's , , and . Each was measured against opencode 1.18.20 with , , and a planted instruction the model was asked to find.
XDG_CONFIG_HOMEauth.jsonXDG_DATA_HOMEOPENCODE_PURE=1OPENCODE_DISABLE_EXTERNAL_SKILLS=1~/.claude~/.agentsOPENCODE_DISABLE_CLAUDE_CODE=1~/.claude/CLAUDE.md.claude/skillsOPENCODE_DISABLE_PROJECT_CONFIG=1opencode.json.opencode/AGENTS.mdopencode debug configopencode debug skillEach arm runs in a copy of the export under , with inside it. puts the arm's ledger, and for a sweep that area's merged findings, in the copy's . The arm writes its findings and coverage record there with , or on an OpenAI model. copies them to and , strips the copy's path from citations, and deletes the copy on exit. The generated agents deny every tool and then allow , , , the write tools, and Serena minus its four write families. One shared rule denies every path outside the arm's own copy, and an rule allows writes only under . Measured on opencode 1.18.20: given an absolute path outside the copy, , and were refused, and inside the copy worked.
$TMPDIR/review-deep-arm.*--dirarm.sh.review-arm/writeapply_patcharm.shparts/coverage/readgrepglobexternal_directoryedit.review-arm/grepglobreadgrepSerena runs as its own process, so opencode's rules do not reach it. Measured on Serena 1.7.0: it refused a relative path that leaves the copy and an absolute directory path, but , , and answered for an absolute file path outside the copy. An arm that knows or guesses a file path elsewhere on the machine can still read that file's symbols through Serena. It resolved a module inside the copy, across the module boundary. They run as , because opencode ignores a subagent's block when the agent is selected with .
find_symbolget_symbols_overviewget_diagnostics_for_filego.workmode: primarytools--agentA reviewer must not call . Two of 27 reviewers on a 1070-file repo spawned a subagent that never returned, and runs a foreground subagent with a blocking , so the parent hung and Step 2's hung with it. The agents set and . After the fix both reviewers completed with zero calls.
taskTaskToolyieldwaittools.task: falsepermission.task: {"*": deny}taskNothing guards against two runs at once. Step 1 wipes and every arm writes to fixed paths, so a second invocation against the same repo destroys the first. Check for an in-flight run before dispatching.
STATE_DIRProvider limits are not the constraint. OpenAI reports 40,000,000 tokens per minute and Azure Foundry 15,000,000.
opencode's own session store can die under write pressure at . Measured on a 1217-file repo: 26 arms failed in one four-second burst with against opencode's shared SQLite store (visible in , not in the arm's own stderr, which stays empty). 25 of the 26 wrote no output at all. Confirmed upstream (anomalyco/opencode#48416, #47566, #38849, #33320, #21215): opencode's SQLite connection runs , so a writer that loses the race for the write lock fails immediately instead of waiting, and on the SQL adapter turns that into a fatal message failure instead of a warning. The failing write fires from a exit handler right after any concurrent process's tool call returns, so it tracks how many opencode processes are alive and busy at that moment, not which ones just launched.
CONCURRENCY=27error="Failed to execute statement"~/.local/share/opencode/log/opencode.logbusy_timeout=0Effect.orDiechild_processarm.shOPENCODE_DB="$STATE_DIR/opencode-db/$base.db"OPENCODE_DB~/.local/share/opencode/opencode.dbauth.jsonbin/The retry loop re-runs the same invocation up to twice more in two cases. The first is a match for in the arm's NDJSON, kept as defense in depth for whatever the per-arm database leaves uncovered. The second is an arm that ends with no findings, meaning is empty and no text event carries a heading. Measured on a 718-file repo, 20 of 351 gemini arms read files, made no write call, and ended on a step with 0 output tokens. None of the 331 gemini arms that wrote findings ended that way. Attempts are separated by 5 to 19 seconds of jittered sleep so retries don't restack into the same contention window. A superseded attempt's NDJSON moves to and is never deleted. That directory sits outside the glob Step 3 extracts from. An arm with no findings after 3 attempts exits 1 with its name on stderr and surfaces as an ordinary entry in Step 3.
opencode rungrep"Failed to execute statement"OUTPUT_PATH## stopparts/attempts/raw-$base.attemptN.ndjsonparts/raw-*.ndjsonMISSINGarm.shSTATE_DIRTARGET_PATHPROJECT_ROOTEXPORTxargsarm.shSTATE_DIRmkdir: /opencode-db: Read-only file system27 个 area 与 model 组合都会审查每个组件,因此一次运行中,每个组件都有 27 个 arm。worker pool 每次运行 个 arm,旁边还有一个 ocr scan。请替换为第 1 步打印的值。
CONCURRENCY请将下面的代码块按原样作为一个后台 Bash 调用运行。该调用会保持打开,直到 pool 和 ocr scan 退出;完成通知就是第 2 步已完成的信号。绝不要使用 、末尾的 ,或以后台方式启动包装脚本,否则调用会立即返回,永远不会产生通知。不要监视日志中的 行。在一次 quartermaster 运行中,agent 分离了该代码块并通过 等待。 在匹配后退出,但 只有在下一次写入时才会退出,而 是日志最后写入的行。结果调用一直处于空闲状态,直到 60 分钟的 终止了 。
nohup&done,tail -f | grep -m1 '^done,'greptaildone,timeouttailbash
export PROJECT_ROOT TARGET_PATH STATE_DIR EXPORT
AREAS="security architecture solid correctness testing ops performance quality data"
MODELS="openai:openai/gpt-6-sol gemini:google/gemini-3.8-flash claude:az-anthropic/claude-opus-5-5"
CONCURRENCY=27
cat > "$STATE_DIR/arm.sh" <<'ARM'
#!/bin/bash
label=$1 model=$2 area=$3 comp=$4
if [ ! -d "$STATE_DIR/parts" ] || [ ! -d "$TARGET_PATH" ] || [ ! -d "$PROJECT_ROOT" ] || [ ! -d "$EXPORT" ]; then
echo "arm.sh: STATE_DIR='$STATE_DIR' TARGET_PATH='$TARGET_PATH' PROJECT_ROOT='$PROJECT_ROOT' EXPORT='$EXPORT'. Export all four with the values Step 1 printed." >&2
exit 255
fi
VARIANT=""
[ "$label" = claude ] && VARIANT="--variant xhigh"
base="$label-$area-$comp"
arm_dir=$(mktemp -d "${TMPDIR:-/tmp}/review-deep-arm.XXXXXX")
trap 'rm -rf "$arm_dir"' EXIT
copy="$(cd -P "$arm_dir" && pwd -P)/repo"
cp -R "$EXPORT" "$copy"
work="$copy/.review-arm"
mkdir -p "$work" "$STATE_DIR/arms"
echo "$copy" > "$STATE_DIR/arms/$base.root"
target="$copy${TARGET_PATH#"$PROJECT_ROOT"}"
if [ "$comp" = sweep ]; then
cp "$STATE_DIR/ledger.txt" "$work/ledger.txt"
cp "$STATE_DIR/$label-$area.md" "$work/listed-findings.md"
scope="LEDGER_PATH: $work/ledger.txt
FINDINGS_PATH: $work/listed-findings.md
LEDGER_PATH lists every reviewable source file. A component-by-component
review already covered them, and its findings are in FINDINGS_PATH. Look only
for defects that are not already listed there, especially defects that span
components. Do not repeat a listed finding."
else
cp "$STATE_DIR/components/$comp.txt" "$work/ledger.txt"
scope="LEDGER_PATH: $work/ledger.txt
COVERAGE_PATH: $work/coverage.txt
LEDGER_PATH lists the source files assigned to you, one path per line,
relative to $copy. Read it first and review every file it lists. The rest of
the repository is context: follow calls into it, and report defects in the
listed files.
For every path in LEDGER_PATH, write one line to COVERAGE_PATH: the path, then
reviewed or skipped, then a short reason. Every listed path must appear
exactly once before you finish."
fi
prompt="Review the source files under $target that your ledger assigns.
Navigate with serena symbolic tools: get_symbols_overview to map a file,
find_symbol to read a definition, find_referencing_symbols to find callers,
find_declaration and find_implementations to resolve a usage. Read a whole
file only when symbolic navigation cannot answer the question. Everything
you need is under $copy.
Cite path:line with the path relative to $copy, and name the enclosing
function, method, or type. A line you saw counts as verified, and find_symbol
returns a symbol's line range. When you cannot verify the exact line, cite the
nearest line you saw and end the finding with \`line unconfirmed\`. Never drop
a real defect because its line number is uncertain.
$scope
OUTPUT_PATH: $work/findings.md
Write your findings to OUTPUT_PATH. Writing that file is mandatory and is
how your work is delivered. Do not return findings as your response."
raw="$STATE_DIR/parts/raw-$base.ndjson"
wrote_findings() {
[ -s "$work/findings.md" ] ||
jq -r 'select(.type=="text") | .part.text' "$raw" 2>/dev/null | grep -q '^## '
}
mkdir -p "$STATE_DIR/opencode-db" "$STATE_DIR/parts/attempts"
attempt=1
max_attempts=3
while :; do
XDG_CONFIG_HOME="$STATE_DIR/opencode-config" \
OPENCODE_PURE=1 \
OPENCODE_DISABLE_EXTERNAL_SKILLS=1 \
OPENCODE_DISABLE_CLAUDE_CODE=1 \
OPENCODE_DISABLE_PROJECT_CONFIG=1 \
OPENCODE_DISABLE_AUTOUPDATE=1 \
OPENCODE_DB="$STATE_DIR/opencode-db/$base.db" \
opencode run \
--agent "reviewer-$area" \
-m "$model" $VARIANT \
--format json \
--dir "$target" \
--title "Review $label $area $comp" \
"$prompt" \
< /dev/null \
> "$raw" 2> "$STATE_DIR/parts/$base.log"
if wrote_findings && ! grep -q '"Failed to execute statement"' "$raw" 2>/dev/null; then break; fi
[ "$attempt" -ge "$max_attempts" ] && break
mv "$raw" "$STATE_DIR/parts/attempts/raw-$base.attempt$attempt.ndjson"
sleep $((5 + RANDOM % 15))
attempt=$((attempt + 1))
done
[ -s "$work/findings.md" ] && sed "s|$copy/||g" "$work/findings.md" > "$STATE_DIR/parts/$base.md"
[ -s "$work/coverage.txt" ] && sed "s|$copy/||g" "$work/coverage.txt" > "$STATE_DIR/coverage/$base.txt"
wrote_findings || { echo "arm.sh: $base wrote no findings in $attempt attempts" >&2; exit 1; }
ARM
: > "$STATE_DIR/tasks.txt"
for comp in $(ls "$STATE_DIR/components" | sed 's/\.txt$//'); do
for entry in $MODELS; do
for area in $AREAS; do
echo "${entry%%:*} ${entry#*:} $area $comp" >> "$STATE_DIR/tasks.txt"
done
done
done
SCOPE=""
[ "$TARGET_PATH" != "$PROJECT_ROOT" ] && SCOPE="--path ${TARGET_PATH#"$PROJECT_ROOT"/}"
OCR_COPY="$(mktemp -d "${TMPDIR:-/tmp}/review-deep-ocr.XXXXXX")/repo"
cp -R "$EXPORT" "$OCR_COPY"
ocr scan --audience agent --repo "$OCR_COPY" $SCOPE --max-tokens 1000000 \
--format json --output "$STATE_DIR/ocr-scan.json" \
< /dev/null > /dev/null 2> "$STATE_DIR/ocr-scan.log" &
echo $! > "$STATE_DIR/ocr.pid"
echo "dispatching $(wc -l < "$STATE_DIR/tasks.txt" | tr -d ' ') arms, $CONCURRENCY at a time, plus 1 ocr scan"
xargs -P "$CONCURRENCY" -L 1 bash "$STATE_DIR/arm.sh" < "$STATE_DIR/tasks.txt"
wait
rm -rf "$(dirname "$OCR_COPY")"
echo "done, $(ls "$STATE_DIR"/parts/*.md 2>/dev/null | wc -l | tr -d ' ') part files written"xargs -Ptasks.txtparts/*.mdCONCURRENCY=27opencode审查器作为隔离的 appliance 运行。 指向第 1 步生成的配置,因此不会加载操作者的 opencode agent、skill、rule、plugin 或 MCP server。凭据仍可解析,因为 opencode 将 保存在 下,而 provider 会从环境变量读取 key。 跳过外部 plugin, 跳过 和 下的 skill 扫描, 跳过 和 , 跳过被审查仓库的 、 和 。这些设置均已使用 opencode 1.18.20 的 、 以及要求 model 查找的植入指令进行验证。
XDG_CONFIG_HOMEauth.jsonXDG_DATA_HOMEOPENCODE_PURE=1OPENCODE_DISABLE_EXTERNAL_SKILLS=1~/.claude~/.agentsOPENCODE_DISABLE_CLAUDE_CODE=1~/.claude/CLAUDE.md.claude/skillsOPENCODE_DISABLE_PROJECT_CONFIG=1opencode.json.opencode/AGENTS.mdopencode debug configopencode debug skill每个 arm 都在 下运行导出的一份副本,并让 指向副本内部。 会将 arm 的 ledger 放入副本的 ;对于 sweep,还会放入该 area 合并后的发现项。arm 使用 将发现项和覆盖率记录写入该目录,使用 OpenAI model 时则使用 。 将它们复制到 和 ,从引用中去除副本路径,并在退出时删除副本。生成的 agent 拒绝所有工具,随后仅允许 、、、写入工具,以及除四类写入功能外的 Serena 工具。一条共享的 rule 拒绝副本以外的所有路径,一条 rule 仅允许在 下写入。在 opencode 1.18.20 上实测:对副本外绝对路径执行 、执行 和 均被拒绝,而对副本内路径执行 可以正常工作。
$TMPDIR/review-deep-arm.*--dirarm.sh.review-arm/writeapply_patcharm.shparts/coverage/readgrepglobexternal_directoryedit.review-arm/grepglobreadgrepSerena 作为独立进程运行,因此 opencode 的规则无法约束它。在 Serena 1.7.0 上实测:它会拒绝离开副本的相对路径和绝对目录路径,但 、 和 可以响应副本外绝对文件路径的查询。如果 arm 知道或猜到了机器上其他位置的文件路径,仍可能通过 Serena 读取该文件的 symbol。它能够解析副本内的 module,并跨越 module 边界。它们以 运行,因为 opencode 在通过 选择 agent 时会忽略 subagent 的 块。
find_symbolget_symbols_overviewget_diagnostics_for_filego.workmode: primary--agenttools审查器不得调用 。在一个包含 1070 个文件的仓库中,27 个审查器中有两个生成了永远不会返回的 subagent,而 会以前台方式运行 subagent 并执行阻塞式 ,导致父进程挂起,第 2 步的 也随之挂起。agent 设置了 和 。修复后,两个审查器都正常完成,且 调用数为零。
taskTaskToolyieldwaittools.task: falsepermission.task: {"*": deny}task当前没有机制防止同时运行两次。第 1 步会清除 ,且每个 arm 都写入固定路径,因此针对同一仓库的第二次调用会破坏第一次运行。调度前请检查是否存在正在进行的运行。
STATE_DIRprovider 限额不是约束。OpenAI 报告每分钟 40,000,000 token,Azure Foundry 为 15,000,000。
在 的写入压力下,opencode 自身的 session store 可能会失效。在一个包含 1217 个文件的仓库中实测:26 个 arm 在四秒内集中失败,错误为 ,目标是 opencode 的共享 SQLite store;该错误可见于 ,而 arm 自身的 stderr 为空。26 个失败 arm 中有 25 个完全没有写出任何内容。上游已确认此问题(anomalyco/opencode#48416、#47566、#38849、#33320、#21215):opencode 的 SQLite 连接使用 ,因此竞争写入锁失败的 writer 会立即报错,而不是等待;SQL adapter 上的 又会将此错误转换为致命的 message failure,而不是 warning。失败写入发生在任一并发进程的 tool call 返回后,由 exit handler 触发,因此它取决于当时存活且繁忙的 opencode 进程数量,而不取决于刚刚启动的是哪些进程。
CONCURRENCY=27error="Failed to execute statement"~/.local/share/opencode/log/opencode.logbusy_timeout=0Effect.orDiechild_processarm.shOPENCODE_DB="$STATE_DIR/opencode-db/$base.db"OPENCODE_DB~/.local/share/opencode/opencode.dbauth.jsonbin/重试循环会在两种情况下以相同方式重新执行 ,最多额外重试两次。第一种情况是 arm 的 NDJSON 中匹配到 ,这作为纵深防御保留,用于覆盖独立 arm 数据库未能解决的情况。第二种情况是 arm 结束时没有发现项,即 为空,并且没有 text 事件包含 标题。在一个包含 718 个文件的仓库中实测,351 个 gemini arm 里有 20 个读取了文件,却未发起 write call,最终以 step 结束,输出 token 数为 0。331 个成功写出发现项的 gemini arm 中没有一个出现这种情况。每次重试前会等待 5 到 19 秒的随机抖动间隔,避免重试集中在同一个竞争窗口。被取代的 attempt NDJSON 会移动到 ,且永不删除。该目录位于第 3 步用于提取内容的 glob 之外。arm 在 3 次 attempt 后仍没有发现项,将以状态码 1 退出,并在 stderr 中输出其名称,随后在第 3 步中显示为普通的 条目。
opencode run"Failed to execute statement"OUTPUT_PATH## stopparts/attempts/raw-$base.attemptN.ndjsonparts/raw-*.ndjsonMISSING当 、、 或 未设置,或者指向不存在的目录时, 会以状态码 255 退出。 会在出现状态码 255 后停止启动 arm,因此,如果从从未导出这些变量的 shell 调度批次,第一批 arm 会失败,而不是报告成功。Shell 变量不会在不同 Bash 调用之间继承,因此每次调用 都必须导出这四个变量。有一次重试批次使用空的 运行了全部 20 个 arm。每个 arm 都因 而失败,但批次仍以状态码 0 退出。
STATE_DIRTARGET_PATHPROJECT_ROOTEXPORTarm.shxargsarm.shSTATE_DIRmkdir: /opencode-db: Read-only file systemStep 2b: Confirm Arms Start
第 2b 步:确认 arm 已启动
Run this as a separate foreground call while Step 2 is still waiting. An arm that never created a session wrote nothing and never will, and it holds a pool slot.
bash
find "$STATE_DIR/parts" -maxdepth 1 -name 'raw-*.ndjson' -size 0 -mmin +1 | sed 's|.*/raw-||; s|\.ndjson$||; s/^/NO SESSION /'A healthy arm writes NDJSON within seconds of launch. Kill any arm named here. Step 3 re-dispatches it.
Check the ocr scan the same way, using the PID Step 2 saved. It runs as , not , so or an name match never finds it; a process check with no result was misreported once as "the ocr scan has exited" when it was still running.
nodeocrpgrep -x ocrocr scanbash
OCR_PID=$(cat "$STATE_DIR/ocr.pid" 2>/dev/null)
if [ -n "$OCR_PID" ] && kill -0 "$OCR_PID" 2>/dev/null; then
echo "ocr scan running, pid $OCR_PID, log last wrote $(stat -f '%Sm' "$STATE_DIR/ocr-scan.log" 2>/dev/null)"
else
echo "ocr scan not running and $STATE_DIR/ocr-scan.json is $([ -s "$STATE_DIR/ocr-scan.json" ] && echo present || echo MISSING)"
fiA stale log with no growing output for several minutes is not proof of a hang; the tool only logs warnings and failed tool calls, so silence during successful work is normal. Measured on a 1217-file repo: the log went quiet for roughly 40 minutes, then wrote nothing further and exited clean about 80 minutes after launch, with present and (719 files reviewed, 2586 findings). Tens of minutes of silence on a repo that size is the expected shape of a healthy run, not a symptom. Absence from ( fails) together with a missing is proof it is dead. Re-launch only in that case, with the same command from Step 2, against the same path.
ocr-scan.jsonstatus=successpskill -0ocr-scan.json$STATE_DIR/ocr-scan.json在第 2 步仍在等待时,将下面命令作为独立的前台调用运行。从未创建 session 的 arm 没有写出任何内容,并且以后也不会写出,同时还会占用一个 pool 槽位。
bash
find "$STATE_DIR/parts" -maxdepth 1 -name 'raw-*.ndjson' -size 0 -mmin +1 | sed 's|.*/raw-||; s|\.ndjson$||; s/^/NO SESSION /'健康的 arm 会在启动后数秒内写出 NDJSON。终止这里列出的任何 arm,第 3 步会重新调度它们。
使用第 2 步保存的 PID,以相同方式检查 ocr scan。它以 而不是 的形式运行,因此 或匹配 名称永远找不到它;曾有一次将没有结果的进程检查误报为“ocr scan 已退出”,而它实际上仍在运行。
nodeocrpgrep -x ocrocr scanbash
OCR_PID=$(cat "$STATE_DIR/ocr.pid" 2>/dev/null)
if [ -n "$OCR_PID" ] && kill -0 "$OCR_PID" 2>/dev/null; then
echo "ocr scan running, pid $OCR_PID, log last wrote $(stat -f '%Sm' "$STATE_DIR/ocr-scan.log" 2>/dev/null)"
else
echo "ocr scan not running and $STATE_DIR/ocr-scan.json is $([ -s "$STATE_DIR/ocr-scan.json" ] && echo present || echo MISSING)"
fi日志陈旧且数分钟内没有增长并不能证明发生挂起;该工具只记录 warning 和失败的 tool call,因此成功工作期间保持安静是正常现象。在一个包含 1217 个文件的仓库中实测:日志安静了大约 40 分钟,随后没有再写入内容,并在启动约 80 分钟后干净退出;此时 存在且 (审查了 719 个文件,发现 2586 个问题)。对于这种规模的仓库,长时间无输出正是健康运行的预期表现,并不是症状。只有 中不存在该进程( 失败)且缺少 才能证明它已经死亡。仅在这种情况下,使用第 2 步中的相同命令、针对相同的 路径重新启动。
ocr-scan.jsonstatus=successpskill -0ocr-scan.json$STATE_DIR/ocr-scan.jsonStep 3: Collect and Verify
第 3 步:收集并验证
An arm sometimes returns its findings as its response instead of writing OUTPUT_PATH. does this. The findings are in the NDJSON, so copy them out first. An arm with nothing to copy is left with no part file, so a check and a file count agree. Then merge each area and model's parts into one file.
gpt-6-solMISSINGThe merge rebuilds each merged file from scratch out of the component parts and, once Step 4 has run, that area's sweep part. Running it again at any point gives the same result, so a re-dispatch after the sweep keeps the sweep's findings.
bash
for f in "$STATE_DIR"/parts/raw-*.ndjson; do
b=$(basename "$f" .ndjson); out="$STATE_DIR/parts/${b#raw-}.md"
[ -s "$out" ] && continue
jq -r 'select(.type=="text") | .part.text' "$f" | awk '/^## /{p=1} p' | sed -E 's#[^ `]*/review-deep-arm\.[^/ ]+/repo/##g' > "$out"
[ -s "$out" ] || rm -f "$out"
done
for label in openai gemini claude; do
for area in security architecture solid correctness testing ops performance quality data; do
out="$STATE_DIR/$label-$area.md"
parts=$(ls "$STATE_DIR"/parts/"$label-$area"-c*.md "$STATE_DIR"/parts/"$label-$area"-sweep.md 2>/dev/null)
[ -z "$parts" ] && continue
{ grep -h -m1 '^## ' $parts | head -1; echo; grep -h '^- \*\*' $parts; } > "$out"
grep -q '^- \*\*' "$out" || echo "No findings." >> "$out"
for c in "$STATE_DIR"/components/*.txt; do
comp=$(basename "$c" .txt)
[ -s "$STATE_DIR/parts/$label-$area-$comp.md" ] || echo "MISSING $label $area $comp"
done
done
donebash
for label in openai gemini claude; do
echo "== $label =="
for area in security architecture solid correctness testing ops performance quality data; do
f="$STATE_DIR/$label-$area.md"
if [ ! -s "$f" ]; then
echo " $area MISSING"
else
n=$(grep -c '^- \*\*' "$f")
cited=$(grep -oE '^- \*\*(Critical|High|Medium|Low)\*\* `[^`]+`' "$f" | grep -c ':[0-9]')
unconf=$(grep -c 'line unconfirmed' "$f")
echo " $area ok findings=$n cited=$cited unconfirmed=$unconf"
fi
done
doneThen check coverage. Each arm's coverage record says which of its files it reviewed. The opened-files count checks the records against what arms actually read.
bash
total=$(wc -l < "$STATE_DIR/ledger.txt" | tr -d ' ')
while read -r label model area comp; do
f="$STATE_DIR/coverage/$label-$area-$comp.txt"
want=$(wc -l < "$STATE_DIR/components/$comp.txt" | tr -d ' ')
got=$(awk '$2 ~ /^reviewed/' "$f" 2>/dev/null | wc -l | tr -d ' ')
[ "$got" -lt "$want" ] && echo "SHORT $label $area $comp reviewed=$got of $want"
done < "$STATE_DIR/tasks.txt"
cat "$STATE_DIR"/coverage/*.txt 2>/dev/null | awk '$2 ~ /^reviewed/ {print $1}' | sort -u > "$STATE_DIR/reviewed.txt"
echo "ledger files reviewed by at least one arm=$(comm -12 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/reviewed.txt" | wc -l | tr -d ' ') of $total"
comm -23 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/reviewed.txt" | sed 's/^/ never reviewed: /'
jq -r 'select(.type=="tool_use") | .part.state.input | (.filePath // .relative_path // .path // empty)' \
"$STATE_DIR"/parts/raw-*.ndjson 2>/dev/null | sed -E 's#^.*/review-deep-arm\.[^/]+/repo/##' | sort -u > "$STATE_DIR/opened.txt"
echo "ledger files opened=$(comm -12 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/opened.txt" | wc -l | tr -d ' ') of $total"
if [ -s "$STATE_DIR/ocr-scan.json" ]; then
jq -r '"ocr status=\(.status) files=\(.summary.files_reviewed) findings=\(.comments|length)"' "$STATE_DIR/ocr-scan.json"
else
echo "ocr-scan.json MISSING, run the Step 2b ocr liveness check before treating this as a dead scan"
fiA line is an arm that marked some of its component or wrote no coverage record. Read its reasons in the coverage record. A skip because the file belongs to another area is fine. A skip for lack of depth is a gap.
SHORTskippedRe-dispatch a arm from a shell that exports the values Step 1 printed, then rerun the extract-and-merge block above. already retried the arm twice, so re-dispatch it once.
MISSINGarm.shbash
export PROJECT_ROOT=<PROJECT_ROOT> TARGET_PATH=<TARGET_PATH> STATE_DIR=<STATE_DIR> EXPORT=<EXPORT>
bash "$STATE_DIR/arm.sh" <label> <model> <area> <comp>Treat an area as unreviewed when its merged file is missing, or has neither a finding line nor exactly Never record either as Report every arm still missing after one retry to the operator as not reviewed, naming the model, area, and component.
No findings.No findings.Compare against . A finding whose first backticked field carries no is unusable, because nobody can confirm it without re-reading the whole file. counts a finding carrying a whether or not it ends with , so subtract for the count the reviewer stands behind. Read uncited findings before re-dispatching, and re-dispatch only when they carry no symbol either.
citedfindings:linecitedpath:lineline unconfirmedunconfirmedarm 有时会把发现项作为响应返回,而不是写入 。 会这样处理。发现项位于 NDJSON 中,因此应先将其提取出来。没有可复制内容的 arm 不会留下 part 文件,因此 检查与文件计数会保持一致。然后,将每个 area 和 model 的 part 合并为一个文件。
OUTPUT_PATHgpt-6-solMISSING合并过程会根据组件 part 重新从头构建每个合并文件;第 4 步运行后,还会包含该 area 的 sweep part。任何时候重新运行都会得到相同结果,因此在 sweep 之后重新调度仍会保留 sweep 的发现项。
bash
for f in "$STATE_DIR"/parts/raw-*.ndjson; do
b=$(basename "$f" .ndjson); out="$STATE_DIR/parts/${b#raw-}.md"
[ -s "$out" ] && continue
jq -r 'select(.type=="text") | .part.text' "$f" | awk '/^## /{p=1} p' | sed -E 's#[^ `]*/review-deep-arm\.[^/ ]+/repo/##g' > "$out"
[ -s "$out" ] || rm -f "$out"
done
for label in openai gemini claude; do
for area in security architecture solid correctness testing ops performance quality data; do
out="$STATE_DIR/$label-$area.md"
parts=$(ls "$STATE_DIR"/parts/"$label-$area"-c*.md "$STATE_DIR"/parts/"$label-$area"-sweep.md 2>/dev/null)
[ -z "$parts" ] && continue
{ grep -h -m1 '^## ' $parts | head -1; echo; grep -h '^- \*\*' $parts; } > "$out"
grep -q '^- \*\*' "$out" || echo "No findings." >> "$out"
for c in "$STATE_DIR"/components/*.txt; do
comp=$(basename "$c" .txt)
[ -s "$STATE_DIR/parts/$label-$area-$comp.md" ] || echo "MISSING $label $area $comp"
done
done
donebash
for label in openai gemini claude; do
echo "== $label =="
for area in security architecture solid correctness testing ops performance quality data; do
f="$STATE_DIR/$label-$area.md"
if [ ! -s "$f" ]; then
echo " $area MISSING"
else
n=$(grep -c '^- \*\*' "$f")
cited=$(grep -oE '^- \*\*(Critical|High|Medium|Low)\*\* `[^`]+`' "$f" | grep -c ':[0-9]')
unconf=$(grep -c 'line unconfirmed' "$f")
echo " $area ok findings=$n cited=$cited unconfirmed=$unconf"
fi
done
done随后检查覆盖率。每个 arm 的覆盖率记录会说明它审查了哪些文件。已打开文件计数会将这些记录与 arm 实际读取的内容进行核对。
bash
total=$(wc -l < "$STATE_DIR/ledger.txt" | tr -d ' ')
while read -r label model area comp; do
f="$STATE_DIR/coverage/$label-$area-$comp.txt"
want=$(wc -l < "$STATE_DIR/components/$comp.txt" | tr -d ' ')
got=$(awk '$2 ~ /^reviewed/' "$f" 2>/dev/null | wc -l | tr -d ' ')
[ "$got" -lt "$want" ] && echo "SHORT $label $area $comp reviewed=$got of $want"
done < "$STATE_DIR/tasks.txt"
cat "$STATE_DIR"/coverage/*.txt 2>/dev/null | awk '$2 ~ /^reviewed/ {print $1}' | sort -u > "$STATE_DIR/reviewed.txt"
echo "ledger files reviewed by at least one arm=$(comm -12 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/reviewed.txt" | wc -l | tr -d ' ') of $total"
comm -23 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/reviewed.txt" | sed 's/^/ never reviewed: /'
jq -r 'select(.type=="tool_use") | .part.state.input | (.filePath // .relative_path // .path // empty)' \
"$STATE_DIR"/parts/raw-*.ndjson 2>/dev/null | sed -E 's#^.*/review-deep-arm\.[^/]+/repo/##' | sort -u > "$STATE_DIR/opened.txt"
echo "ledger files opened=$(comm -12 <(sort "$STATE_DIR/ledger.txt") "$STATE_DIR/opened.txt" | wc -l | tr -d ' ') of $total"
if [ -s "$STATE_DIR/ocr-scan.json" ]; then
jq -r '"ocr status=\(.status) files=\(.summary.files_reviewed) findings=\(.comments|length)"' "$STATE_DIR/ocr-scan.json"
else
echo "ocr-scan.json MISSING, run the Step 2b ocr liveness check before treating this as a dead scan"
fiSHORTskipped从已导出第 1 步所打印值的 shell 中重新调度 arm,然后重新运行上述提取和合并代码块。 已经重试过该 arm 两次,因此这里再重新调度一次。
MISSINGarm.shbash
export PROJECT_ROOT=<PROJECT_ROOT> TARGET_PATH=<TARGET_PATH> STATE_DIR=<STATE_DIR> EXPORT=<EXPORT>
bash "$STATE_DIR/arm.sh" <label> <model> <area> <comp>如果某个 area 的合并文件缺失,或者既没有发现项行,也没有内容恰好为 的行,则应将该 area 视为未审查。绝不能将这两种情况都记录为 。在一次重试后,仍缺失的每个 arm 都应作为“未审查”报告给操作者,并注明 model、area 和 component。
No findings.No findings.比较 与 。如果某个发现项的第一个反引号字段不包含 ,则该发现项不可用,因为不重新读取整个文件就无法确认。只计算带有 的发现项,无论其是否以 结尾,因此审查器认可的引用数应减去 。重新调度前应阅读未引用发现项;仅当它们也不包含任何 symbol 时才重新调度。
citedfindings:linepath:lineline unconfirmedunconfirmedStep 4: Sweep
第 4 步:Sweep
One sweep arm per area and model gets the whole ledger and that area's merged findings, and looks only for defects not already listed. It catches what a component boundary hid.
bash
: > "$STATE_DIR/sweeps.txt"
for entry in $MODELS; do
for area in $AREAS; do
[ -s "$STATE_DIR/${entry%%:*}-$area.md" ] && echo "${entry%%:*} ${entry#*:} $area sweep" >> "$STATE_DIR/sweeps.txt"
done
done
xargs -P "$CONCURRENCY" -L 1 bash "$STATE_DIR/arm.sh" < "$STATE_DIR/sweeps.txt"Run this block as one background Bash call, the same way as Step 2. Its completion notification is the signal to continue.
Then rerun the Step 3 extract-and-merge block. It extracts each sweep's findings and rebuilds that area's merged file with them. Check each sweep afterward.
bash
while read -r label model area comp; do
s="$STATE_DIR/parts/$label-$area-sweep.md"
if [ -s "$s" ]; then echo "$label-$area sweep added=$(grep -c '^- \*\*' "$s")"; else echo "MISSING $label $area sweep"; fi
done < "$STATE_DIR/sweeps.txt"Re-dispatch a sweep the same way as a arm in Step 3.
MISSINGMISSINGRe-export , , , and , and reset , , and , when a block runs as a separate call from Step 2.
STATE_DIRTARGET_PATHPROJECT_ROOTEXPORTAREASMODELSCONCURRENCYAfter the last sweep and the last re-dispatch, delete the export. Step 5 makes its own checkout for the judges.
bash
rm -rf "$EXPORT"每个 area 和 model 都有一个 sweep arm,它会获得完整 ledger 和该 area 合并后的发现项,并且只查找尚未列出的缺陷。它用于发现被组件边界隐藏的问题。
bash
: > "$STATE_DIR/sweeps.txt"
for entry in $MODELS; do
for area in $AREAS; do
[ -s "$STATE_DIR/${entry%%:*}-$area.md" ] && echo "${entry%%:*} ${entry#*:} $area sweep" >> "$STATE_DIR/sweeps.txt"
done
done
xargs -P "$CONCURRENCY" -L 1 bash "$STATE_DIR/arm.sh" < "$STATE_DIR/sweeps.txt"请按照第 2 步相同的方式,将该代码块作为一个后台 Bash 调用运行。其完成通知就是可以继续的信号。
随后重新运行第 3 步的提取和合并代码块。它会提取每个 sweep 的发现项,并使用这些发现项重新构建该 area 的合并文件。之后检查每个 sweep。
bash
while read -r label model area comp; do
s="$STATE_DIR/parts/$label-$area-sweep.md"
if [ -s "$s" ]; then echo "$label-$area sweep added=$(grep -c '^- \*\*' "$s")"; else echo "MISSING $label $area sweep"; fi
done < "$STATE_DIR/sweeps.txt"按照第 3 步中 arm 的相同方式重新调度 sweep。
MISSINGMISSING如果某个代码块作为独立于第 2 步的调用运行,请重新导出 、、 和 ,并重置 、 和 。
STATE_DIRTARGET_PATHPROJECT_ROOTEXPORTAREASMODELSCONCURRENCY最后一次 sweep 和最后一次重新调度完成后,删除导出内容。第 5 步会为 judge 创建自己的 checkout。
bash
rm -rf "$EXPORT"Step 5: Collate, Verify, and Report
第 5 步:整理、验证并报告
Run the scripted stages. Run each model stage as a background Bash call. Its pool returns when every call has finished, and the completion notification is the signal to continue. Never poll a running stage with . Run the stage command itself as the background call. Never detach it with or a trailing , and never wait on a log line with , for the reasons Step 2 gives.
sleepnohup&tail -f | grep -m1bash
S="${CLAUDE_SKILL_DIR}/scripts" # replace ${CLAUDE_SKILL_DIR} with this SKILL.md's own directory on a non-Claude harness
[ -f "$S/init_run.py" ] || { echo "scripts not found at $S"; exit 1; }
RUN=<RUN_DIR printed by Step 1>
python3 "$S/checkout.py" "$RUN"
python3 "$S/normalize.py" "$RUN"
python3 "$S/collate.py" "$RUN" # one model call per component, background
uv run --with scikit-learn --with scipy --with numpy python "$S/windows.py" "$RUN"
python3 "$S/merge.py" "$RUN" # one model call per similarity window, background
python3 "$S/rank.py" "$RUN"
python3 "$S/verify.py" "$RUN" # one model call per Critical or High issue, background
python3 "$S/report.py" "$RUN"| Stage | Output | What it does |
|---|---|---|
| | Exports the reviewed commit with |
| | Parses every merged findings file and ocr's comments into findings with stable IDs. Exits 1 on any bullet it cannot parse. |
| | Groups each component's findings into issues, where one issue is one code change. Every finding ID must land in exactly one issue or in the dropped list. |
| | Orders all issues by text similarity and cuts overlapping windows of 150, so one window can see a defect reported in two components. |
| | Merges issues that describe the same defect, where one change fixes all. The same pattern in independent places stays apart. |
| | Applies the merges and ranks by severity, source count, and finding count. |
| | Opus at high tries to refute each Critical and High issue and returns a verdict, quoted evidence, the locations and members that do not hold, and its own severity. Fable at high rechecks, blind, every issue the verifier confirms as Critical. |
| | Counts, every confirmed issue at the lower of its two ratings, the refuted ones with the reason, and coverage gaps. |
Every model call runs as its own process with under , a 5-minute prompt cache, and the prompt on stdin. The pool checks that each process started with exactly those tools, checks every answer's content, and retries a rejected answer up to three times with the reasons appended. A verdict whose evidence quote is not the code at the reviewed commit is rejected. The review-tickets skill documents the measurements behind each of these settings.
claude -p --bare--tools Bash,Read--permission-mode dontAskThe merge rule is narrower than it was. An earlier rule also merged the same kind of defect with the same kind of fix. It built one issue of 77 findings across 24 files, and tickets built from it cited locations that did not support the claim.
Report the counts from , the confirmed Critical issues, and the coverage gaps to the operator. The run directory is the input to the review-tickets skill.
report.md运行脚本化阶段。每个 model 阶段都应作为一个后台 Bash 调用运行。其 pool 会在所有调用完成后返回,而完成通知就是可以继续的信号。不要使用 轮询正在运行的阶段。直接运行阶段命令本身作为后台调用。不要使用 或末尾的 将其分离,也不要使用 等待日志行,原因与第 2 步相同。
sleepnohup&tail -f | grep -m1bash
S="${CLAUDE_SKILL_DIR}/scripts" # replace ${CLAUDE_SKILL_DIR} with this SKILL.md's own directory on a non-Claude harness
[ -f "$S/init_run.py" ] || { echo "scripts not found at $S"; exit 1; }
RUN=<RUN_DIR printed by Step 1>
python3 "$S/checkout.py" "$RUN"
python3 "$S/normalize.py" "$RUN"
python3 "$S/collate.py" "$RUN" # one model call per component, background
uv run --with scikit-learn --with scipy --with numpy python "$S/windows.py" "$RUN"
python3 "$S/merge.py" "$RUN" # one model call per similarity window, background
python3 "$S/rank.py" "$RUN"
python3 "$S/verify.py" "$RUN" # one model call per Critical or High issue, background
python3 "$S/report.py" "$RUN"| 阶段 | 输出 | 作用 |
|---|---|---|
| | 使用 |
| | 将每个合并发现项文件和 ocr comment 解析为具有稳定 ID 的发现项。遇到任何无法解析的 bullet 时以状态码 1 退出。 |
| | 将每个组件的发现项整理为 issue,其中一个 issue 对应一项代码更改。每个发现项 ID 必须且只能归入一个 issue,或归入 dropped 列表。 |
| | 按文本相似度对所有 issue 排序,并切分 150 个重叠项的窗口,使一个窗口能够发现两个组件中分别报告的同一缺陷。 |
| | 合并描述同一缺陷的 issue,即一次更改可以修复所有这些 issue。独立位置上的相同模式仍会保持分离。 |
| | 应用合并结果,并根据严重程度、来源数量和发现项数量进行排名。 |
| | Opus at high 会尝试反驳每个 Critical 和 High issue,并返回 verdict、引用的证据、不成立的位置和成员,以及自身给出的严重程度。Fable at high 会以盲审方式重新检查 verifier 确认为 Critical 的每个 issue。 |
| | 统计数量,以两者评级中较低者列出每个已确认 issue,列出被反驳的 issue 及其原因,并报告覆盖率缺口。 |
每个 model 调用都作为独立的 进程运行,使用 、、5 分钟 prompt cache,并将 prompt 放在 stdin 上。pool 会检查每个进程是否恰好以指定工具启动,检查每个答案的内容,并在答案被拒绝时附加原因后最多重试三次。证据引用若不是被审查 commit 中的代码,verdict 会被拒绝。review-tickets skill 记录了这些设置背后的测量结果。
claude -p --bare--tools Bash,Read--permission-mode dontAsk合并规则比之前更严格。早期规则还会合并具有相同缺陷类型和相同修复类型的项;它曾将 24 个文件中的 77 个发现项合并为一个 issue,而据此创建的 ticket 所引用的位置并不支持相应结论。
向操作者报告 中的计数、已确认的 Critical issue 和覆盖率缺口。运行目录是 review-tickets skill 的输入。
report.mdExpected Output Files
预期输出文件
.llmtmp/review-deep/- , 27 merged findings files, component parts plus sweep
<label>-<area>.md - ,
ocr-scan.json, andocr-scan.log, the coverage passocr.pid - and
ledger.txt, the reviewable files and the excluded ones with ocr's reasonskipped.txt - , the partition
components/cNN.txt - , each arm's reviewed or skipped record
coverage/<label>-<area>-<comp>.txt - and
reviewed.txt, the files arms marked reviewed and the files they openedopened.txt - ,
parts/<label>-<area>-<comp>.md,parts/raw-*.ndjson, each arm's findings, event stream, and stderrparts/*.log - , a superseded attempt's event stream, kept when arm.sh retries past a SQLite write-contention failure or an arm that wrote no findings
parts/attempts/raw-*.attemptN.ndjson - , each arm's isolated opencode session database
opencode-db/<label>-<area>-<comp>.db - , the generated opencode config every arm runs under, and its per-area prompt files
opencode-config/ - , the path of the copy each arm ran in, the one root its tool calls may touch
arms/<label>-<area>-<comp>.root - ,
arm.sh,tasks.txt, the runner and its work listssweeps.txt
The run directory, , holds Step 5's output and survives the next review of a different commit:
${XDG_CACHE_HOME:-~/.cache}/review-deep/<repo>-<commit12>/- , the repository, commit, models, concurrency, judge exclusions, and commit sentence
run.json - , the export every judge reads, and
checkout/checkout_excluded.json - ,
findings.jsonl,issues-by-component.jsonl,dropped.jsonl,unassigned.jsonl,windows.jsonglobal_merges.json - and
issues.jsonl, every issue with its member findings inlinedissues.md - and
verify/results.jsonl, the verdictsverify/second.jsonl report.md- ,
collate/,merge/,verify/first/, each withverify/second/and every model process's event stream underpool.logcalls/
.llmtmp/review-deep/- :27 个合并发现项文件,包含组件 part 和 sweep
<label>-<area>.md - 、
ocr-scan.json和ocr-scan.log:覆盖率检查ocr.pid - 和
ledger.txt:可审查文件,以及被排除文件和 ocr 给出的原因skipped.txt - :分区结果
components/cNN.txt - :每个 arm 的已审查或已跳过记录
coverage/<label>-<area>-<comp>.txt - 和
reviewed.txt:arm 标记为已审查的文件及其打开过的文件opened.txt - 、
parts/<label>-<area>-<comp>.md、parts/raw-*.ndjson:每个 arm 的发现项、事件流和 stderrparts/*.log - :被取代 attempt 的事件流;当 arm.sh 在 SQLite 写入竞争失败或 arm 没有写出发现项后进行重试时予以保留
parts/attempts/raw-*.attemptN.ndjson - :每个 arm 独立的 opencode session database
opencode-db/<label>-<area>-<comp>.db - :每个 arm 运行时所使用的 opencode 配置及其按 area 划分的 prompt 文件
opencode-config/ - :每个 arm 运行所在的副本路径,也是其 tool call 唯一允许访问的根目录
arms/<label>-<area>-<comp>.root - 、
arm.sh、tasks.txt:运行器及其工作列表sweeps.txt
运行目录 保存第 5 步的输出,并在下一次审查不同 commit 后继续存在:
${XDG_CACHE_HOME:-~/.cache}/review-deep/<repo>-<commit12>/- :仓库、commit、model、并发度、judge exclusion 和 commit 句子
run.json - :每个 judge 读取的导出,以及
checkout/checkout_excluded.json - 、
findings.jsonl、issues-by-component.jsonl、dropped.jsonl、unassigned.jsonl、windows.jsonglobal_merges.json - 和
issues.jsonl:每个 issue 及其内联的成员发现项issues.md - 和
verify/results.jsonl:verdictverify/second.jsonl report.md- 、
collate/、merge/、verify/first/:各包含verify/second/,并在pool.log下保存每个 model 进程的事件流calls/
NDJSON Reference
NDJSON 参考
One JSON object per line. Skip lines that fail to parse.
| Type | Meaning | Key fields |
|---|---|---|
| An LLM turn begins | |
| Model emitted text | |
| Model called a tool | |
| A turn completed | |
| Session-level error | |
A reason of means done and means continuing.
step_finishstoptool-callsbash
jq -s '[.[] | select(.type=="step_finish") | .part.cost] | add' "$NDJSON"
tac "$NDJSON" | jq -s 'first(.[] | select(.type=="step_finish")) | .part.tokens.total'
jq -r 'select(.type=="tool_use") | .part.tool' "$NDJSON" | sort | uniq -c | sort -rn
jq -r 'select(.type=="error") | .error.data.message' "$NDJSON"Serena carried 119 of 181 tool calls on a TypeScript repo and 2 of 28 on a repo of HTML, CSS, and a Dockerfile. Symbolic navigation is worth little where no language server backs the files.
每行一个 JSON object。跳过无法解析的行。
| 类型 | 含义 | 关键字段 |
|---|---|---|
| LLM turn 开始 | |
| model 输出文本 | |
| model 调用 tool | |
| turn 完成 | |
| session 级错误 | |
step_finishstoptool-callsbash
jq -s '[.[] | select(.type=="step_finish") | .part.cost] | add' "$NDJSON"
tac "$NDJSON" | jq -s 'first(.[] | select(.type=="step_finish")) | .part.tokens.total'
jq -r 'select(.type=="tool_use") | .part.tool' "$NDJSON" | sort | uniq -c | sort -rn
jq -r 'select(.type=="error") | .error.data.message' "$NDJSON"在一个 TypeScript 仓库中,Serena 承担了 181 次 tool call 中的 119 次;在一个包含 HTML、CSS 和 Dockerfile 的仓库中,则只承担了 28 次中的 2 次。如果没有语言服务器支持相关文件,symbolic navigation 的价值就很有限。
Tests
测试
Tests and evals for review-deep and review-tickets, plus the guard tests and preflight tests that cover all four skills, live in at the root of this plugin's repository, outside the installed skill.
evals/| Check | Command, from | Cost |
|---|---|---|
| Unit tests, one directory per skill, plus preflight and guard | | none |
| End-to-end replay on the harbor fixture, all three kinds | | none |
Live quality eval against | | about $3 at list price |
unit/review_deepwindows.pyuv run --with pytest --with numpy --with scipy --with scikit-learn pytest -q unit/review_deepRun the unit tests and the replay after any change to . The replay stops with "prompt changed" when a prompt changes. Record again with , check the run with , and save it with . Run the live eval on demand after changing a model, an effort level, or a prompt.
scripts/python3 pipeline.py WORK_DIR --mode recordlive_eval.pypython3 golden.py save RUN_DIRreview-deep 和 review-tickets 的测试与 eval,以及覆盖全部四个 skill 的 guard test 和 preflight test,位于此插件仓库根目录下的 ,不在已安装的 skill 中。
evals/| 检查项 | 命令,从 | 成本 |
|---|---|---|
| 单元测试,每个 skill 一个目录,外加 preflight 和 guard | | 无 |
| 对 harbor fixture 进行端到端 replay,覆盖全部三种类型 | | 无 |
基于 | | 按标价约 3 美元 |
unit/review_deepwindows.pyuv run --with pytest --with numpy --with scipy --with scikit-learn pytest -q unit/review_deep对 做任何更改后,都应运行单元测试和 replay。Replay 会在 prompt 发生变化时以“prompt changed”停止。重新录制:;使用 检查运行;然后保存:。更改 model、effort level 或 prompt 后,可按需运行实时 eval。
scripts/python3 pipeline.py WORK_DIR --mode recordlive_eval.pypython3 golden.py save RUN_DIRRules
规则
- The invoking agent is a launcher. It performs no review analysis of its own. The Step 5 scripts and their model calls do all of it.
- NEVER let a Step 5 model call read the live working tree. They run in the run directory's .
checkout/ - NEVER poll a background stage with , a wait loop, or a log watcher such as
sleep. Wait for its completion notification.tail -f | grep -m1 - Keep prompts in as
prompts/. A markdown formatter rewrote a.txtprompt and changed its meaning..md - ALWAYS redirect stdin from on
/dev/null. Omitting it hangs the process before session creation with no output and no error, which is anomalyco/opencode issue #38723.opencode run - Impose no token cap and no turn cap on a reviewer. Let it finish.
- Run Step 2b a minute or so after dispatch, and again during long runs. An arm with an empty NDJSON is dead and holds a pool slot, and wall time cannot tell that apart from a slow review.
- Run the Step 2 and Step 4 dispatch blocks as background Bash calls, never detached with or
nohup. The pool blocks until every arm exits, and backgrounding keeps that out of the main session. The call's completion notification is the only signal that the pool has finished.& - ALWAYS export ,
STATE_DIR, andTARGET_PATHin every Bash call that runsPROJECT_ROOT, including a single re-dispatch. Shell variables do not carry between calls, andarm.shexits 255 without them.arm.sh - Launch every arm through the pool and the ocr scan in one bash block. They are independent processes.
- Use plain message invocation, not . The
--commandflag has a known issue with the context7 MCP server.--command - Do NOT clean up per-area files, NDJSON, or logs during a run. Step 1 wipes them at the start of the next one.
- If an arm fails, still wait for and report the others.
- The opencode arms need only the operator's providers in and a Serena home at
~/.config/opencode/opencode.json. Their agents, Serena context, and Serena manual come from this plugin through~/.serena-reviewer.opencode_env.pychecks the providers and the Serena home by name before Step 1 wipes anything.preflight.py --tier deep
- 调用此 skill 的 agent 是 launcher。它自身不执行任何审查分析。第 5 步脚本及其 model 调用完成全部分析。
- 绝不能让第 5 步的 model 调用读取实时工作树。它们必须读取运行目录中的 。
checkout/ - 绝不要使用 、等待循环或
sleep等日志监视器轮询后台阶段。等待其完成通知。tail -f | grep -m1 - 将 prompt 保留在 中并使用
prompts/扩展名。Markdown formatter 曾重写.txtprompt 并改变其含义。.md - 在 上始终将 stdin 从
opencode run重定向。省略此操作会使进程在创建 session 前挂起,且不产生输出和错误;这是 anomalyco/opencode issue #38723。/dev/null - 不要对审查器施加 token 上限或轮次上限。让它自行完成。
- 调度后约一分钟运行一次第 2b 步,长时间运行期间也再次运行。NDJSON 为空的 arm 已死亡并会占用 pool 槽位,而 wall time 无法区分它与运行缓慢的审查。
- 将第 2 步和第 4 步的调度代码块作为后台 Bash 调用运行,绝不要使用 或
nohup分离。pool 会阻塞至所有 arm 退出,而使用后台运行可避免其阻塞主 session。调用的完成通知是 pool 已完成的唯一信号。& - 在每个运行 的 Bash 调用中,始终导出
arm.sh、STATE_DIR和TARGET_PATH,包括单次重新调度。Shell 变量不会跨调用继承,缺少这些变量时PROJECT_ROOT会以状态码 255 退出。arm.sh - 在同一个 bash 代码块中启动每个 arm 和 ocr scan。它们是相互独立的进程。
- 使用普通 message 调用,而不是 。
--commandflag 存在与 context7 MCP server 相关的已知问题。--command - 运行期间不要清理按 area 划分的文件、NDJSON 或日志。第 1 步会在下一次运行时将其清除。
- 如果某个 arm 失败,仍须等待并报告其他 arm。
- opencode arm 只需要 中的操作者 provider,以及位于
~/.config/opencode/opencode.json的 Serena home。其 agent、Serena 上下文和 Serena 手册由插件通过~/.serena-reviewer提供。opencode_env.py会在第 1 步清除任何内容之前,按名称检查 provider 和 Serena home。preflight.py --tier deep