研发协作
审计 Rust 代码安全时:用主对话编排子代理做…
对 Rust crate、service、library 做全面安全审查,重点覆盖 safe/unsafe 边界、unsafe 内存安全、并发风险、panic DoS、FFI、async runtime 错误,并输出 REPORT.md/SARIF。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
审计 Rust 代码安全时:用主对话编排特定
解决思路
- 在主对话中运行,入口为 `/rust-review:rust-review`;orchestrator 拥有 `Task*` ledger 作为重试记账,workers 和 judges 没有 Task 工具。
- Workers 和 judges 使用命名的插件子代理:`rust-review:rust-review-worker`、`rust-review:rust-review-dedup-judge`、`rust-review:rust-review-fp-judge`;工具集在 `plugins/rust-review/agents/*.md` 声明。
- Findings 通过共享输出目录中的 markdown-with-YAML 文件交换;输出目录包含 `context.md`、`plan.json`、`worker-prompts/`、`findings/`、`findings-index.d/`、`findings-index.txt`、`coverage/`、`run-summary.md`、`dedup-summary.md`、`fp-summary.md`、`REPORT.md`、`REPORT.sarif`。
- 子代理工具:worker 用 Read、Write、Edit、Bash;dedup-judge 用 Read、Write、Edit、Glob;fp-judge 用 Read、Write、Edit、Bash。工具来自各 agent frontmatter。
- 拥有 `Bash` 的 agent 不会同时拥有专用 `Glob` 或 `Grep` 工具;应改用 Bash 中的 `find`、`grep`、`rg`。只有不持有 Bash 的 dedup-judge 使用 `Glob`。
- worker、fp-judge、orchestrator 用 `Read` 或 Bash 的 `find`、`rg`、`grep`、`test -f` 解析和搜索路径;不要给持有 Bash 的 agent 协议重新引入 `Glob`/`Grep`。
- cluster/finder prompt 种子使用 ripgrep 正则语法;持有 Bash 的 agent 必须用 `rg` 运行。若未安装 `rg`,回退到 `grep -E` 并把 ripgrep 的空白/数字/词边界语法转换为 POSIX 类,不能用原始空白语法导致静默空结果。
- 架构流程:coordinator 写 `context.md` → `build_run_plan.py` → `TaskCreate` × M → spawn primer(foreground)→ spawn M workers(parallel)→ classify Phase-7 outcomes 并写 `findings-index.txt` → dedup-judge → fp-judge → report safety net(SARIF + REPORT.md)→ 返回 `REPORT.md`。
- 先解析 `RUST_REVIEW_PLUGIN_ROOT` 到包含 `prompts/clusters/unsafe-boundary.md` 和 `scripts/build_run_plan.py` 的插件目录。依次尝试:Native Claude Code 的 `${CLAUDE_PLUGIN_ROOT}`,用 `ls ${CLAUDE_PLUGIN_ROOT}/prompts/clusters/unsafe-boundary.md` 验证;Codex 的 `${CODEX_PLUGIN_ROOT}`;fallback 搜索 `find ~/.claude ~/.codex . -path '*/plugins/rust-review/prompts/clusters/unsafe-boundary.md' -print -quit 2>/dev/null`。
- 取 fallback 命中的路径并去掉尾部 `/prompts/clusters/unsafe-boundary.md` 得到 root;home 目录先于 `.` 搜索,确保已安装副本优先于被审计仓库中的 vendored 副本。设置 `RUST_REVIEW_PLUGIN_ROOT`。
- 如果三种方式都失败,abort 并列出搜索过的 root;不要带空变量进入 Phase 4,否则每个 `uv run --no-project ${RUST_REVIEW_PLUGIN_ROOT}/scripts/...` 都会因路径错误失败。
- 全程保持两个 scope 分离:`finding_scope_root` 是用户请求的审计子树,workers 只能提交脆弱位置在该子树内的 findings;`context_roots` 是 workers 和 judges 可只读检查的仓库 root/文件,用于验证 reachability、callers、wrappers、build flags、mitigations、threat-model。
- 默认 `context_roots=.`,除非用户明确禁止更广上下文;读取 `finding_scope_root` 之外的上下文允许,但在那里提交 findings 不允许。若用户禁止更广上下文,则设 `context_roots=${finding_scope_root}` 并说明 reachability confidence 可能降低。
- 拒绝合理化:不要因为 `unsafe` 少见就手工跳过 memory-safety cluster;在 Phase 1 准确设置 `has_unsafe`,由 `build_run_plan.py` 决定。当 `has_unsafe=true` 时运行整个 memory-safety cluster,false 时正确省略。unsafe-boundary cluster 无 `requires` 且始终运行。
- 拒绝合理化:不要相信 `the compiler caught it`。borrow checker 证明 safe-code 无 data races,但不证明 unsafe blocks、panic reachability、ABBA deadlocks、atomic-load/store sequencing、FFI ABI mismatch。
- 拒绝合理化:`unwrap()` 即使有 `// SAFETY: documented infallible` 也不一定安全。`// SAFETY:` 记录 unsafe operations,不记录 infallibility claims;如果文档错误仍是风险,按 low severity 提交并让 FP judge 决定。
- 拒绝合理化:不要因 `has_unsafe=false` 就跳过运行。纯 safe Rust 仍有 panic-DoS、atomic races、drop-panics、trait-implementation hazards;运行 always-on clusters。
- 拒绝合理化:background spawns 不会并行化 workers。单条 assistant message 中的 `Agent` calls 已经并发;`run_in_background=true` 会破坏 Phase 6a primer cache,使每个 worker 支付完整 cache-creation(`cache_read_input_tokens=0`),约 15K-token primer 被浪费 M 次。默认 worker spawn 省略 `run_in_background`。
- 拒绝合理化:不要内联重新推导 cluster list、paths、pass prefixes;`build_run_plan.py` 是选择和渲染的唯一权威。改写会丢掉 worker self-check 所需字段,产生 `worker-N abort: spawn prompt malformed`。始终运行脚本并 `Read plan.json`。
- 拒绝合理化:运行部分成功时不要只根据已完成内容写 `REPORT.md`。用成功报告隐藏 partial runs 是 correctness bug。如果任何 Phase-5 cluster task 不是 `completed`,在 `run-summary.md` 和最终响应中突出显示。
- 拒绝合理化:零 findings 也要运行 Phase 8。始终运行两个 judges 和 Phase 8b:dedup-judge 在 empty index 上写 minimal no-op `dedup-summary.md`,fp-judge 写 empty `REPORT.md`/`REPORT.sarif`,Phase 8b 的 SARIF generator 对 empty case 输出 `results: []`。SARIF consumers 依赖稳定 artifact set。
- 拒绝合理化:`Bash: ls README*` 不适合 preflight。在 zsh 下,未匹配 glob 会在 `2>/dev/null` 前中止整个 compound command。使用 `find`(no-match 也不失败),不要用 `Glob`(持有 Bash 的 agent 不可用)。
- Phase 0 参数收集:技能直接调用,无 command wrapper。解析 `/rust-review:rust-review` 行上的 free-text 参数(如 `flamenco only`、`high severity only`、`use haiku`)并预填可推断答案;然后用一次 `AskUserQuestion` 询问缺失的必需参数。不要静默默认必需参数。
- 解析 `threat_model` 为 `REMOTE`/`LOCAL_UNPRIVILEGED`/`BOTH`:remote、network、attacker 推断 `REMOTE`;local、unprivileged 推断 `LOCAL_UNPRIVILEGED`;否则询问。
- 解析 `worker_model` 为 `haiku`/`sonnet`/`opus`:args 中显式模型名则使用;否则询问,无静默默认。
- 解析 `severity_filter` 为 `all`/`medium`/`high`:all、every、noisy 推断 `all`;medium and above 推断 `medium`;high only、criticals only 推断 `high`;否则询问,无静默默认。
- 解析 `scope_subpath` 为 repo-relative directory(可选):X only、just audit X/、review subdirectory X 推断 `src/X/` 或匹配子目录;对仓库顶级子目录做 fuzzy matching。若缺失设 `.`;若歧义则询问。
- `AskUserQuestion` 只调用一次,只包含未解决的必需参数;仅当用户明确请求 narrowed scope 但歧义时才加 `scope_subpath`。如果必需参数都已预填且 scope 缺失或已解析,跳过问题。
- 解析 `scope_subpath` 后设 `finding_scope_root=${scope_subpath:-.}`;默认设 `context_roots=.`,让 workers 能验证 narrowed subtree 外的 callers/build settings 而不提交 out-of-scope findings。若用户明确禁止 broader context,设 `context_roots=${finding_scope_root}` 并注明 reachability confidence 可能降低。
- Phase 1 前置:若 `${finding_scope_root}` 下没有 `*.rs` 文件,abort 并给出清晰消息。
- 先确认 `uv` 存在(`command -v uv`);每一个 Phase 4 之后的 helper script 都通过它运行。若缺失,abort 并让用户安装(`brew install uv` 或官方 installer);下游无法运行。
- 在 `${finding_scope_root:-.}` 内用 Bash 命令探测;非空输出表示 flag 为 true。orchestrator 因持有 Bash 不能使用专用 `Grep`/`Glob`,改用 Bash 的 `grep`/`rg`/`find`。
- 探测 Rust source presence:`find ${finding_scope_root:-.} -name '*.rs' -print -quit`。
- 探测 `has_unsafe`:用 `grep -rlE` 搜索 `unsafe extern|fn|impl|trait` 和 `unsafe {`,`--include='*.rs'`。
- 探测 `has_ffi`:用 `grep -rlE` 搜索 `extern C|system|stdcall|cdecl|win64|sysv64|aapcs|fastcall|thiscall|vectorcall|efiapi(-unwind)`、`extern fn`、`extern {`、`#[repr(C|transparent)]`、`CString|CStr`、`use libc|core::ffi|std::ffi|std::os::raw|cty`、`libc::`、`bindgen|cbindgen`、`c_void`。
- 探测 `has_concurrency`:用 `grep -rlE` 搜索 `std::thread|std::sync`、`parking_lot::`、`crossbeam`、`rayon::`、`tokio::sync`、`core::sync::atomic|std::sync::atomic`、`Atomic[A-Za-z0-9_]*`、`UnsafeCell`、`static mut`、`unsafe impl Send|Sync`、`memmap2::`、`Mmap(Options|Mut)?`、`MAP_SHARED`、`shm_open`、`mmap(`、`memfd_create`、`shared_memory`、`raw_sync`、`CreateFileMapping`、`MapViewOfFile`、`once_cell`、`sigaction`、`signal_hook`、`nix::sys::signal`、`libc::signal`、`libc::sigaction`。
- 探测 `has_async`:用 `grep -rlE` 搜索 `async fn|async move|async {`、`.await`、`tokio::`、`async_std::`、`futures::`。
- 探测 `has_packed_repr`:用 `grep -rlE` 搜索 outer `#[repr(...packed...)]` 和 inner `#![repr(...packed...)]`。
- 探测 `has_fs_io`:用 `grep -rlE` 搜索 `PathBuf|Path`;再用 `grep -rlE` 搜索 `fs::|File::(open|create)|OpenOptions|.exists()|.metadata()|symlink_metadata|read_dir|read_to_string`。任一 probe 非空即 true。
- 这些 detector 故意保守:拿不准就设为 true;false-positive flag 只多一个无害 worker,false-negative 会跳过整个 pass。`has_fs_io` 以 path types 和 filesystem anchors 为 key,不把裸 `.join(`/`.push(` 当 gate,避免匹配无关 iterator/JoinHandle joins 和 `Vec::push`。
- 同时探测 `Cargo.toml` 中的 `[dependencies] tokio`、`async-std` 等,以在 scope subpath 暂无 `.await` 时也设 `has_async=true`(library crates 常 re-export)。
- 探测 `Cargo.toml` 是否存在(信息用途);在 `run-summary.md` 记录审计对象是 Cargo workspace、single crate 还是 loose `.rs` 文件。`context_roots` 可能以逗号分隔(`build_run_plan.py` 当作 list),所以逐 root probe,而不是把 `a,b` 当一个不存在路径。
- Phase 2 输出目录:解析绝对 `output_dir`,默认 `$(pwd)/.rust-review-results/$(date -u +%Y%m%dT%H%M%SZ)/`。
- 执行 `mkdir -p ${output_dir}/findings ${output_dir}/coverage`。`coverage/` 保存 per-worker coverage-gate audit 文件 `coverage/worker-{N}.md`;workers 写到这里,而不是把表格嵌在 reply。
- Phase 3 代码库上下文:在 `Read` 前用 Bash `find` preflight README/build/manifest,因为 `Read` 缺失文件会中止 turn,且 orchestrator 持有 Bash 不可用 `Glob`。
- 使用 `find . -maxdepth 2 -name 'README*' -o -name 'Cargo.toml' -o -name 'rust-toolchain.toml' -o -name 'build.rs'`,它 no-match 也不失败。不要用 `Bash: ls README*`,zsh 未匹配 glob 会在 `2>/dev/null` 前中止 compound command。
- 略读 `README.{md,rst,txt}` 以及 `Cargo.toml`、`Cargo.lock`、`rust-toolchain.toml`、`build.rs`。
- 写 `${output_dir}/context.md`,YAML frontmatter 包含 `threat_model`、`severity_filter`、`scope_subpath`、`finding_scope_root`、`context_roots`、`has_unsafe`、`has_ffi`、`has_concurrency`、`has_async`、`has_packed_repr`、`has_fs_io`、`output_dir`、`cargo_manifest`(`workspace`/`single-crate`/`absent` 加路径)。
- `context.md` markdown body 包含五节:Purpose(1-3 句)、Scope(`finding_scope_root` 内内容,及其外 findings 为 out of scope)、Entry points(network、files、CLI、IPC、serde deserialization、FFI inputs)、Trust boundaries(sandboxed vs trusted peers vs arbitrary remote)、Existing hardening(fuzzing harnesses、MIRI runs、clippy::pedantic、cargo-deny、cargo-audit)。
- Phase 4 构建运行计划:选择、过滤、路径解析、spawn-prompt 渲染全部交给脚本,以保持 spawn prompts 完整一致。
- 运行 `uv run --no-project ${RUST_REVIEW_PLUGIN_ROOT}/scripts/build_run_plan.py --plugin-root ${RUST_REVIEW_PLUGIN_ROOT} --output-dir ${output_dir} --threat-model ${threat_model} --severity-filter ${severity_filter} --scope-subpath ${finding_scope_root:-.} --context-roots ${context_roots:-.} --has-unsafe ${has_unsafe} --has-ffi ${has_ffi} --has-concurrency ${has_concurrency} --has-async ${has_async} --has-packed-repr ${has_packed_repr} --has-fs-io ${has_fs_io} --max-passes-per-worker 4`。
- 脚本写 `plan.json`、`worker-prompts/worker-N.txt`,若 `--cache-primer=true`(默认)还写 `worker-prompts/cache-primer.txt`,并在 stdout 打印 JSON summary。任何 missing prompt 都 non-zero exit;展示消息并停止。
- 默认 `--max-passes-per-worker 4` 时,planner 选约 8 个 clusters → M≈13 workers(纯 safe Rust,无 FFI/concurrency/async;info-disclosure always on),约 10 clusters → M≈15(concurrent safe Rust),约 15 clusters → M≈23(full Rust:unsafe + FFI + concurrency + async,外加 `has_fs_io` 时 `input-os-safety`、`has_packed_repr` 时 `layout-safety`)。M 是 post-chunk worker count(`plan.workers.length`)。
- chunking 会拆分 multi-pass non-consolidated clusters(如 `panic-dos`、`memory-safety`);两个 consolidated clusters `unsafe-boundary`、`concurrency-locking` 永不被 chunk:每个一个 worker,一次构建共享 inventory 并跑所有 phases。`recursion-dos` 是 one pass per worker。
- 脚本返回后 `Read plan.json` 获取结构化 selection;绝不重新推导 filtering 或 paths。
- `--max-passes-per-worker N` 限制每 worker pass 数。planner 确定性把任何 pass 数 >N 的 non-consolidated cluster 拆成 `ceil(K/N)` 个连续 chunk;每个 chunk 成为独立 `rust-review-worker` spawn,cluster_id 带 `-{i}` 后缀(如 `panic-dos-1`、`panic-dos-2`)。consolidated clusters 豁免,无论 pass 数或 override 都不 chunk。
- 共享 prompt-cache prefix 和 `Cluster prompt:` path 在各 chunk 间 byte-identical,cache primer 仍能 warm 每个 worker。默认 4 根据 `manifest.json` 中的 heavy-tail clusters 校准。部分 output-heavy non-consolidated clusters 声明更小的 manifest-level `max_passes_per_worker` override,让每个昂贵 pass 单独 worker(如 `recursion-dos`)。传 `--max-passes-per-worker 0` 禁用所有 chunking,包括 manifest overrides(one worker per cluster)。
- Phase 5 创建簿记任务:设 M=`plan.workers.length`。创建 `cluster_task_ids[]`,与 `plan.workers` 一一对应,全部 `pending`。
- Task ledger 仅 orchestrator 簿记(TUI visibility + Phase-7 retry tracking);workers 从不读写。
- 每个 worker 一次 `TaskCreate`,metadata 填 `kind=cluster`、`worker_n`、`cluster_id`、`spawn_prompt_path`、`pass_prefixes`、`attempt=1`,全部从 `plan.workers[i]` 原样复制。按 `plan.workers` 顺序跟踪 `cluster_task_ids[]`。
- Phase 6a cache primer:若 `plan.run.cache_primer == true`,`build_run_plan.py` 已写 `${output_dir}/worker-prompts/cache-primer.txt`。在单独 assistant message 中 spawn:`Read` 该文件,原样作为 `Agent` prompt,`subagent_type=rust-review:rust-review-worker`,`model=${worker_model}`,`description=Rust review cache primer`,不加 `run_in_background`。
- 脚本写的前缀与 `worker-1.txt` 到 `<context>` block 为止 byte-identical;该 byte-identity 让并行 workers cache hit。Primer trailer 含 `Cache primer: true`,worker system prompt 把它当 first-class mode,零 tool call 返回 exactly `worker-PRIMER abort: cache primer (no analysis performed)`。丢弃该 abort 行,Phase 7 忽略(无 `worker-N` id)。foreground spawn 已串行,无需 `sleep`。`plan.run.cache_primer == false` 时跳过 Phase 6a。
- Phase 6b spawn M 个真实 workers:workers 必须 foreground spawn(无 `run_in_background` 字段,或 `run_in_background=false`)。这里的 parallel 指一条 assistant message 包含该 wave 的 `Agent` calls,本身已并发。M 大时拆成连续 waves,每 wave ≤16 calls,一条 message 一个 wave。
- background spawns 不是并行化方式,且会破坏 Phase 6a primer cache:每个 worker 首轮 full cache-creation(`cache_read_input_tokens=0`),约 15K-token primer 被浪费 M 次。发送 spawn message 前审计草稿:每个 `Agent` call 都不能有 `run_in_background` key;若写了 `run_in_background=true`,删除。
- required spawn shape:发出单条 assistant message 包含该 wave 的 `Agent` tool invocations。sequential spawning(每 message 一个 `Agent` call)会串行化 review,也是错的,但会 loud failure(timing);background-spawn failure 是 silent cost。
- M 超过 per-message cap 时 wave 规则:harness 限制单条 assistant message 可 dispatch 的 `Agent` calls 数(Claude Code 观察到约 20;真实 25-worker run 静默只保留前 20,剩余 5 需第二条 message)。M>16 时提前计划 waves:拆成连续 ≤16 个 `Agent` calls 的 waves,每 wave 自己的单条 assistant message。
- wave 内:所有 `Agent` calls 在一条 message,foreground(无 `run_in_background`),与单 wave run 相同。
- 跨 wave:wave k+1 是单独 message,只能在 wave k 的 `Agent` calls 全部返回后发送(tool-use message 结束 turn)。waves 因此相互串行;这是正确且 loud 的。不要尝试 overlap。
- 绝不要为了把更多 workers 塞进一条 message 而使用 `run_in_background=true`。更多 waves,绝不用 background;background 破坏 primer cache,是本技能防范的 cardinal error。
- 跨 wave cache:primer prefix 约 5 分钟 cache TTL,每次 hit 刷新,连续 waves 会持续命中(25-worker run 确认第二 wave `cache_read≈14 K`)。若后续 wave 距前一 wave >约 5 分钟,先在自己的 message 中重新 spawn Phase-6a primer 以重新 warm prefix。
- 平衡 waves(如 M=25 → 13+12,而不是 20+5),避免某 wave 贴近 cap 且最后 wave 是 tiny straggler。所有 waves 返回后,带着完整 M 个 worker results 进入 Phase 7。
- 对每个 worker `N ∈ [1..M]`(在其 wave 中):1)`Read ${output_dir}/worker-prompts/worker-N.txt`;2)把文件内容原样作为 `Agent` tool 的 `prompt` 参数传入:`subagent_type=rust-review:rust-review-worker`,`model=${worker_model}`,`description=Rust review worker N`,`prompt` 为 `worker-N.txt` 全文无编辑,`run_in_background` 字段必须省略或 `false`,绝不能 `true`。
- spawn prompt 是唯一权威。原样传递:每个字段都是 worker self-check 所需;任何偏差触发 `worker-N abort: spawn prompt malformed`。
- 拒绝 anti-patterns:传 `run_in_background=true`;M 大时把 >约 16 个 `Agent` calls 塞进一条 message(harness 会静默保留前约 20 并丢掉其余)——用 balanced waves ≤16,绝不用 background spawns;手写 spawn prompt 而不读 `worker-N.txt`;插入 Task 相关指令(如 `first call TaskList`、`Assigned task id: <N>`),workers 没有 Task tools;传递前编辑 rendered prompt(裁剪 redundant fields、折叠 pass lists)。
- Phase 7 等待 workers 并分类结果:Phase-6 的 `Agent` invocations 会阻塞直到每个 worker 返回。检查每个 worker return text,按顺序应用 classifier,first match wins。
- classifier 1:`worker-N complete:` → provisional success。解析 `wrote N finding files` count,然后运行 artifact validator,再 `TaskUpdate` 到 `completed`。
- classifier 2:`abort: spawn prompt malformed`、`abort: pre-work budget exceeded` 或 `abort: TaskList unavailable`(legacy)→ non-retryable orchestrator bug。停止 run,surface abort + spawn-prompt path。重跑同一 prompt 会重复失败;pre-work-budget exhaustion 总是意味着 worker 无法通过 self-check,重试无济于事。
- classifier 3:其他 `worker-N abort:` → retryable。标记 `pending`,设 `metadata.abort_reason`,`needs_respawn=true`,`attempt` 加一。
- classifier 4:`Agent` errored 或没有 `complete:`/`abort:` token → retryable。同 classifier 3(transient worker crash)。
- 若任何 non-retryable,停止。否则 re-spawn 前清理每个 retryable worker 的 prefix-space:Phase-7 index 从 disk 构建,崩溃 attempt 的 higher-id stragglers 否则会被 resurrect 到 report。遍历 worker 实际 `pass_prefixes`(来自 task metadata),逐个替换 `${pfx}`,不要用字面 `PREFIX`。
- 清理命令:`for pfx in PREFIX1 PREFIX2; do find ${output_dir}/findings -maxdepth 1 -type f -name '${pfx}-*.md' -delete; done`。zsh-safe:`find ... -delete` no-match 也不 abort(`rm PREFIX-*.md` glob 会 abort)。
- 对每个 `pending` retryable 且 `attempt <= 2` 的 worker,在一个 parallel block 中 re-spawn(每 cluster 最多 2 attempts)。第一次失败后 `attempt` 刚增到 2,所以 guard 必须允许 2 才允许唯一一次重试;`attempt < 2` 会阻止所有重试。第二次失败增到 3,不满足 `<= 2`,结束重试。replacement workers 对每个 prefix 复用 deterministic finding IDs,所以清空 prefix-space 加 fresh write 得到一致 shard/coverage/disk set。
- 对每个 provisional `complete:` cluster,在标记 completed 前用 `plan.json` 验证 worker-owned shard、coverage file、coverage rows、filed IDs、claimed finding count。可一个 worker 一条命令,也可多 worker 一条命令。
- 两种 claimed-count 形式都有效;不要传裸 `worker-N=N` 而不分组到 `--claimed-count` flag 后或重复 flag。示例:`uv run --no-project ${RUST_REVIEW_PLUGIN_ROOT}/scripts/validate_artifacts.py ${output_dir}/plan.json --worker worker-N --claimed-count worker-N=<claimed_count_from_complete_line>`。
- 也可用多 worker 形式:`uv run --no-project ${RUST_REVIEW_PLUGIN_ROOT}/scripts/validate_artifacts.py ${output_dir}/plan.json --worker worker-1 --worker worker-2 --claimed-count worker-1=0 worker-2=3`,或重复 `--claimed-count worker-1=0 --claimed-count worker-2=3`。
- 若 validation non-zero exit,把 completion 视为 malformed 且 retryable(classifier row #4):标 task `pending`,把 validator output 存入 `metadata.abort_reason`,设 `needs_respawn=true`,`attempt` 加一。
- Missing `findings-index.d/worker-N.txt`、missing `coverage/worker-N.md`、missing coverage rows、invalid `skipped:` rows、filed IDs 不在 shard 或 disk、claimed-count mismatch 都算 malformed completion。达到 retry cap 后,cluster task 保持 incomplete,并把 validator output surface 到 `run-summary.md` 和最终响应。只有 validation-clean provisional completions 可以 `TaskUpdate` 到 `completed`。
- 构建 index:canonical index 是 disk 上实际存在的 finding files,不是 shard union。从 disk 构建保证 finding 即使没有匹配 shard entry(worker 在 `Write` 和 shard-append 间崩溃,或 single-prefix empty-shard trap)也会被 dedup → fp-judge → REPORT/SARIF 捡起而不是静默消失,并保证每个 index entry 都解析到真实文件。
- index 命令:`find ${output_dir}/findings -maxdepth 1 -type f -name '*.md' 2>/dev/null | sort -u > ${output_dir}/findings-index.txt`。`find` no-match 不失败;空 `findings/` 产生空 index,是明确的 zero findings 信号。`sort -u` 折叠 Phase-7 retry duplicates。
- Reconcile against per-worker shards:disk 上但不在任何 shard 的 path 是其 worker 未记录的 orphan。它已经在上面的 index 中(所以未丢),打印它以 surface bookkeeping gap。Non-fatal。按 basename reconcile(finding ids 唯一),避免 path-format 差异制造 false orphans。
- Reconcile 命令:`if [ -d ${output_dir}/findings-index.d ]; then comm -13 <(find ${output_dir}/findings-index.d -maxdepth 1 -type f -name 'worker-*.txt' -exec awk 1 {} + 2>/dev/null | sed 's#.*/##; /^[[:space:]]*$/d' | sort -u) <(find ${output_dir}/findings -maxdepth 1 -type f -name '*.md' 2>/dev/null | sed 's#.*/##' | sort -u); fi`。
- Shards 仍是 per-worker audit trail(`validate_artifacts.py` 会检查)和 dedup-judge crash-recovery fallback,但不再 gate 什么进入 pipeline。对每个 orphan basename 通过 `plan.json` 映射 `<PREFIX>` 到 owning worker,并在 `run-summary.md` 注明该 worker shard incomplete;finding 已在 index 未丢,但 bookkeeping gap 应可见。交叉检查 index line count 与 `wrote N` worker claims 之和;记录 mismatch 但不要 abort。
- task updates 和 index creation 后,运行 `TaskList` 并写 `${output_dir}/run-summary.md`,包含:resolved parameters(`threat_model`、`severity_filter`、`finding_scope_root`、`context_roots`、capability flags `has_unsafe`/`has_ffi`/`has_concurrency`/`has_async`、Cargo manifest status)。
- `run-summary.md` 还包含 worker outcome table(`worker_n`、`cluster_id`、claimed finding count、shard line count、coverage-file path `coverage/worker-{N}.md`、task status、retry/abort state)、`findings-index.txt` line count 与 worker claims 的 mismatch、以及 Phase 8 后的 judge status 或 judge skipped/failed 原因。
- 如果任何 Phase-5 cluster task 不是 `completed`,或任何 worker 返回的 `complete:` 行带 `truncated at hard cap` token(在搜索每个 pass 前撞到 tool-call cap;coverage file 会显示一个或多个 `cleared (NOT SEARCHED — truncated at hard cap)` rows),在 `run-summary.md` 和最终响应中 prominently 包含它。hard-cap-truncated worker 对 ledger 标 `completed` 但仍是 partial result:不要让 completed status 在成功报告后隐藏 incomplete coverage。
- 零 findings 也始终运行 Phase 8:两个 judges 在 empty index 上 short-circuit;dedup-judge 写 minimal no-op `dedup-summary.md`,fp-judge 写 empty `REPORT.md`/`REPORT.sarif`,让 SARIF consumers 得到 stable artifact set。
- Phase 8 judge pipeline 顺序执行 dedup → fp+severity:Entry 是 `findings-index.txt` 存在;Exit 是 dedup-judge 和 fp-judge 返回,`dedup-summary.md`、`fp-summary.md`、`REPORT.md`、最好 `REPORT.sarif` 已写。
- 每个 judge 的完整协议是其 system prompt(`agents/rust-review-{dedup,fp}-judge.md`);spawn prompts 只传 per-run variables。不要引用 `prompts/internal/judges/`,这些文件不存在。
- 两个 judges 必须 SEQUENCE 运行,不能并行。存在硬数据依赖:fp-judge 必须看到 dedup-judge 写的 `merged_into`/`also_known_as` annotations,且它只跳过已带 `merged_into` 的文件。
- 如果两个 `Agent` calls 在一条 message 中,它们会并发运行;fp-judge 在任何 merge annotations 存在前读取 findings,把每个 duplicate 当 separate primary,且因 `dedup-summary.md` 尚不存在触发 `dedup did not run` fallback,产生 inflated duplicated `REPORT.md`/SARIF。
- 在自己的 assistant message 中 spawn dedup-judge,等它返回 `dedup-judge complete:`(或 `abort:`)后,再在单独 message 中 spawn fp-judge。发送 fp-judge spawn 前确认 dedup 完成:`Bash: test -f ${output_dir}/dedup-summary.md` 必须成功(或看到 dedup `complete:` token)。绝不把两个 judge `Agent` calls 放同一 message。
- 第一条 message:`Agent(subagent_type=rust-review:rust-review-dedup-judge, description=Dedup judge, prompt=output_dir: ${output_dir})`。等待返回并分类后再继续。
- 然后单独 message:`Agent(subagent_type=rust-review:rust-review-fp-judge, description=FP + severity judge, prompt=output_dir: ${output_dir} sarif_generator_path: ${sarif_generator_path})`;把 `sarif_generator_path` resolve 到 `${RUST_REVIEW_PLUGIN_ROOT}/scripts/generate_sarif.py`。
- Judge failure handling:与 Phase 7 classifier 同形,应用到 judge return text。`… complete:` → success。`… abort:` → non-retryable for that judge。Surface abort line 加 `ls -l ${output_dir}/findings-index.txt`,然后仍运行 Phase 8b(SARIF + `REPORT.md` safety net 保证 artifact set,即使 judge aborts),并停止而不 spawn 后续 judges。Stop 指不继续 judge pipeline,不指跳过 Phase 8b。
- 没有 `complete:`(help message/error/question)→ retryable once。用 `SendMessage(to=<agentId>, …)` 而不是 fresh spawn(agent 已付 protocol-parse cost)。包含 `findings-index.txt` 中显式 finding paths。第二次仍失败则 surface transcript 并继续 Phase 8b。
- Phase 8b report safety net:Entry 是 fp-judge 返回或 run 提前 abort;Exit 是 `${output_dir}/REPORT.sarif` 和 `${output_dir}/REPORT.md` 都存在。
- 运行 `test -d ${output_dir}/findings && uv run --no-project ${RUST_REVIEW_PLUGIN_ROOT}/scripts/generate_sarif.py ${output_dir}`。
- 只要 `findings/` 存在就无条件运行 SARIF generator:它是 idempotent(full overwrite),zero-survivor runs 输出 `results: []`,并处理 partial runs(无 `fp_verdict` 的 findings 作为 `LIKELY_TP` 输出,不受 `severity_filter` 影响,因为 severity 未 judge-validated,标记 `unjudged: true`/`severity_validated: false` 并带 `[UNVALIDATED SEVERITY — not judged]` message prefix,确保 inferred severity guess 不会在 medium/high filter 下静默丢)。始终 overwrite 防止 fp-judge mid-write crash 留下 corrupt `REPORT.sarif`。
- 如果 generator 在 stdout 打印 `WARNING: skipped N …`(还记录 `invocations[].properties.skipped_findings` 到 SARIF,并为每个 dropped file 发 warning notification),说明一个或多个 finding files 不可读或无 parseable frontmatter,被排除出 report。这是 dropped result,必须在 `run-summary.md` 和最终响应中 prominently surface count 和 paths,像 non-completed cluster task 一样。不要让 otherwise-clean SARIF 隐藏 loss。
- 保证 `REPORT.md` 存在。与 SARIF(mechanical)不同,`REPORT.md` 是 fp-judge curated artifact,所以不要覆盖 judge 写的。fp-judge 用 Bash heredoc 写 `REPORT.md`,不用 `Write` tool,因为 harness 对 subagent report files 阻止 `Write` tool;不要通过重新要求 `Write` 来 fix judge。orchestrator 是 main agent,不受该 block,自己的 `Write` 可用。
- 检查 `REPORT.md`;若缺失(judge 崩溃,甚至 Bash-heredoc write 失败,或它把 report 作为 chat text 返回而未写文件),orchestrator 自己写 `REPORT.md`,而不是让 run 失败。
- 若 fp-judge 在 transcript 中返回 report body,`Write` 该文本 verbatim 到 `${output_dir}/REPORT.md`。
- 否则从 on-disk findings synthesize:取 survivor primaries(`fp_verdict ∈ {TRUE_POSITIVE, LIKELY_TP}`,无 `merged_into`;若 judge 从未运行,把无 `fp_verdict` 的 finding 当 survivor)列在 `findings-index.txt`,对 judged survivors only 应用 `context.md` 中的 `severity_filter`。
- Unjudged findings(无 `fp_verdict`)无论 filter 都包含,并渲染在 `Unvalidated (severity not judged)` section,带 `[UNVALIDATED SEVERITY — not judged]` label,镜像 SARIF behavior,确保 strict filter 不会静默丢它们。
- 写 `REPORT.md` 时镜像 fp-judge template:YAML frontmatter(`stage: final-report`、`threat_model`、`severity_filter`、`total_primaries`、`reported_findings`),severity-distribution table,然后每个 reported finding 按 severity 分组一节(CRITICAL/HIGH embed Description/Code/Data flow/Impact/Recommendation body;MEDIUM/LOW reference finding file)。
- 无论哪种方式,在 `${output_dir}/run-summary.md` 注明 `REPORT.md` 是 orchestrator-synthesized(非 judge-authored)。只有 `${output_dir}/findings/` 不存在(Phase 2 失败)时才跳过 SARIF generator 和该检查。此 phase 后更新 `run-summary.md` 的 judge/SARIF/report status。
- Phase 9 返回报告:Entry 是 Phase 8b 完成;Exit 是 Success Criteria 每项验证为 true,`REPORT.md` 返回调用方。
- 在构造响应前走 Success Criteria checklist,并针对 on-disk artifacts 确认每项(cluster tasks 用 `TaskList`,files 用 `ls`/`Read`)。若任何 criterion 失败,在响应中 prominently surface failure,不要把 partial run 藏在成功报告后。
- `Read ${output_dir}/REPORT.md` 并把内容返回调用方。追加 Artifacts list,指向 `findings/`、`findings-index.txt`、`run-summary.md`、`dedup-summary.md`、`fp-summary.md`、`REPORT.md`、`REPORT.sarif`。
- Finding file frontmatter 三阶段:1)Worker 写 base fields(`id`、`bug_class`、`title`、`location`、`function`、`confidence`、`worker`)+ seven body sections。
- 2)Dedup-judge 在 duplicates 上加 `merged_into`,或在 absorbed primaries 上加 `also_known_as` + `locations`。
- 3)FP+Severity judge 在每个 primary 上加 `fp_verdict` + `fp_rationale`;对 survivors(`TRUE_POSITIVE`/`LIKELY_TP`)再加 `severity`、`attack_vector`、`exploitability`、`severity_rationale`。
- Authoritative schema 在 `agents/rust-review-worker.md` 的 Finding File Format。
- Bug classes/clusters 权威来源是 `prompts/clusters/manifest.json`。37 个 bug classes 位于 `always`-gated clusters(cluster 始终运行);其中 35 个 always fire,2 个——`logic-correctness` 中的 `adversarial-trait`(TRAITADV)和 `closure-panic`(CLOSUREPANIC)——额外带 `requires: has_unsafe`,所以只在 `has_unsafe=true` 时 fire。所有 conditional gate 启用时共 69 个 bug classes。
- `memory-safety` cluster 由 `has_unsafe` gate(其所有 bug classes 都需要 `unsafe`);PATHJOIN 和 TOCTOU 通过 `input-os-safety` 由 `has_fs_io` gate;PACKEDREF 位于 conditional `layout-safety` cluster(`has_packed_repr`);PTREXPOSE 通过 `info-disclosure` cluster always-on。`unsafe-boundary` 和 `concurrency-locking` 完全 consolidated(runtime 不重新读取其 sub-prompts)。
- Success Criteria:每个 Phase-5 cluster task 都 `completed`(用 `TaskList` 验证)。
- Success Criteria:`${output_dir}/run-summary.md` 存在,记录 resolved scope/context、Cargo manifest probe result、worker claims vs index count、task status、judge/SARIF status。
- Success Criteria:每个 primary finding(无 `merged_into`)都有 `fp_verdict` + `fp_rationale`;每个 survivor(`TRUE_POSITIVE`/`LIKELY_TP`)还有 `severity`、`attack_vector`、`exploitability`、`severity_rationale`。
- Success Criteria:`REPORT.md` 存在,并按 `severity_filter` 过滤(Phase 8b safety net 保证即使 fp-judge fail to write 也存在)。
- Success Criteria:`REPORT.sarif` 存在(Phase 8b safety net 保证)。
适用边界 · 注意事项
- 纯 C 或纯 C++ 代码库不适用,应使用 `c-review`。
- 智能合约(Solana programs、NEAR contracts、Ink!)不适用,应使用 `solana-vulnerability-scanner` 或合约专用技能。
- 内核态 Rust 驱动且无 userspace allocator 时覆盖不完整,只能标为 advisory;秘密/密钥内存卫生(zeroization、`Zeroize`/`ZeroizeOnDrop`/`secrecy`、栈/堆残留副本)也不适用,应使用 `zeroize-audit`。