⏳ This skill is pending AI review.
Scores will appear once the review pipeline completes.
code-review
Use when code has been written, before committing, or when merging a PR; also when the user says "review my code", "check before commit", "take a look at this change", "PR review", or asks for a strict review/merge gate.
Choose how to use this skill
You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.
1. Native installer
This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.
Do not guess an installer command or replace an existing version without reviewing the diff.
2. Complete package recommended
Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.
No complete ProSkills package is published for this listing yet.3. Prompt-only
Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.
Need only the instruction file?
Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.
No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.
// RATINGS
Not yet listed on ClawHub or SkillsMP
// README
code-review-skill
English | 简体中文
Evidence-backed code review. Static-first, probe-driven, and gated.
The skill sources live in skills/code-review/ (English) and skills/code-review-zh/ (Chinese), independently of any host's installation directory. The current stable release is v0.1.1; see VERSION.
Most AI reviews stop at “this looks risky.” This skill produces a review you can audit: every finding points to a location, a contract or code path, and a severity; every gate decision is rendered from machine-readable JSON. It reads the diff, surrounding source, tests, and project rules — no build, test run, or coverage report required.
It never modifies the code under review.
Latest news
v0.1.1 (2026-11-05)
Added
- code-review: surface unverified items and tighten behavior checks
- release: publish stable versions after validated VERSION changes
Fixed
- code-review: return immediately after saving probe JSON
Release status
v0.1.1 is a stable release within the scope described below.
Pre-1.0 contract or format changes will be documented in NEWS.
- Severity is three levels:
high,medium,low. Records written by the earlier four-level version, including anycriticalvalue, are not compatible;collect-metricsrejects them. Re-run the review or migrate the records before using them with this release. - Scope: static-first review with four probes and JSON-derived report/gate output. It does not replace tests, CI, or human review.
- Validation: 87 offline pipeline, NEWS, installation, bilingual, and locale tests; clean-project end-to-end gate runs; additional probe smokes. These are regression checks, not a general recall claim. Concurrency-defect detection has not been validated.
For the next stable release, update VERSION (for example, to 0.1.1) and push it to main. After Validate skills succeeds, the release workflow generates NEWS from commits, updates the README release information, and publishes the version tag and GitHub Release. Existing releases are preserved; failed CI does not publish.
Contents
- Latest news
- Release status
- Why this skill
- Quick start
- How it works
- Evidence contract
- Configuration
- What this is not
- Validation
- Requirements
- Repository layout
- Contributing
- License
Why this skill
No self-grading.
The implementation context does not sign off on its own work. Four probes run in fresh contexts, each files its own evidence, and the gate is derived from those records.
This is separation of duties, not independent human adjudication; all probes still use the same model.
| Static-first | Works on a fresh clone or a partial checkout. Coverage is optional, not a prerequisite. |
| Evidence contract | A finding without a location, summary, and checkable evidence is not a finding. |
| Four probes | Behavior, tests, rules, and impact are separate questions with separate reports. |
| Machine-checkable gate | Counts and blockers come from JSON, not from prose the model retyped. |
| Auditable output | The human report and the JSON records are rendered from the same source and verified against each other. |
Quick start
Install
Run this command inside your project:
# English (default)
npx -y skills add kyy-logs/code-review-skill --agent codex --skill code-review --yes
# Chinese
npx -y skills add kyy-logs/code-review-skill --agent codex --skill code-review-zh --yes
Use --agent claude-code for Claude Code. Codex installs to .agents/skills/<skill>/; Claude Code installs to .claude/skills/<skill>/. Node.js 22.20+ is required. Install one language per project. The Python installers also accept --lang en|zh-CN and default to English.
Run
Ask in Claude Code:
Review these changes with code-review.
Or invoke the skill directly:
/code-review
For a fixed range:
Use code-review on commits abc123..def456
The skill supports two review modes:
worktree— staged, unstaged, and untracked changes against the currentHEAD.commits— a fixedbase..headrange.
Read the result
docs/work/active/{slug}/
├── code-review.md # human report
└── probes/
├── behavior-review.json
├── test-coverage.json
├── repo-guard.json
└── impact-analysis.json
How it works
The four probes use the same model in fresh contexts. This is separation of duties, not independent human review. A passing gate is not human approval, CI, or proof that no issues remain. The current skill has no separate reviewer or finding-verification stage; raw findings feed the report and gate.
- Scope — determine
worktreeorcommits; record base and head as commit IDs. - Probes — dispatch four independent agents with the same scope and diff, each writing its own probe JSON. Run in batches if host capacity is limited, or record a serial fallback if agents are unavailable. Cross-probe duplicates are retained and counted; actual speed and token savings remain unverified.
- Sync — validate probe JSON and derive counts with
collect-metrics. - Reconcile — high findings must be closed or marked false-positive by the probe.
- Report — render the human report’s fact sections from JSON, then verify them.
- Gate — required-probe failures and open high findings block the merge.
The four probes
| Probe | Required | Question it answers |
|---|---|---|
behavior-review | yes | Does the changed behavior match its actual contract and implementation path? |
test-coverage | yes | Are the tests meaningful, and is incremental coverage measured when data exists? |
repo-guard | yes | Does the change violate repository or project rules? |
impact-analysis | no | Who calls this, what contracts change, and what is the performance/architecture blast radius? |
Probes are intentionally narrow. behavior-review is not a linter, and repo-guard is not a general bug scanner.
Language and stack coverage
The default repo-guard workflow selects .java, .ts, .tsx, and .vue files for pattern scanning. The built-in rule pack includes Java- and Spring-specific guidance; selecting an extension does not establish complete framework or language coverage.
AI semantic checks can inspect relevant code beyond those extensions, but their results depend on the model and available contracts, source, and tests. The historical cases below exercise particular stacks and skill snapshots; they do not establish equally validated support across all stacks. Use applicable project rules and record unverified depende
// HOW IT'S BUILT
// FILES
24 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.
skills/code-review/SKILL.md16 KBb31ca329a757upstream ↗
--- name: code-review description: Use when code has been written, before committing, or when merging a PR; also when the user says "review my code", "check before commit", "take a look at this change", "PR review", or asks for a strict review/merge gate. --- # Code Review > Check for code defects, test quality, and change impact, and confirm that the issues found are closed. ## Resources and Execution Location - `references/`, `scripts/`, and `assets/` are relative to the directory containing the `SKILL.md` read for this run. - Run shell commands from the **reviewed project root**; `docs/work/`, code paths, and project rules all belong to the reviewed project. - `CODE_REVIEW_SKILL_DIR` below is the absolute path of the skill installation directory. Set it on every shell invocation, or replace it directly with the actual absolute path; do not assume variables persist across different shell invocations. - Scripts require basic commands such as Bash 3.2+, Git, awk, grep, and sed. Invoke them via `bash`; no executable bit is needed. - Report parsing requires Python 3.9+ and PyYAML; `python` is invoked by default, and `CODE_REVIEW_PYTHON` can specify the interpreter's absolute path. For installation, see the release repository README; the in-repo entry point is `scripts/install.py`. ## Scope - Review functional, bug-fix, refactoring, or configuration changes that affect code behavior. - Pure documentation, formatting, or typo changes that do not affect code behavior may be skipped. - Static review is still possible when tests fail or have not been run; the report must state the test status and must not claim that merge conditions are met. ## Inputs | Input | Required | Description | |---|---|---| | git repository + diffable changes | Yes | Specify whether to review uncommitted changes or a committed base/head range | | Acceptance criteria | No | When provided, check them off one by one under "Optional Wrap-up" | | Coverage data / command | No | When provided, used for the optional measurement in `test-coverage`; without it, static quality checks on the relevant test code still run | | Rule pack | No | Defaults to the skill's built-in `references/rules/`; can be overridden with `CODE_REVIEW_RULES_DIR` | | Review identifier | No | `{slug}`, defaults to `<date>-<topic>` | The artifact root `{output_root}` defaults to `docs/work`; override it with project `.code-review/config.json` `output_dir` or `CODE_REVIEW_OUTPUT_DIR`. This run's artifacts are written to `{output_root}/active/{slug}/`. ## Review Process > Check items are not hardcoded in this file; they are driven by the `references/probes.yaml` registry. > Probe implementation details are in `references/probes/<probe-name>.md`; this file covers **main workflow orchestration and shared probe execution boundaries**. > > **Execution boundary: static review by default.** For coverage checks, prefer reusing an existing report; > when measurement is needed, coverage commands may be run within what this round's task allows. Other checks remain read-only evidence. > Do not modify the reviewed source code during review. Test and CI output are review evidence; > **all tests green does not mean the behavior is correct**. 1. **Determine the review scope**: **Choose a mode**; when unspecified, review uncommitted changes by default. The base/head below are recorded as concrete commit IDs. - **Uncommitted changes (worktree)**: base is the current HEAD. Use `git diff <base> --` to collect staged and unstaged changes; use `git ls-files --others --exclude-standard -z` to collect untracked files, and review their full contents as newly added code. - **Committed range (commits)**: use the user-specified base/head, and collect the diff with `git diff <base> <head> --`. When head is omitted, use the current HEAD and review only that commit range. **Read the corresponding source**: - In worktree mode, read the working tree files. - In commits mode, use `git show <head>:<path>` to read the target version, and read base when an older version is needed. File scans use only on-disk files consistent with the target version. **Record and hand off**: save the mode, base, target (working tree or head), changed-file list, and diff, for use by all probes. Use NUL separators when processing the file list programmatically, and pass each path as a quoted argument. After collecting scope, initialize a new run before dispatching: ```bash bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" init {slug} ``` The returned JSON gives `review_dir`, `output_dir`, `line_target`, and `branch_target`. Existing active directories, archived runs, or event history with the same slug are rejected. Use a unique slug for a new review; explicitly continuing the same run reuses its directory without init. Never dispatch two runs writing the same directory. Pass the actual directory and effective targets to all probes, overriding the registry's coverage defaults. Init creates the report template. Source inspection is read-only; the workflow writes reports, probe JSON, policy.json, events, and archive metrics. 2. **Read the registry and determine the probe list**: Read `references/probes.yaml` and decide by `requires`: - Specified but missing / unreadable → `error` - Optional input not provided → `skipped`, stating what is missing - Available → execute 3. **Run probes in parallel**: **Dispatch**: the main executor dispatches an independent agent per probe according to the registry, providing the review scope, diff, project root, skill path, and that probe's inputs and `params`, and including in the dispatch prompt "Execution boundary" above and "Shared probe constraints" in this step. Also pass any provided requirements, acceptance criteria, and change context as text or file paths, and ask each probe to read relevant material. Note missing inputs without inventing them. If dependency source or contract material is already available, pass its path, version, and origin for on-demand reuse; do not pre-download all dependencies. The agent reads the instructions pointed to by the registry entry's `file` and `references/output-schema.md`, and executes according to the parameters. Run in batches when concurrent slots are insufficient; if independent agents are unavailable, run serially and record the limitation. **Shared probe constraints (include in dispatch)**: an agent checks only its own probe, and does not re-run the whole process or dispatch further agents; the source version follows step 1. A finding must explain how this change introduced or triggered the issue; unrelated pre-existing issues are described separately and are not included in this round's finding list or counts. Reuse the review scope and material confirmed and handed off by the main executor, together with context already read by this agent. Read more as needed; merge independent read-only queries where possible, run dependent queries step by step, and preserve accurate line numbers and necessary context on first reads of source. Limit local searches to the reviewed repository, explicitly provided related directories, and cache paths corresponding to dependency coordinates. Prefer `rg`, starting with changed files and known relevant directories and narrowing by file type; return only paths when locating files. Expand the scope only for a specific evidence gap; avoid searching the whole repository with `grep -r` and then filtering the output. Record missing or unreadable evidence without expanding to the disk root or user directory, or repeating searches without a new lead. Write only `{output_root}/active/{slug}/probes/<probe-name>.json`, not shared reports or metrics. Save identified but unresolved specific concerns in `unverified_items` under the output contract, rather than only in the return message. Once checks are complete, first wait for any background tasks you started to finish, or stop tasks that are no longer needed and confirm they have exited. Then validate against the output contract, save the final JSON, and immediately return the handoff message. After saving, do not search, gather more evidence, or repeat explanations of findings. **Handoff**: every probe must have JSON for this run. Record skip or error reasons according to the output contract; on execution errors, preserve findings already made and let the other probes continue. If an agent fails to start or returns no valid result, the main executor records `error` and the reason without reusing old results. The return message only briefly states the JSON path, the actual checked scope, and necessary limitations, including specific evidence gaps; the main executor incorporates the relevant limitations into the final report. Record `duration` when reliable timing is available; the sum of per-probe durations does not represent this round's wall-clock time. `test-coverage` hands off the report path, target version, numerator/denominator, and percentage for valid coverage via its return message, and the main executor reports them; `--line` accepts only diff line coverage: ```bash bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" coverage {slug} --line {diff-line-coverage} ``` Add `--branch {branch-coverage}` when a branch value exists, or report only that item. Omit unmeasured or inapplicable metrics without filling in 0; do not call it when neither value is valid. A single-item report updates only that item, and the script does not verify the statistical basis. **Aggregation**: after all agents finish, the main executor runs the synchronization command. The script reads `required` from the registry and computes counts at each level from the probe JSON; do not rewrite raw results or hand-fill counts. Do not deduplicate across probes yet; count by original finding ID, which may include duplicate records of the same root cause: ```bash bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" sync {slug} --probes ``` This step invalidates the previous batch's reconciliation state, and reconciliation is not recorded yet. 4. **Reconciliation completeness check**: Derive the final counts automatically from the probe JSON: ```bash bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" sync {slug} ``` `required` only controls whether that probe's `failed/error` execution status directly blocks; it does not change the finding closure requirement. High findings from any probe enter reconciliation and still block merge until closed; medium/low are recorded in the report and excluded from the reconciliation total: ```text RECONCILE_TOTAL = total number of high findings RECONCILE_DONE = number of those whose status is reconciled or false-positive RECONCILE_PENDING = TOTAL - DONE ``` Run it even when there are no high findings, explicitly recording the zero result; do not substitute old counts or hand-filled numbers. Fix format errors first; if records are missing, IDs are duplicated, or statuses conflict, synchronization fails, no counts are written, and the old result must not be used to pass. When there are unresolved issues (`RECONCILE_PENDING > 0`), still deliver the report this round and list the blocking items; do not proceed toward merge. After follow-up fix tasks complete, re-review the affected parts; see `execution.max_retry` in `references/probes.yaml` for the re-dispatch limit. 5. **Compile the report**: Fill in the review scope, explanation, fix suggestions, and limitations per the template; do not embed raw JSON in the body; when acceptance criteria are provided, complete the item-by-item check-off first. The script displays `unverified_items` as "Pending confirmation" in Findings without including them in severity counts; original records retain their IDs, evidence, and counts; when showing the number of distinct issues, group them by the same root cause and consequence and list the corresponding IDs, without raising severity due to duplication or substituting the displayed count for probe or reconciliation counts. Run the following commands to generate the report's facts section from the JSON, validate the display, and obtain the final answer summary: ```bash bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" render-report {slug} bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" verify-report {slug} bash "$CODE_REVIEW_SKILL_DIR/scripts/collect-metrics" summary {slug} ``` The scripts update only the `code-review:probes` / `gate` marker regions under Findings and Review Conclusion, preserving other body text and raw JSON. Rendering validates before an atomic update and can be repeated; it stops when markers are missing or duplicated. Validation covers IDs, locations, evidence, severity, status, and counts. `summary` validates the report first and outputs the machine summary only when it is consistent. The high/medium/low counts, finding IDs, and blocking conclusion in the final answer must follow the summary; do not add unrecorded issues or omit recorded high findings. Deliver the report, blocking items, and unverified content to the user; you may add explanations and suggestions, but must not rewrite the automatic regions or overturn known blocking facts. "No recorded blocking items" does not mean acceptance, coverage, tests, or CI have passed. Both `gate-pass {slug} code-review` and `archive` with review records validate the report first, then synchronize counts, and do not automatically fix a rewritten display. ## <HARD-GATE> **Any of the following conditions blocks merge**. This round's report lists the blocking items; the table below gives the follow-up handling requirements: | Blocking condition | Follow-up action | |---|---| | A required probe is `failed` / `error` (`PROBE_REQUIRED_FAILED > 0`) | Handle the issues found or the execution error, then re-run the corresponding probe | | A high finding has not completed reconciliation (`RECONCILE_PENDING > 0`) | Handle per step 4, and re-dispatch the affected probes after fixing | | Reconciliation was never reported (`RECONCILE_REPORTED=0`) | Complete step 4; run the final synchronization even when there are no high findings | | Coverage data was provided and is below baseline (below project line_target; default 85%) | List the coverage gaps, add tests, and re-review | | Acceptance criteria were provided and not all pass, or the criteria themselves are ambiguous | List the failing items; clarify ambiguity first, and do not self-check them as passing | | A high finding remains in `wontfix` | Complete handling per the output contract; "won't fix" cannot offset a pending item | See section 4 of `references/output-schema.md` for how execution status and input errors are determined. Script validation verifies consistency between report records and counts; it cannot prove the checks were sufficient. ## Output Contract - **Main report**: `{output_root}/active/{slug}/code-review.md`, generated per the sections and filling requirements in `assets/code-review.md`. - **Machine records**: `{output_root}/active/{slug}/probes/<probe-name>.json`, format per `references/output-schema.md`; the human report's facts section is derived from them. Historical records must be explicitly migrated to standalone JSON; do not infer from free text or auto-fill zeros, and do not read old embedded blocks at the same time. ## Optional Wrap-up The following items are done conditionally, not as mandatory steps each round: - **Acceptance**: when acceptance criteria are provided, check them off one by one before the final conclusion; otherwise skip and say so. - **lessons**: when a `lessons/` directory exists and something that actually happened this round is worth reusing, record it with the specific problem, cause, and handling: - Pitfalls hit during implementation → `lessons/pitfalls.md` - Cases where AI output was corrected → `lessons/bad-cases.md` - New architectural decisions → `lessons/arch-decisions.md` - **Archive**: when archiving is needed, move `{output_root}/active/{slug}/` to `{output_root}/archive/{YYYY}/`.skills/code-review/scripts/collect-metrics15 KB89d6a20922bcupstream ↗
#!/usr/bin/env bash # code-review skill 质量指标采集(随 skill 安装) # 由 code-review 的探针与主会话调用。 # # code-review 的计数由 sync-results.py 从报告派生;其他指标仍由调用方提供。 # 本脚本记录事件日志,并在 archive 时渲染 scorecard 与趋势 CSV。 set -euo pipefail [ "${BASH_VERSINFO[0]}" -gt 3 ] || { [ "${BASH_VERSINFO[0]}" -eq 3 ] && [ "${BASH_VERSINFO[1]}" -ge 2 ]; } || { echo "Bash 3.2+ required" >&2; exit 1; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=lib-metrics . "$SCRIPT_DIR/lib-metrics" usage() { cat <<'EOF' 用法: scripts/collect-metrics <子命令> <slug> [选项] 子命令: init <slug> Exclusively create a new run; return its directory and effective policy deviation <slug> --count N --reasonable M 偏离总数与其中合理的条数 security <slug> --high N --medium M --low L 安全与规范检查的三级违规计数 lessons <slug> --count N --files "a.md,b.md" 写入的 lessons 条数与文件列表 coverage <slug> [--line N] [--branch N] 两项独立选填,只记录提供的数值;单项上报仅更新该项 gate-pass <slug> <node> [--kv k=v ...] 记录一次门禁通过。node 取 prd-review / tech-design / tdd-implementation / code-review 之一 sync <slug> [--probes] 校验报告并自动记录探针计数及 reconciliation;--probes 仅记录探针,不进行对账 render-report <slug> 从独立 JSON 直接更新 Markdown 报告的自动区域 verify-report <slug> 只读校验报告自动区域与 JSON 事实完全一致 summary <slug> 先校验报告,再输出最终答复可引用的机器摘要(计数、ID、阻塞项) archive <slug> [--profile P] 渲染 quality-scorecard.txt 并更新 quality-trend.csv rollback <slug> 记录一次回滚(无自动埋点,供人工补录) 说明: 输出根: 项目 output_dir / CODE_REVIEW_OUTPUT_DIR(默认 docs/work) 事件日志: <output_root>/workflow-events.log scorecard: <output_root>/active/<slug>/quality-scorecard.txt 趋势: <output_root>/quality-trend.csv EOF } # 先完整校验,再一次性追加派生事件;校验失败不写入计数。 sync_review_results() { local slug="$1" phase="${2:-final}" directory skill_dir rows batch="" line local node event key value ts iso details="" status required duration high low validate_slug "$slug" directory="$(find_slug_dir "$slug")" || die "找不到审查目录: $slug" skill_dir="${CODE_REVIEW_SKILL_DIR:-$(dirname "$SCRIPT_DIR")}" local args=(--project-root "$WF_ROOT" --review-dir "$directory" --registry "$skill_dir/references/probes.yaml" --events) [ "$phase" != probes ] || args+=(--probes) rows="$("${CODE_REVIEW_PYTHON:-python}" -X utf8 "$SCRIPT_DIR/sync-results.py" "${args[@]}")" || return 1 event_time ts="$EVENT_TS"; iso="$EVENT_ISO" printf -v batch '%s\t%s\t%s\t-\t-\treview-sync\tphase\t%s\n' "$ts" "$iso" "$slug" "$phase" SYNC_TOTAL=0; SYNC_DONE=0 while IFS=$'\t' read -r node event key value; do [ -n "$node" ] || continue printf -v line '%s\t%s\t%s\t-\t%s\t%s\t%s\t%s\n' "$ts" "$iso" "$slug" "$node" "$event" "$key" "$value" batch+="$line" if [ "$event" = metric:reconcile ]; then case "$key" in total) SYNC_TOTAL="$value" ;; done) SYNC_DONE="$value" ;; esac elif [ "$event" = metric:probe ]; then case "$key" in status) status="$value" ;; required) required="$value" ;; duration) duration="$value" ;; high) high="$value" ;; low) low="$value" ;; medium) printf -v line '%s status=%s required=%s duration=%s high=%s medium=%s low=%s\n' \ "$node" "$status" "$required" "$duration" "$high" "$value" "$low" details+="$line" ;; esac fi done <<< "$rows" mkdir -p "$WF_WORK" if [ ! -f "$WF_EVENTS" ]; then printf '%s\n%s' "$EV_SCHEMA" "$batch" > "$WF_EVENTS" else printf '%s' "$batch" >> "$WF_EVENTS" fi printf '%s' "$details" if [ "$phase" = probes ]; then echo "已同步探针结果(尚未对账)" else echo "已同步报告: total=$SYNC_TOTAL done=$SYNC_DONE pending=$(( SYNC_TOTAL - SYNC_DONE ))" fi } review_report() { local slug="$1" action="$2" directory skill_dir validate_slug "$slug" directory="$(find_slug_dir "$slug")" || die "找不到审查目录: $slug" skill_dir="${CODE_REVIEW_SKILL_DIR:-$(dirname "$SCRIPT_DIR")}" "${CODE_REVIEW_PYTHON:-python}" -X utf8 "$SCRIPT_DIR/sync-results.py" \ --project-root "$WF_ROOT" --review-dir "$directory" --registry "$skill_dir/references/probes.yaml" "$action" } summary_report() { local slug="$1" directory skill_dir validate_slug "$slug" directory="$(find_slug_dir "$slug")" || die "找不到审查目录: $slug" skill_dir="${CODE_REVIEW_SKILL_DIR:-$(dirname "$SCRIPT_DIR")}" "${CODE_REVIEW_PYTHON:-python}" -X utf8 "$SCRIPT_DIR/sync-results.py" \ --project-root "$WF_ROOT" --review-dir "$directory" --registry "$skill_dir/references/probes.yaml" --summary } # 校验非负整数。空串在这里一律拒绝 —— 「没传」与「传了 0」必须区分开, # 否则一个漏传的参数会静默变成 0 分指标。 require_uint() { # <名称> <值> case "${2:-}" in ''|*[!0-9]*) die "$1 必须是非负整数,收到: '${2:-}'" ;; esac } # 通用选项解析:把 "标志 值" 对收进 PARAM_* 全局变量。 # 不用 getopt(1):长选项在部分平台的 getopt 上不可用。 # # 两次 shift 而不是 shift 2:--flag 恰为最后一个参数时 shift 2 会失败并 # 触发 set -e 直接杀掉脚本;拆成两次则第二次失败可以用 || true 吞掉。 # 取参一律 "${2:-}":同样是为了躲开 set -u 在未绑定参数上的中止。 parse_flags() { # <...args> PARAM_count=""; PARAM_reasonable=""; PARAM_low=""; PARAM_high="" PARAM_medium=""; PARAM_files=""; PARAM_line=""; PARAM_branch="" PARAM_line_set=false; PARAM_branch_set=false PARAM_profile=""; PARAM_kv="" while [ $# -gt 0 ]; do case "$1" in --count) PARAM_count="${2:-}"; shift; shift 2>/dev/null || true ;; --reasonable) PARAM_reasonable="${2:-}"; shift; shift 2>/dev/null || true ;; --low) PARAM_low="${2:-}"; shift; shift 2>/dev/null || true ;; --high) PARAM_high="${2:-}"; shift; shift 2>/dev/null || true ;; --medium) PARAM_medium="${2:-}"; shift; shift 2>/dev/null || true ;; --files) PARAM_files="${2:-}"; shift; shift 2>/dev/null || true ;; --line) PARAM_line_set=true; PARAM_line="${2:-}"; shift; shift 2>/dev/null || true ;; --branch) PARAM_branch_set=true; PARAM_branch="${2:-}"; shift; shift 2>/dev/null || true ;; --profile) PARAM_profile="${2:-}"; shift; shift 2>/dev/null || true ;; --kv) # 可重复。用换行分隔累积 —— --kv 的值本身不含换行(append_event 会消毒)。 if [ -n "$PARAM_kv" ]; then PARAM_kv="$PARAM_kv ${2:-}" else PARAM_kv="${2:-}" fi shift; shift 2>/dev/null || true ;; *) return 1 ;; esac done return 0 } [ $# -ge 1 ] || { usage >&2; exit 1; } CMD="$1"; shift || true case "$CMD" in init) [ "$#" -eq 1 ] || die "init requires one new slug" "${CODE_REVIEW_PYTHON:-python}" -X utf8 "$SCRIPT_DIR/sync-results.py" \ --registry "${CODE_REVIEW_SKILL_DIR:-$(dirname "$SCRIPT_DIR")}/references/probes.yaml" \ --project-root "$WF_ROOT" --init-review "$1" ;; -h|--help|help) usage; exit 0 ;; deviation) slug="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } parse_flags "$@" || die "deviation: 未知选项" [ -n "$PARAM_count" ] || die "deviation: 缺少 --count" [ -n "$PARAM_reasonable" ] || die "deviation: 缺少 --reasonable" require_uint "--count" "$PARAM_count" require_uint "--reasonable" "$PARAM_reasonable" append_event "$slug" "-" "-" "metric:deviation" count "$PARAM_count" append_event "$slug" "-" "-" "metric:deviation" reasonable "$PARAM_reasonable" echo "已记录 deviation: count=$PARAM_count reasonable=$PARAM_reasonable" ;; security) slug="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } parse_flags "$@" || die "security: 未知选项" require_uint "--low" "${PARAM_low:-0}" require_uint "--high" "${PARAM_high:-0}" require_uint "--medium" "${PARAM_medium:-0}" append_event "$slug" "-" "-" "metric:security" low "${PARAM_low:-0}" append_event "$slug" "-" "-" "metric:security" high "${PARAM_high:-0}" append_event "$slug" "-" "-" "metric:security" medium "${PARAM_medium:-0}" echo "已记录 security: high=${PARAM_high:-0} medium=${PARAM_medium:-0} low=${PARAM_low:-0}" ;; lessons) slug="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } parse_flags "$@" || die "lessons: 未知选项" [ -n "$PARAM_count" ] || die "lessons: 缺少 --count" require_uint "--count" "$PARAM_count" append_event "$slug" "-" "-" "metric:lessons" count "$PARAM_count" append_event "$slug" "-" "-" "metric:lessons" files "${PARAM_files:-}" echo "已记录 lessons: count=$PARAM_count files='${PARAM_files:-}'" ;; coverage) slug="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } parse_flags "$@" || die "coverage: 未知选项" # 显式提供的选项必须有有效值;先全部校验,避免只写入一半数据。 if [ "$PARAM_line_set" = true ]; then require_uint "--line" "$PARAM_line"; fi if [ "$PARAM_branch_set" = true ]; then require_uint "--branch" "$PARAM_branch"; fi if [ "$PARAM_line_set" = false ] && [ "$PARAM_branch_set" = false ]; then echo "覆盖率未提供(视作未测量),不记录 metric:coverage" >&2 exit 0 fi # 每项独立写入;省略的指标不写事件,也不覆盖该项已有记录。 if [ "$PARAM_line_set" = true ]; then append_event "$slug" "-" "-" "metric:coverage" line "$PARAM_line" fi if [ "$PARAM_branch_set" = true ]; then append_event "$slug" "-" "-" "metric:coverage" branch "$PARAM_branch" fi echo "已记录 coverage: line=${PARAM_line:-本次未上报} branch=${PARAM_branch:-本次未上报}" ;; gate-pass) slug="${1:-}"; shift || true node="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } [ -n "$node" ] || { echo "错误: 缺少 node" >&2; usage >&2; exit 1; } parse_flags "$@" || die "gate-pass: 未知选项" if [ "$node" = code-review ]; then review_report "$slug" --check-report sync_review_results "$slug" collect_metrics "$slug" [ "$CM_RECONCILE_PENDING" -eq 0 ] || die "code-review 仍有未处置的 high" [ "$CM_PROBE_REQUIRED_FAILED" -eq 0 ] || die "required 探针为 failed/error" [ "$CM_COVERAGE_PASS" != false ] || die "增量行覆盖率低于基线" fi # 总是额外写一行 key="-":门禁状态的判定依据是「这个 (slug,node) 发生过 # gate-pass」。像 prd-review 这种永远带 --kv 的埋点,若不写这行就检测不到。 append_event "$slug" "-" "$node" "gate-pass" "-" "-" if [ -n "$PARAM_kv" ]; then # PARAM_kv 以换行分隔累积(见 parse_flags)。用 IFS=$'\n' + for 拆分: # 不能用 `read <<EOF`——未加引号的 heredoc 分隔符会对 body 做参数展开与 # **命令替换**,于是 --kv 值里的 $(...) 会被真的执行(命令注入)。 # for 循环在当前 shell 跑,die 才能正常终止脚本。 _old_ifs="$IFS"; IFS=$'\n' for kv in $PARAM_kv; do [ -n "$kv" ] || continue k="${kv%%=*}" v="${kv#*=}" # 没有 = 时 ${kv#*=} 会原样返回整串,此时 v==kv 说明格式错。 [ "$v" != "$kv" ] || die "gate-pass: --kv 需要 k=v 形式,收到: '$kv'" [ -n "$k" ] || die "gate-pass: --kv 的键不能为空: '$kv'" append_event "$slug" "-" "$node" "gate-pass" "$k" "$v" done IFS="$_old_ifs" fi echo "已记录 gate-pass: $node" ;; render-report|verify-report) slug="${1:-}"; shift || true [ -n "$slug" ] && [ "$#" -eq 0 ] || die "$CMD: 需要且仅接受一个 slug" action=--write-report [ "$CMD" != verify-report ] || action=--check-report review_report "$slug" "$action" ;; summary) slug="${1:-}"; shift || true [ -n "$slug" ] && [ "$#" -eq 0 ] || die "summary: 需要且仅接受一个 slug" review_report "$slug" --check-report >/dev/null summary_report "$slug" ;; sync) slug="${1:-}"; shift || true [ -n "$slug" ] || die "sync: 缺少 slug" phase=final if [ "${1:-}" = --probes ]; then phase=probes; shift; fi [ "$#" -eq 0 ] || die "sync: 只接受 <slug> [--probes]" sync_review_results "$slug" "$phase" ;; probe|reconcile) die "手填探针计数和对账计数已停用,请写报告后调用 sync <slug> [--probes]" ;; archive) slug="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } parse_flags "$@" || die "archive: 未知选项" _review_dir="$(find_slug_dir "$slug" || true)" _apply_snapshot "$slug" if [ -d "$_review_dir/probes" ] || [ -f "$_review_dir/code-review.md" ] \ || [ "$CM_PROBE_TOTAL" -gt 0 ] || [ "$CM_RECONCILE_REPORTED" = 1 ]; then review_report "$slug" --check-report sync_review_results "$slug" fi # 先校验再写:否则校验失败时已经往日志里留下了一行孤立的 archive 事件。 require_events "$slug" append_event "$slug" "-" "-" "archive" profile "${PARAM_profile:--}" run_scorecard "$slug" "$PARAM_profile" echo "已生成 scorecard: $(scorecard_of "$(find_slug_dir "$slug")")" echo "已更新趋势: $WF_TREND" # 「没走完流程就归档」的唯一可检测信号。只统计本 profile 的节点, # 否则 hotfix(2 节点)会为 3 个不相干的节点发告警。 pending="$(_pending_count)" if [ "$pending" -gt 0 ]; then warn "本 profile 内有 $pending 个节点无门禁记录(PENDING)。注意本版埋点只覆盖 4 个节点,execution-plan 恒为 PENDING" fi ;; rollback) slug="${1:-}"; shift || true [ -n "$slug" ] || { echo "错误: 缺少 slug" >&2; usage >&2; exit 1; } parse_flags "$@" || die "rollback: 未知选项" append_event "$slug" "-" "-" "rollback" "-" "1" echo "已记录 rollback: $slug" ;; *) echo "错误: 未知子命令: $CMD" >&2 usage >&2 exit 1 ;; esacskills/code-review/scripts/lib-metrics37 KBb218e4c3c228upstream ↗
Contents are not inlined in the catalog. Open the upstream file.
skills/code-review/scripts/rule-grep10 KBfdd8c6eda1cdupstream ↗
#!/usr/bin/env bash # code-review skill —— 规则检测模式扫描(repo-guard 探针的第一步) # rule-grep —— 把规则包里**已经写好的「检测模式」**跑一遍,先筛出候选行。 # # 【为什么要它】 # 规则包里带「检测模式」(正则)的条目,不该让 AI **用眼睛执行正则**。 # 先执行已有模式筛选候选,再由 AI 结合规则和上下文复核。 # # 【单一事实源】正则**从规则文件里抽**,不在这里重抄一遍。 # 两处各写一份必然漂;抽出来用,规则改了这里自动跟上。 # # ⚠️ 它只**筛**,不**判**: # - 命中的行**仍然要读对应规则文件**(正例/反例/为什么)才判得准 —— 正则只说「这行像」 # - **未命中也不等于合规**:一部分「检测模式」是从散文里摘的,本来就不完整 # (实测那个误判就是这么来的)。所以未命中只作「已经看过」的记录,不是结论。 # # 用法: # scripts/rule-grep <文件...> # 通常是 git diff --name-only 出来的源码文件 # scripts/rule-grep --list # 只列模式(看能机判多少),不扫 # # 退出码: 0 = 跑完(**不表示有/无命中**);1 = 用法错误。命中与否看输出。 set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 规则包位置,优先级: # 1. $CODE_REVIEW_RULES_DIR # 2. 项目根 .code-review/rules/ # 3. skill 安装目录内的 references/rules/ DEFAULT_STD="$HERE/../references/rules" STD="${CODE_REVIEW_RULES_DIR:-}" if [ -z "$STD" ]; then # 规则属于被审项目;不能从 skill 安装位置推导项目根。 PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)" if [ -z "$PROJECT_ROOT" ]; then # 无 Git 的样例目录也可使用项目规则;支持从项目子目录调用。 PROJECT_ROOT="$PWD" search_dir="$PWD" while [ -n "$search_dir" ] && [ "$search_dir" != "/" ]; do if [ -d "${search_dir%/}/.code-review/rules" ] || [ -d "${search_dir%/}/docs/work" ]; then PROJECT_ROOT="$search_dir" break fi parent_dir="$(dirname "$search_dir")" [ "$parent_dir" = "$search_dir" ] && break search_dir="$parent_dir" done fi PROJECT_STD="$PROJECT_ROOT/.code-review/rules" if [ -d "$PROJECT_STD" ]; then STD="$PROJECT_STD"; else STD="$DEFAULT_STD"; fi fi [ -d "$STD" ] || { echo "错误: 找不到规则包: $STD" >&2; exit 1; } LIST_ONLY=0 if [ "${1:-}" = "--list" ]; then LIST_ONLY=1; shift; fi if [ "$LIST_ONLY" -eq 0 ] && [ "$#" -eq 0 ]; then echo "用法: scripts/rule-grep <文件...> | scripts/rule-grep --list" >&2 exit 1 fi # --- 抽取:`### <ID> [严重级] <标题>` 之后的 `**检测模式**` 块,取**反引号里的片段** --- # # ⚠️ 不能在 `|` 或 `/` 上切 —— 那些是**正则自己的或**(`Executors.(newFixedThreadPool|...)`), # 切了就把它切碎了。反引号才是模式的分界。 _extract() { local f for f in "$STD"/*.md; do [ -f "$f" ] || continue # index.md 是规则包说明,不是规则本身;里面的示例会污染抽取 [ "$(basename "$f")" = "index.md" ] && continue awk -v src="$(basename "$f")" ' /^### [A-Za-z]/ { id = ""; sev = ""; title = "" n = split($0, a, " ") id = a[2] if (match($0, /\[[A-Z]+\]/)) sev = substr($0, RSTART + 1, RLENGTH - 2) if (sev != "" && sev != "HIGH" && sev != "MEDIUM" && sev != "LOW") exit 2 # 标题 = [严重级] 之后的全部 if (match($0, /\] /)) title = substr($0, RSTART + 2) inpat = 0 semantic = 0 next } /^\*\*(检测方式|Detection mode)\*\*/ { semantic = 1 inpat = 0 next } /^\*\*(检测模式|Detection pattern)\*\*/ { if (semantic) next inpat = 1 # ⚠️ **这一行本身也可能带模式** —— 规则库有两种写法,而这里原先直接 `next`: # 块式: **检测模式**(任一命中即违规): # - `pattern` ← 在**下面**几行 # 内联式:**检测模式**: `pattern` ← 就在**这一行上** # 于是内联式的模式**全部被丢掉**,而且不报错、不计入「不可机判」桶、 # 连计数都没有 —— 输出看起来完全正常。 line = $0 while (match(line, /`[^`]+`/)) { printf "%s\t%s\t%s\t%s\n", src, id, sev, substr(line, RSTART + 1, RLENGTH - 2) line = substr(line, RSTART + RLENGTH) } next } inpat && /^\*\*/ { inpat = 0 } # 遇到 **正例** / **反例** 就停 inpat && /^- / { line = $0 while (match(line, /`[^`]+`/)) { printf "%s\t%s\t%s\t%s\n", src, id, sev, substr(line, RSTART + 1, RLENGTH - 2) line = substr(line, RSTART + RLENGTH) } } ' "$f" || return 1 done } # 标题另取一次(放 awk 里要跟着 id 传递,单独取更简单) _title_of() { awk -v want="$1" ' /^### [A-Za-z]/ { n = split($0, a, " "); cur = a[2] if (match($0, /\] /)) t = substr($0, RSTART + 2) if (cur == want) { print t; exit } }' "$STD"/*.md 2>/dev/null } PATTERNS="$(_extract)" || { echo "错误: 规则严重级别仅支持 HIGH / MEDIUM / LOW" >&2; exit 1; } if [ "$LIST_ONLY" -eq 1 ]; then printf '%s\n' "$PATTERNS" | while IFS=$'\t' read -r src id sev pat; do [ -n "$id" ] || continue printf '%-9s %-13s %s\n' "$id" "[$sev]" "$pat" done exit 0 fi # --- 跑:可机判的先编译试一遍,编译不过的**必须报出来**,不能静默跳过 --- # # ⚠️ 还有一类**比编译不过更坏的**:**看着像正则、其实是描述**。 # `方法签名中含用户输入参数`、`String.format("%s", ...)` # —— 它们在 ERE 里**全都合法**,于是永远"未命中",给出**虚假的安心**。 # 所以两条件单独归一类:**含中文**(那是规则描述)· **含 `...`**(省略号占位)。 _is_prose() { # ⚠️ 用 here-string,**不要用管道**:`grep -q` 一命中就退出,上游 `printf` 收到 # SIGPIPE(退出 141),而本脚本开了 `pipefail` —— 于是整条管道的状态变成非零, # `_is_prose` **永远返回假**,散文一条都拦不住。实测踩过。 LC_ALL=C grep -q '[^ -~]' <<< "$1" # 含非 ASCII → 中文描述 } # `$` 出现在模式的**中段**(非末尾)→ ERE 把它读成**行尾锚点**,于是该模式 # **永远不可能命中**。编译得过、跑得动、结果恒为「未命中」—— 读起来就是 # 「这条看过了、干净」。**虚假的安心**,和上面那类散文同型,但更隐蔽: # 散文至少看得出是中文。 # # 例如 `a$b` 想匹配字面 `$`,但未转义时会被当作行尾锚点: # `printf 'a$b\n' | grep -E 'a$b'` 不会命中。 # # **只报不改**:规范库是内部标准,改它属于另一件事 —— 这里只负责让它**出声**。 # 判据保守:只看未转义的 `$`,且排除字符类(`[$]` 里的 `$` 是字面量)。 _has_inline_anchor() { local p="$1" i=0 c inbr=0 # ⚠️ `n` **必须单独一行赋值**。写成一条 `local p="$1" … n="${#p}"` 时, # `${#p}` 求值的时刻 `p` 还没绑上 → `set -u` 下 **unbound variable**, # 而且它是在 while 循环里被调的 → **整个扫描当场死**,不是跳过一条模式。 # (同型错误本会话已踩过四次:变量必须在**引用处之前**就已经赋值。) local n="${#p}" while [ "$i" -lt "$n" ]; do c="${p:$i:1}" if [ "$c" = '\' ]; then i=$((i + 2)); continue; fi if [ "$c" = '[' ]; then inbr=1 elif [ "$c" = ']' ]; then inbr=0 elif [ "$c" = '$' ] && [ "$inbr" -eq 0 ] && [ $((i + 1)) -lt "$n" ]; then return 0 fi i=$((i + 1)) done return 1 } _hit=0; _ok=0; _bad=0; _prose=0; _miss="" while IFS=$'\t' read -r src id sev pat; do [ -n "$id" ] || continue if _is_prose "$pat" || case "$pat" in *...*) true ;; *) false ;; esac; then _prose=$((_prose + 1)) printf ' 不可机判 %-9s %-13s %s\n (是**描述**不是模式 —— 仍需人工过)\n' "$id" "[$sev]" "$pat" continue fi grep -qE -- "$pat" /dev/null 2>/dev/null rc=$? if [ "$rc" -eq 2 ]; then # 2 = 正则本身非法 _bad=$((_bad + 1)) # ⚠️ 措辞要准:这一桶里**多数不是"写坏了的正则",而是"片段"** —— # 规则把说明和几段模式写在同一行(`\`for(X : collection)\` 且同 diff 中存在 # \`.add(\` | …`),按反引号切出来的片段单独看就是括号不配对的。 # 这类**必须人工过**,而它和「跑过且没命中」是两回事 —— 说成「编译不过」 # 会让人以为规则写错了正则,跑去改一个其实没坏的东西。 printf ' 不可机判 %-9s %-13s %s\n (**单独不成模式** —— 常见于规则把片段与说明写在同一行。需人工过,不是「跑过且没命中」)\n' \ "$id" "[$sev]" "$pat" continue fi # 编译得过 ≠ 能命中。见 `_has_inline_anchor` 的注释:中段 `$` = 行尾锚点 = 恒不命中。 if _has_inline_anchor "$pat"; then _bad=$((_bad + 1)) printf ' 不可机判 %-9s %-13s %s\n (`$` 在模式**中段** —— ERE 里那是行尾锚点,该模式**永远不可能命中**。要字面量得写 \\$)\n' \ "$id" "[$sev]" "$pat" continue fi _ok=$((_ok + 1)) out="$(grep -nE -- "$pat" "$@" 2>/dev/null || true)" if [ -n "$out" ]; then _hit=$((_hit + 1)) printf '%s\n' "$out" | while IFS= read -r l; do printf ' %s\n %-9s %-13s %s\n' "$l" "$id" "[$sev]" "$(_title_of "$id")" done else _miss="$_miss$id " fi done <<< "$PATTERNS" echo echo "== 覆盖 ==" echo "可机判 $_ok 条(命中 $_hit 条)· 不可机判 $((_bad + _prose)) 条(散文 $_prose · 编译不过 $_bad)" [ -n "$_miss" ] && echo "未命中: $_miss" echo echo "⚠️ **未命中不等于合规** —— 散文类模式(上面那 $_prose 条)本来就不会命中;" echo " 命中也**不等于违规** —— 读对应规则文件(正例/反例)才判得准。"skills/code-review/scripts/sync-results.py20 KB9d8ba87db7d3upstream ↗
#!/usr/bin/env python3 """Validate review JSON records, derive metrics, and render a human Markdown report.""" import argparse from collections import Counter from copy import deepcopy import json import html import os from pathlib import Path import re import sys import tempfile import yaml SEVERITIES = ('high', 'medium', 'low') STATUSES = ('open', 'reconciled', 'wontfix', 'false-positive') NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9._-]*\Z') LABELS_PATH = Path(__file__).resolve().parents[1] / 'assets' / 'report-labels.json' LABEL_KEYS = frozenset({ 'coverage_policy', 'language', 'probe_table_header', 'probe_table_separator', 'required_yes', 'required_no', 'status_passed', 'status_failed', 'status_error', 'status_skipped', 'findings_heading', 'finding_id', 'finding_location', 'finding_status', 'finding_status_open', 'finding_status_reconciled', 'finding_status_wontfix', 'finding_status_false_positive', 'finding_status_suffix', 'finding_evidence', 'finding_reconciled_by', 'no_findings', 'reconcile_line', 'gate_prefix', 'gate_blocked', 'gate_clear', 'gate_detail', 'gate_footer', 'summary_heading', 'summary_findings', 'summary_high_ids', 'summary_medium_ids', 'summary_low_ids', 'summary_reconcile', 'summary_required_failed', 'summary_status', 'summary_blocked', 'summary_clear', 'summary_none', 'unverified_status', 'unverified_probe', 'unverified_reason', 'unverified_needed_evidence', 'summary_unverified', }) def load_labels(): data = json.loads(LABELS_PATH.read_text(encoding='utf-8-sig'), object_pairs_hook=unique_object) require(isinstance(data, dict), 'report labels must be an object') require(data.get('language') in ('en', 'zh-CN'), 'unsupported report language') missing = LABEL_KEYS - data.keys() require(not missing, 'report labels missing keys: ' + ', '.join(sorted(missing))) for key in LABEL_KEYS: require(isinstance(data[key], str) and data[key].strip(), 'empty report label: ' + key) return data def require(condition, message): if not condition: raise ValueError(message) def unique_object(pairs): result = {} for key, value in pairs: require(key not in result, f'重复字段: {key}') result[key] = value return result def runtime_config(project, configuration): """Resolve project policy once for shell commands and probe handoff.""" project = Path(project).resolve() path = project / '.code-review/config.json' data = load_record(path) if path.exists() else {} fields(data, (), ('output_dir', 'line_target', 'branch_target'), 'project policy') registry_data = yaml.load(Path(configuration).read_text(encoding='utf-8-sig'), Loader=UniqueLoader) params = next((p.get('params', {}) for p in registry_data['probes'] if p['name'] == 'test-coverage'), {}) result = {} for key, default in (('line_target', 85), ('branch_target', 80)): value = data.get(key, params.get(key, default)) require(type(value) is int and 0 <= value <= 100, key + ' must be an integer from 0 to 100') result[key] = value output = os.environ.get('CODE_REVIEW_OUTPUT_DIR', data.get('output_dir', 'docs/work')) text(output, 'output_dir') require(not any(c in output for c in '\t\r\n'), 'invalid output_dir') result['output_dir'] = (project / output).resolve().as_posix() require(Path(result['output_dir']) != project, 'output_dir must not be the project root') return result def init_review(project, configuration, slug): require(NAME.fullmatch(slug) is not None and slug not in ('.', '..'), 'invalid slug') policy = runtime_config(project, configuration) root = Path(policy['output_dir']) require(not list((root / 'archive').glob('*/' + slug)), 'slug already archived: ' + slug) events = root / 'workflow-events.log' if events.exists(): require(not any(len(row.split('\t')) > 2 and row.split('\t')[2] == slug for row in events.read_text(encoding='utf-8').splitlines()), 'slug already has events: ' + slug) directory = root / 'active' / slug directory.parent.mkdir(parents=True, exist_ok=True) directory.mkdir() # Exclusive creation: a new run must never reuse existing output. (directory / 'probes').mkdir() template = LABELS_PATH.parent / 'code-review.md' content = template.read_text(encoding='utf-8').replace('{line_target}', str(policy['line_target'])) content = content.replace('{branch_target}', str(policy['branch_target'])) (directory / 'code-review.md').write_text(content, encoding='utf-8') (directory / 'policy.json').write_text(json.dumps(policy, indent=2), encoding='utf-8') print(json.dumps(dict(review_dir=directory.as_posix(), **policy))) class UniqueLoader(yaml.SafeLoader): pass def yaml_mapping(loader, node): return unique_object(loader.construct_pairs(node, deep=True)) UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, yaml_mapping) def text(value, label): require(isinstance(value, str) and bool(value.strip()), f'{label} 必须为非空字符串') return value def fields(value, required, optional, label): require(isinstance(value, dict), f'{label} 必须为对象') require(set(required) <= value.keys(), f'{label} 缺少字段: {set(required) - value.keys()}') require(value.keys() <= set(required) | set(optional), f'{label} 含未知字段: {value.keys() - set(required) - set(optional)}') def load_record(path): return json.loads(path.read_text(encoding='utf-8-sig'), object_pairs_hook=unique_object, parse_constant=lambda value: (_ for _ in ()).throw(ValueError(f'非法 JSON 数值: {value}'))) def registry(path): data = yaml.load(path.read_text(encoding='utf-8-sig'), Loader=UniqueLoader) require(isinstance(data, dict) and isinstance(data.get('probes'), list) and data['probes'], '注册表缺少 probes 清单') probes = {} for item in data['probes']: require(isinstance(item, dict), '探针配置必须为对象') name = text(item.get('name'), '探针 name') require(NAME.fullmatch(name) is not None and name not in probes, f'非法或重复探针名: {name}') require(type(item.get('required')) is bool, f'{name}: required 必须为布尔值') probes[name] = item['required'] return probes def finding(item, probe): required = ('id', 'probe', 'severity', 'status', 'location', 'summary', 'evidence') optional = ('repro', 'reconciled_by', 'reconcile_commit') fields(item, required, optional, 'finding') for key in item: text(item[key], f'finding.{key}') require(item['probe'] == probe, f"{item['id']}: probe 不匹配") pattern = re.escape(probe) + r'-[1-9][0-9]*' require(re.fullmatch(pattern, item['id']) is not None, f"非法 finding ID: {item['id']}") require(item['severity'] in SEVERITIES, f"{item['id']}: 非法 severity") require(item['status'] in STATUSES, f"{item['id']}: 非法 status") require(item['status'] != 'wontfix' or item['severity'] in ('medium', 'low'), f"{item['id']}: 严重问题不得 wontfix") if item['status'] == 'reconciled': text(item.get('reconciled_by'), f"{item['id']}.reconciled_by") return deepcopy(item) def read_probes(directory, configuration): probe_dir = directory / 'probes' expected = {name + '.json' for name in configuration} actual = {p.name for p in probe_dir.glob('*.json')} require(actual == expected, f'探针报告清单不匹配: 缺少 {expected - actual}; 未注册 {actual - expected}') summaries, findings, unverified_items = [], {}, [] for name, required in configuration.items(): block = load_record(probe_dir / (name + '.json')) fields(block, ('probe', 'status', 'findings'), ('duration', 'reason', 'unverified_items'), name) require(block['probe'] == name, f'{name}: 报告 probe 不匹配') require(block['status'] in ('passed', 'failed', 'skipped', 'error'), f'{name}: 非法执行状态') duration = block.get('duration', 0) require(type(duration) is int and duration >= 0, f'{name}: duration 必须为非负整数秒') require(isinstance(block['findings'], list), f'{name}: findings 必须为数组') if block['status'] in ('skipped', 'error'): text(block.get('reason'), f'{name}.reason') elif 'reason' in block: text(block['reason'], f'{name}.reason') pending_items = block.get('unverified_items', []) require(isinstance(pending_items, list), f'{name}: unverified_items 必须为数组') for item in pending_items: keys = ('location', 'summary', 'reason', 'needed_evidence') fields(item, keys, (), f'{name}.unverified_items') for key in keys: text(item[key], f'{name}.unverified_items.{key}') unverified_items.append(dict(probe=name, **deepcopy(item))) counts = Counter() open_high = 0 for raw in block['findings']: item = finding(raw, name) require(item['id'] not in findings, f"重复 finding ID: {item['id']}") findings[item['id']] = item counts[item['severity']] += 1 open_high += item['severity'] == 'high' and item['status'] == 'open' require(block['status'] != 'skipped' or not block['findings'], f'{name}: skipped 不能带 finding') require(block['status'] != 'passed' or not open_high, f'{name}: passed 与 open high 矛盾') require(block['status'] != 'failed' or open_high, f'{name}: failed 缺少 open high') summaries.append(dict(name=name, required=required, status=block['status'], duration=duration, reason=block.get('reason', ''), **{severity: counts[severity] for severity in SEVERITIES})) return summaries, findings, unverified_items def summarize(directory, configuration, probes_only=False): probes, findings, unverified_items = read_probes(directory, registry(configuration)) reconciliation = None if not probes_only: severe = [item for item in findings.values() if item['severity'] == 'high'] done = sum(item['status'] in ('reconciled', 'false-positive') for item in severe) reconciliation = dict(total=len(severe), done=done, pending=len(severe) - done) return dict(probes=probes, reconcile=reconciliation, findings=list(findings.values()), unverified_items=unverified_items) def markdown(value): """Render record text literally, including pipes, newlines, and code fragments.""" value = html.escape(str(value), quote=False) value = re.sub(r'([\\`*_\[\]|#!])', r'\\\1', value) return value.replace('\r\n', '\n').replace('\r', '\n').replace('\n', '<br>') def render_sections(summary, labels, policy=None): rows = [labels['probe_table_header'], labels['probe_table_separator']] for p in summary['probes']: status = labels['status_' + p['status']] rows.append('| ' + ' | '.join(markdown(value) for value in (p['name'], labels['required_yes'] if p['required'] else labels['required_no'], status, p['duration'], p['high'], p['medium'], p['low'], p['reason'])) + ' |') rows += ['', labels['findings_heading'], ''] for item in summary['findings']: status = labels['finding_status_' + item['status'].replace('-', '_')] title, _, description = item['summary'].replace('\r\n', '\n').replace('\r', '\n').partition('\n') rows += [f"#### [{item['severity']}] {markdown(title)}", ''] if description.strip(): rows += [markdown(description), ''] rows += [ labels['finding_id'] + markdown(item['id']), labels['finding_location'] + markdown(item['location']), labels['finding_status'] + status + labels['finding_status_suffix'].format(status=item['status'])] if item.get('reconciled_by'): rows.append(labels['finding_reconciled_by'] + markdown(item['reconciled_by'])) rows += ['', '<details>', '<summary>' + html.escape(labels['finding_evidence']) + '</summary>', '', markdown(item['evidence']), '', '</details>', ''] for item in summary['unverified_items']: title, _, description = item['summary'].replace('\r\n', '\n').replace('\r', '\n').partition('\n') rows += [f"#### [{labels['unverified_status']}] {markdown(title)}", ''] if description.strip(): rows += [markdown(description), ''] rows += [ labels['unverified_probe'] + markdown(item['probe']), labels['finding_location'] + markdown(item['location']), labels['unverified_reason'] + markdown(item['reason']), labels['unverified_needed_evidence'] + markdown(item['needed_evidence']), '', ] if not summary['findings'] and not summary['unverified_items']: rows.append(labels['no_findings']) r = summary['reconcile'] rows += ['', labels['reconcile_line'].format(**r)] failed = [p['name'] for p in summary['probes'] if p['required'] and p['status'] in ('failed', 'error')] gate = labels['gate_prefix'] + (labels['gate_blocked'] if r['pending'] or failed else labels['gate_clear']) gate += labels['gate_detail'].format(pending=r['pending'], failed=len(failed)) gate += '\n\n' + labels['gate_footer'] if policy is not None: gate += '\n\n' + labels['coverage_policy'].format(**policy) return dict(probes='\n'.join(rows).strip(), gate=gate) def summary_text(summary, labels): """Canonical delivery summary derived only from validated JSON records.""" findings = summary['findings'] by_severity = { severity: [item['id'] for item in findings if item['severity'] == severity] for severity in SEVERITIES } required_failed = [ probe['name'] for probe in summary['probes'] if probe['required'] and probe['status'] in ('failed', 'error') ] reconcile = summary['reconcile'] blocked = bool(reconcile['pending'] or required_failed) lines = [ labels['summary_heading'], labels['summary_findings'].format( **{severity: len(by_severity[severity]) for severity in SEVERITIES}), labels['summary_high_ids'] + (', '.join(by_severity['high']) or '-'), labels['summary_medium_ids'] + (', '.join(by_severity['medium']) or '-'), labels['summary_low_ids'] + (', '.join(by_severity['low']) or '-'), labels['summary_unverified'].format(count=len(summary['unverified_items'])), labels['summary_reconcile'].format(**reconcile), labels['summary_required_failed'] + (', '.join(required_failed) or labels['summary_none']), labels['summary_status'] + (labels['summary_blocked'] if blocked else labels['summary_clear']), ] return '\n'.join(lines) def update_report(directory, sections, check=False): path = directory / 'code-review.md' with path.open(encoding='utf-8-sig', newline='') as stream: raw = stream.read() newline = '\r\n' if '\r\n' in raw else '\n' source = raw.replace('\r\n', '\n') require(not re.search(r'^```json probe-result\b', source, re.M), '原始记录须保存为独立 JSON,最终报告不得嵌入机器记录区块') require('<!-- code-review:reviewer:start -->' not in source and '<!-- code-review:reviewer:end -->' not in source and re.search(r'^```json reviewer-result', source, re.M) is None, '报告仍包含旧版 Reviewer 区域;reviewer 功能已移除,请迁移或删除该区域后重新渲染') updated = source for name, content in sections.items(): begin, end = f'<!-- code-review:{name}:start -->', f'<!-- code-review:{name}:end -->' require(source.count(begin) == source.count(end) == 1, f'{name}: 自动区域标记缺失或重复') pattern = re.compile(re.escape(begin) + r'\n.*?\n' + re.escape(end), re.S) require(pattern.search(updated) is not None, f'{name}: 自动区域标记顺序错误') updated = pattern.sub(lambda _: begin + '\n' + content + '\n' + end, updated, count=1) if check: require(source == updated, '报告自动区域与原始记录不一致,请重新渲染') return # Validate all input and all markers before touching the report; replace atomically. descriptor, temporary = tempfile.mkstemp(prefix='.code-review-', suffix='.tmp', dir=directory) try: with os.fdopen(descriptor, 'w', encoding='utf-8', newline='') as stream: stream.write(updated.replace('\n', newline)) os.replace(temporary, path) finally: if os.path.exists(temporary): os.unlink(temporary) def events(summary): rows = [] for probe in summary['probes']: for key in ('status', 'required', 'duration', 'high', 'low', 'medium'): value = str(probe[key]).lower() if type(probe[key]) is bool else str(probe[key]) rows.append('\t'.join((probe['name'], 'metric:probe', key, value))) if summary['reconcile'] is not None: for key in ('total', 'done'): rows.append(f"-\tmetric:reconcile\t{key}\t{summary['reconcile'][key]}") return '\n'.join(rows) def main(): # Native Windows Python otherwise emits CRLF, which would enter Bash metric values. sys.stdout.reconfigure(newline='\n') parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--review-dir', type=Path) parser.add_argument('--registry', type=Path, required=True) parser.add_argument('--project-root', type=Path) parser.add_argument('--runtime-config', action='store_true') parser.add_argument('--init-review') parser.add_argument('--probes', action='store_true', help='Validate probe reports without claiming reconciliation') output = parser.add_mutually_exclusive_group() output.add_argument('--events', action='store_true') output.add_argument('--has-high', action='store_true') output.add_argument('--render', action='store_true', help='Preview generated report sections') output.add_argument('--write-report', action='store_true', help='Replace generated report regions only') output.add_argument('--check-report', action='store_true', help='Verify every generated field without writing') output.add_argument('--summary', action='store_true', help='Print the canonical delivery summary from validated JSON') args = parser.parse_args() try: if args.runtime_config or args.init_review: require(args.project_root is not None, 'project root required') if args.init_review: init_review(args.project_root, args.registry, args.init_review) else: policy = runtime_config(args.project_root, args.registry) print('{output_dir}\t{line_target}\t{branch_target}'.format(**policy)) return 0 require(args.review_dir is not None, 'review directory required') labels = load_labels() summary = summarize(args.review_dir, args.registry, args.probes) if args.render or args.write_report or args.check_report: require(not args.probes, '渲染和展示校验需要最终同步结果') policy = runtime_config(args.project_root, args.registry) if args.project_root else None sections = render_sections(summary, labels, policy) if args.write_report or args.check_report: update_report(args.review_dir, sections, check=args.check_report) if args.summary: require(not args.probes, '摘要需要最终同步结果') except (ValueError, TypeError, OSError, yaml.YAMLError) as error: print(f'错误: {error}', file=sys.stderr) return 1 if args.write_report or args.check_report: print('报告展示校验通过' if args.check_report else '已更新报告自动区域') elif args.render: print('\n\n'.join(sections.values())) elif args.events: print(events(summary)) elif args.has_high: print(str(any(p['high'] for p in summary['probes'])).lower()) elif args.summary: print(summary_text(summary, labels)) else: print(json.dumps(summary, ensure_ascii=False)) return 0 if __name__ == '__main__': sys.exit(main())skills/code-review/references/concurrency-examples.md3.5 KBa8d1703308d0upstream ↗
# Concurrency interleaving examples These are constructed execution orders, not runtime records. Select relevant examples and verify the interleaving against concrete entry points, reads, writes, and contracts; similar structure alone does not establish a defect. The examples do not cover every matrix type or require every trace to be output. ## 1. Check-then-act: separately locked operations can oversell Contract: stock must not become negative. The check and decrement each hold the same lock, but the decrement does not recheck. ```text Initially: stock = 1. A: acquire the lock, read stock > 0, release the lock. B: acquire the lock, check stock > 0, decrement to 0, release the lock. A: reacquire the lock, act on the earlier result, decrement to -1, release the lock. ``` Failure: A's check becomes stale before the action; locking individual reads and writes does not protect the compound operation. Verify: check and decrement share one critical section, or an effective recheck, conditional update, or equivalent atomic protocol protects the action. ## 2. Cancellation and task termination: an old task writes after its guard is released Contract: only one task may write to a resource at a time. Here, cancellation notification does not wait for termination, and old and new tasks share their result state. ```text Initially: A is running, busy = true. User: request cancellation of A. Cancellation callback: busy = false; A has not exited. User: start again; B passes the busy check and begins execution. A: continue writing the shared result; B also starts writing it. ``` Failure: the guard admits a new task while the old task can still affect the same resource. Verify: the old task and relevant subtasks have terminated before releasing the guard, or actual task isolation prevents the effect. Preventing the old task from publishing results only rules out the corresponding result-overwrite risk; whether busy/loading may be cleared still depends on that state's contract. Separately verify state accuracy and the conditions for admitting a new task. ## 3. Stale callbacks: an old request overwrites a new result Contract: the page displays data for the current selection. Requests can complete out of order, and callbacks do not check request identity. ```text User: select X and start request A. User: switch to Y and start request B. B: return first; the page displays the result for Y. A: return later; its callback unconditionally updates the page with the result for X. ``` Failure: a callback from an old request changes the current page state. Verify: request IDs or versions are checked before publishing results, or another actual guarantee prevents stale callbacks from taking effect; requesting cancellation alone does not establish that callbacks cannot run. ## 4. Correct control: rechecking under the lock prevents a stale decision Contract: reuse cached results when available. Cache publication and the decision to start loading use the same lock. ```text Initially: cache is empty. A: read an empty cache under the lock, then release it. B: publish cache = S under the lock, then release it. A: enter the loading-decision critical section, recheck cache, find S, and decide to reuse it without starting a load. ``` Protection: A does not act on the stale empty-cache check; the decision uses the current cache under the lock. The exclusion rests on the actual recheck and shared-lock protocol, not merely the presence of a lock. If another flag is checked instead, verify why it guarantees that the original action's precondition still holds.skills/code-review/references/output-schema.md9.5 KBd7898b43b38cupstream ↗
# Probe output contract (findings) This file defines the finding fields, severity levels, handling statuses, and probe execution statuses, for use by probes when they write standalone JSON. A finding is one specific problem; `severity` indicates how serious it is, a finding's `status` indicates handling progress, and the probe status indicates the execution result of one round of checks. ## 1. How to write a single issue | Field | Requirement | Value | Description | |---|---|---|---| | `id` | Required | `<probe>-<number>` | Globally unique, number starts at 1, **must be stable** | | `probe` | Required | Probe name | Probe name taken from `references/probes.yaml` | | `severity` | Required | `high` / `medium` / `low` | See the criteria table below | | `status` | Required | `open` / `reconciled` / `wontfix` / `false-positive` | See the state machine below | | `location` | Required | `path:line` | When no line can be located, use the file level and explain why | | `summary` | Required | Title and brief explanation | First line: short title. Subsequent lines: trigger, actual versus expected behavior, supported impact, and fix suggestion; do not repeat detailed evidence | | `evidence` | Required | Concrete basis | Cite a rule ID / interface contract / code path / test output. **Anything without evidence is not a finding** | | `repro` | Optional | Verification command | E.g. `grep -n 'formatCents' src/…/Calculator.java`, `git show <head>:<path>`. Read-only evidence commands only; must not build or run tests | | `reconciled_by` | Conditionally required | Handling note | Fill in when `status=reconciled`: where it was changed and why | | `reconcile_commit` | Optional | commit id | Fill in when a corresponding handling commit exists | Optional fields not provided and conditional fields that do not apply may be omitted; empty fields need not be filled. `summary` remains a string; use JSON `\n` for line breaks. Legacy single-line records still render. Evidence is preserved in full at the end of each finding, collapsed by default. Keep static-reasoning status, unverified assumptions, and contract disputes visible in the explanation, not only in hidden evidence. Each probe saves this round's records as a standalone `probes/<probe-name>.json`. For example: ```json { "probe": "test-coverage", "status": "failed", "duration": 12, "findings": [{ "id": "test-coverage-1", "probe": "test-coverage", "severity": "high", "status": "open", "location": "src/main/java/com/example/Calculator.java:137", "summary": "Incremental line coverage is 60%, below the 85% baseline", "evidence": "Report matches the target version; 3 of 5 executable changed lines in production code are covered, 3/5 = 60%" }] } ``` `probe`, `status`, and `findings` are required; `duration` is a non-negative integer in seconds and may be omitted, with omission recorded as 0. `skipped` / `error` must also include a non-empty `reason`; when there are no issues, explicitly write `findings: []` rather than inventing empty records. Checked scope, general execution limitations, and additional suggestions go in the human report; each finding's explanation and fix suggestion go in `summary`. The machine JSON is the single source of truth; do not copy full findings or hand-fill counts elsewhere. **Pending confirmation**: when a specific concern has been identified but a contract, reachability, or evidence gap prevents confirming a defect, save it in the probe's optional top-level `unverified_items` array rather than only in the return message. Each item contains only these four non-empty strings: | Field | Content | |---|---| | `location` | Source location, preferably `path:line` | | `summary` | Title, observed behavior, and possible consequences; distinguish facts from inference | | `reason` | The specific reason confirmation is pending; for contract conflicts, cite both sides | | `needed_evidence` | Evidence or a product decision needed to resolve the concern | Omit the field or use `[]` when there are no such concerns; legacy JSON remains valid. Do not turn general limitations such as tests not run into individual pending items, or duplicate a concern already explained in a finding. Proven defects still belong in `findings`; this field must not bypass grading. The script displays these items beside high / medium / low in the report's Findings section, labeled "Pending confirmation", with their source probe. They have no severity or handling status, do not enter defect counts or reconciliation, and do not directly change probe status or gates; malformed records still fail validation. ## 2. Issue severity | Level | Criterion | Enters reconciliation | |---|---|---| | `high` | Serious security rule violation / money or data correctness issue / HIGH-severity standards rule violation / contract break / measured diff line coverage below the effective project target (default 85%) / acceptance item failing | **Yes** | | `medium` | Confirmed behavioral defects that deviate from expected behavior, have localized impact, and do not meet the high criteria; maintainability issues, missing tests | No (record only) | | `low` | Style, naming | No | Only the three levels above are accepted; other levels are an error and are not auto-converted or used to rewrite historical records. ### Behavioral issues **1. Verify the basis** To report `high` on the grounds of violating a method contract, `evidence` must include the full signature of the actual method, the file and line number of the corresponding interface declaration (or the method declaration if there is no interface), and a short excerpt of the contract text. Same-name overloads or similar interfaces cannot substitute. When there is confirmed to be no textual contract, you may cite specific code invariants or tests and their locations; not having read something cannot be treated as it not existing. When the basis has not been verified or listed, the maximum is `medium`, noting "contract not verified". **2. Grade by proven errors** - `high`: at public contract boundaries such as API / SPI / cache / state machine, an incorrect return, exception, or missed invalidation occurs under reachable inputs. Source-code reasoning may serve as evidence; end-to-end execution is not required. - Maximum `medium`: the boundary error is not yet proven and downstream impact is only speculated; or the performance issue is unmeasured. Do not omit a proven behavioral defect solely because performance is unmeasured or downstream effects are unverified. When a boundary error is proven, unverified downstream UI / templates / integration layers are recorded only in `evidence` and do not lower the severity. **3. Handling contract conflicts** When contracts, docs, or tests conflict, list both sides' bases in `evidence`. Only evidence that applies to the target version and explains the conflict can resolve it; "probably a typo / copy-paste / old comment" does not count as evidence. If the judgment depends on an unresolved conflict, save it in `unverified_items`; if kept as a finding instead, do not duplicate it, the maximum is `medium`, and the summary notes "contract to be confirmed". Other issues with independent, sufficient evidence are graded as usual. `unverified` is not a finding severity or status. ### Combined risk and test gaps - **Combined risk**: when multiple distinct issues interact and evidence proves they jointly cause a more serious consequence, grade by the combined impact and explain the interaction path. - **Contract changes and test gaps**: when a corresponding test is confirmed missing, record the test gap; do not automatically escalate it to `high`. When evidence already proves that a contract change causes a breaking impact, grade per the severity criteria above. When test context is not provided or is incomplete, note "relevant tests unverified", and do not assert that no tests exist on that basis. ## 3. Issue handling statuses ``` open ──┬─→ reconciled Handled (code changed / tests added) ├─→ wontfix Explicitly not fixing (**must give a reason**, and severity must be medium/low) └─→ false-positive Probe false positive (**must give evidence** explaining why it is not a problem) ``` ### Re-running after handling After a high finding is handled, re-run the corresponding probe and report the result; do not directly hand-edit it to `passed`. The re-run must state what changed in the code or the basis for judgment; execution status follows the latest result, and historical findings and handling records are retained. ## 4. Probe execution results `requires` lists the inputs a probe needs, checked against this round's provided materials and default configuration. Probe execution status is reported separately from the handling status of individual findings: | Status | Meaning | |---|---| | `passed` | The relevant checks completed with no execution errors and no open high findings | | `failed` | The relevant checks completed with no execution errors but open high findings | | `skipped` | **Normally skipped per the probe's convention**, e.g. this round did not provide the optional input required by `requires`; the reason must be stated. **Not counted as a failure** | | `error` | A specified input path does not exist, cannot be read or parsed, or a command / tool failed. Only when `required=true` does it count toward `PROBE_REQUIRED_FAILED`, same as `failed` | When a specified coverage report path cannot be read, record it as `error`. Missing or corrupted resources pointed to by the default configuration likewise count as `error` and must not be disguised as a normal skip.skills/code-review/references/probes.yaml1.3 KBd4f4af3786d3upstream ↗
# code-review probe registry: maintains the probe list, dependencies, and parameters. # See section 4 of references/output-schema.md for the meaning of required / requires and execution status. version: 1 execution: mode: parallel max_retry: 1 # Maximum re-dispatches when reconciliation finds a gap # requires lists input dependencies; rules uses built-in rules by default, and coverage is optionally handled internally by test-coverage. probes: - name: behavior-review title: Behavioral correctness file: references/probes/behavior-review.md required: true requires: [] - name: test-coverage title: Test quality and incremental coverage file: references/probes/test-coverage.md required: true requires: [] params: line_target: 85 branch_target: 80 - name: repo-guard title: Repository standards file: references/probes/repo-guard.md required: true requires: [rules] params: read_only: true # Rule pack selection priority: see probes/repo-guard.md. rules_dir: references/rules - name: impact-analysis title: Impact analysis file: references/probes/impact-analysis.md required: false requires: [] params: depth: 1 # Caller trace depth (number of fan-in levels)skills/code-review/references/probes/behavior-review.md9.8 KBfce0a851aa7fupstream ↗
# probe: behavior-review (behavioral correctness) **required: true** | **requires: []** Check behavioral defects introduced or triggered by this change. The review scope and source version follow "Determine the review scope" in `SKILL.md`; it does not depend on rule packs and is not limited by the language extensions scanned for rules. Trace actual call chains as needed, unconstrained by the single-level fan-in of `impact-analysis`; not finding a caller does not by itself prove the internal logic correct or incorrect. ## 1. Evidence-gathering method Identify changed behavior entry points or branches from the diff, group them into chains by related state or objects, and gather evidence and judge each chain. Reuse evidence across related branches, but assess different triggers and consequences separately; after finding an issue, continue checking the remaining changed paths. Cite concrete source locations without presuming defects or inventing business rules. 1. **Confirm the change**: read relevant entry points and implementations to verify the actual method signatures, existing contracts, and behavior before and after the change. Expected behavior may be based on interface contracts, design, tests, or explicit code invariants. For changes to validation ranges, defaults, or branches, separately verify whether newly accepted or rejected inputs match the change's intent. Do not treat existing tests as outdated merely because the implementation changed; record specific conflicts when the supporting evidence remains unresolved. 2. **Trace state chains**: first establish the contracts and read uses of key states involved in this change. Then trace each write entry point through "entry point → state reads and writes → guard → subsequent action", comparing trigger timing and separately judging whether state writes, subsequent admission, and side effects respect those contracts. Assess completion, exception, and cancellation entry points sharing a wrapper separately; their judgments are not interchangeable. For concurrency or async behavior, verify actual executing threads, scheduling boundaries, synchronous versus queued callbacks, and task termination ordering. If a state contract cannot be established, save the specific dispute in `unverified_items` under the output contract. 3. **Fill specific gaps**: read more only for a concrete gap that could change the judgment, first identifying the question and likely evidence source. For decisive library or framework calls, verify the target version's declaration, contract, or implementation. For model or cache reads, follow object acquisition and wrapping to confirm the actual implementation and relevant overrides; do not infer behavior from method names, same-name overloads, or default implementations. Search Git history only when a concrete historical lead could change the judgment. When a contract conflict cannot be resolved and there is no new lead, stop gathering more evidence, record it as unverified under the output contract, and state both sides' evidence and the behavioral consequences under each interpretation; do not rule out the issue by accepting only one side. 4. **Complete the judgment**: record each path as "confirmed issue", "ruled out with evidence", or "unverified with a specific gap". Exclusion conclusions cover only the consequences verified by their evidence. Assess other contracts, state meanings, and admission conditions on the same path separately; ruling out one risk does not rule out the entire path. For unverified paths, identify which step in the trigger, ordering, or consequence the gap affects. Distinguish "the supplied implementation supports the path" from "the target build version is confirmed"; retain unverified status when a version gap determines reachability, without dismissing existing evidence wholesale. Move to the next chain when complete, revisiting only when new evidence or a concrete counterexample could change the judgment. ## 2. Behavioral checks For the main paths affected by this change, check with the entry points, callers, and context in mind: - Do conditional branches behave as expected, and are there inverted or missing branches? When routing changes, compare the actual paths for the same input before and after the change, and include pre-existing defects triggered by the new path in this round's checks. - Are state transitions allowed, and do side effects run under the correct conditions? - Are nulls, empty collections, and numeric boundaries handled correctly? - After an exception or partial failure, are state and resources handled correctly? For sequences involving databases, caches, messages, or external calls, check whether partial success, timeouts, or retries lead to inconsistent state or repeated side effects that violate the contract. Choose applicable check directions and concrete counterexamples for each behavior chain, and reason through them: - **Parsing / validation**: first confirm the input language and dependency versions, and proactively read the corresponding syntax or format contract; the current sample alone cannot stand for valid input. Choose valid equivalent forms per the contract, and check consistency along "parse → validate → use" and whether validation can be bypassed. - **Serialization / template rendering**: follow the call chain to read the actual wrapping, rendering, and parsing implementations, and compare with the related old entry points. Trace variable values along the actual conversion order, choose inputs such as quotes, backslashes, or newlines, and check whether escaping or decoding changes the original value or breaks the structure. **Concurrency and async check matrix**: when shared state, retries, or async tasks are involved, select applicable rows from the code signals in this change; a behavior chain may match multiple rows. Read the applicable [interleaving examples](../concurrency-examples.md), map the actors, state, and interleaving points to actual source, and verify reachability and protection mechanisms; do not copy the example conclusions. | Type and code signal | Core risk | Basis for exclusion | | --- | --- | --- | | Check-then-act: inspect a cache, state, or existence before acting | State changes before the action, including between separate critical sections | An atomic protocol preserves the action's precondition; rechecking another state must guarantee that precondition; individually locked accesses are insufficient | | Read-modify-write: read, compute, write back | Concurrent updates overwrite each other | A lock, atomic operation, or suitably isolated transaction protects the whole operation | | Visibility and publication: cross-thread initialization or readiness flags | Readiness is visible but data is incomplete or stale | Effective publication and visibility guarantees, or thread confinement | | Cancellation and task termination: callbacks release busy/loading | A new task enters while the old task or subtasks still run | Verify state-query contracts, cleanup ordering, and new-task admission separately; result isolation rules out result overwrites only and does not by itself justify releasing busy/loading; cancellation notification is not task termination | | Stale callbacks: old and new tasks share state | A late callback overwrites or clears new task state | Task identity/version checks, or effective ordering and isolation | | Repeated execution: retries, duplicate messages or triggers | An operation required once runs repeatedly | Atomic claims, deduplication, or idempotency cover state and observable side effects; equal results alone are insufficient; contractually permitted repetition is not a defect | | Deadlock: multiple locks or calls while holding a lock | Reverse lock ordering or circular waits | Lock ordering, avoiding waits while holding locks, or another guarantee prevents the cycle | For operation races, give a concrete interleaving and state changes; for visibility/publication, explain the synchronization and publication guarantees. Apply section 1's evidence and judgment method. Do not enumerate every thread combination, force findings, or output the matrix; reuse the existing "actual check scope". For other behavioral changes, choose counterexamples per the main paths. ## 3. Recording and reporting Check records uniformly state: **handling point location → trigger conditions and specific input → expected behavior and basis → relevant code path → actual behavior and consequence**. Once the main paths have these judgments, reuse the records to assemble and write the probe JSON. Do not routinely expand dependencies or recheck judged paths while finishing. If new evidence, a concrete counterexample, or contradictory evidence could change a conclusion, return to the relevant chain for evidence gathering. Save specific concerns that cannot yet be confirmed as defects in `unverified_items` under the output contract. In the return message, use the existing "actual check scope" to briefly state checked paths and other scope limitations; the return message does not replace JSON records. Confirmed issues go into the finding's `evidence`; grading and contract-conflict handling follow the [output contract](../output-schema.md). Record different root causes or consequences separately; do not merge them to evade grading. Save issues that depend on unresolved contract conflicts in `unverified_items` when no finding is created. When the syntax basis, the actual implementation path, or the reachability of a valid triggering input has not been verified, state the specific gap and do not treat it as a proven defect. Checks not completed or with insufficient evidence are listed as unverified items and limitations, not lumped into "checked"; when not run, mark "static reasoning" and do not call it reproduced. Behavioral findings do not require an existing rule ID and are not subject to `repo-guard`'s HIGH-only reporting limit.skills/code-review/references/probes/impact-analysis.md4.4 KB01a3a7832fedupstream ↗
# probe: impact-analysis (impact analysis) **required: false** | **requires: []** Check this change's impact on callers, contracts, performance, and architecture. ## 1. Inbound callers of changed methods (fan-in) For methods in this diff whose **signature or caller-visible behavior changed**, trace callers one by one: Use the review scope and target version determined by the main flow. Method declaration changes are only used to help locate; also check changes in return-value meaning, exceptions, and side effects caused by method body changes. Call forms differ by language and must be confirmed against the actual code. For methods involving the above changes, answer: | Question | Why ask | |---|---| | Who calls it? (how many) | The larger the fan-in, the larger the blast radius of the change | | Are callers **outside this repository**? | Cross-repository callers cannot change along with it, so the contract must stay compatible | | Do callers also need changes? | When callers need adaptation but were not changed, describe the specific call path, trigger conditions, and consequences | | Is an overload with the same name affected by mistake? | Overload resolution changes are a classic silent break | `depth=1` (registry parameter): by default, trace only **one level** of callers. ## 2. Contract changes - Were fields added, removed, or changed in external interfaces (HTTP / RPC / MQ message bodies)? - Is the database schema change **backward compatible**? (nullable column added vs type change vs column dropped) - Did config file / environment variable key names change? (multi-environment matrix, see the design doc) - Did the serialization format change? (JSON field names, time format) First confirm whether the change breaks an existing contract, and describe the affected call forms, data, or deployment conditions and the specific consequences, then grade per the severity criteria in `references/output-schema.md`. A compatible change is not graded high merely because no separate compatibility strategy was written. ## 3. Performance Perform a static performance check on the paths involved in this change; it does not depend on whether a design doc is provided. Focus areas: newly added in-loop queries / N+1, full table scans, unbounded collections, synchronous blocking calls, cache breakdown, and batch sizes for bulk operations. When caches or proxies are involved, trace to the actual I/O boundary; without confirming database access, do not equate the number of method calls with the number of new SQL queries. If the design doc has performance targets, compare them against the relevant implementation, and judge whether targets are met only when corresponding measured data exists. When design targets or measured data are missing, record the verification limitation and do not fail it merely for missing material; when targets exist but no measured data does, note "performance targets unverified" and do not claim they are met based on code inspection alone. This probe does not run load tests automatically. When a risk is found, describe the specific code path, the applicable data scale or call conditions, and the possible consequences. ## 4. Architectural impact For modules and related call/dependency paths involved in this change, check against the project's existing architectural conventions: - Is unrelated responsibility crammed into the same module? - Are circular dependencies introduced, or the project's existing dependency direction broken? - After a public interface change, can existing implementations or callers still be replaced and used normally? - Do new abstractions and intermediate layers serve the current need? When a problem is found, cite the specific module, dependency, or call path, and describe the affected contract or constraint and the actual consequence. Do not rate it high risk merely for "not following some design principle", and do not force the project to adopt a fixed layering. Things that are only structural optimization suggestions without proven concrete breaking impact are recorded separately as non-blocking suggestions. ## 5. Anti-patterns - ❌ Reporting high just because a method name changed, without confirming whether there are callers — that is noise and drowns out real problems - ❌ Checking only in-repository callers and ignoring **external** consumers of external interfaces - ❌ Treating "someone could theoretically call it" as a finding (it must be **specific** about which caller)skills/code-review/references/probes/repo-guard.md2.8 KB3a87458d2993upstream ↗
# probe: repo-guard (repository standards) **required: true** | **requires: rules** | `read_only: true` ## 1. Select the rule pack 1. When `$CODE_REVIEW_RULES_DIR` is non-empty, use the specified path; if the path does not exist or is unreadable, record `error` and do not fall back. 2. When unspecified, use `<project root>/.code-review/rules/` if that directory exists; otherwise use the skill's built-in `references/rules/`. First read the selected rule pack's `index.md` to learn the rule topics. A project-provided rule pack **entirely replaces** the built-in one; they are not merged. ## 2. Rule scanning and candidate review The review scope and code version follow "Determine the review scope" in the main flow's `SKILL.md`. 1. **Select files**: choose `.java` / `.ts` / `.tsx` / `.vue` from the change list, including added files and excluding deleted files; when no applicable files exist, go to section 3. 2. **Run the scan**: pass the file paths one by one as independent arguments to `bash "$CODE_REVIEW_SKILL_DIR/scripts/rule-grep"`. The script scans the full file text, and hits may include old code. 3. **AI review**: with the corresponding rule, code context, and this round's diff, confirm whether a violation actually exists. Include only issues introduced or triggered by this change in the findings; record unrelated old issues separately and exclude them from this round's counts. Rules that cannot be decided by pattern go to section 3. When on-disk files differ from the review target version, read the target version's source and perform the AI semantic check per section 3. The scan only provides candidates; a hit is not a violation, and a miss is not compliance. ## 3. On-demand loading and AI semantic checks An AI semantic check means the AI reads the rule text, the diff, and the relevant code context to judge whether the rule applies and whether the code actually violates it. Load the corresponding rule files on demand based on the index, project language, change topic, and scan hits. Prose rules that cannot be machine-judged but are relevant to the change must also be loaded; do not check only the scan hits. Default check topics include: - SQL injection - XSS - Authentication and permission isolation - Sensitive data handling - Hardcoded credentials - Idempotency The specific requirements follow the rule files selected this round. Compare each selected rule against this change, and note in the report the rule files actually loaded and the checked scope. Produce findings only for HIGH-severity rules. When a standards violation is found, record the **applicable rule ID, file, line number, and violation description**. For general behavioral correctness checks, see [behavior-review](behavior-review.md). ## 4. Judgment and reporting Record `error` when the rule pack directory does not exist or not a single rule file can be read.skills/code-review/references/probes/test-coverage.md5.9 KBae028e4f5be1upstream ↗
# probe: test-coverage (test quality and incremental coverage) **required: true** | **requires: []** Each round, the AI checks the quality of the relevant tests; coverage is an optional sub-item. Tests need not be run, nor coverage measured, every round. ## 1. Inputs and boundaries - Locate the relevant test source and configuration for the target version per "Determine the review scope" in `SKILL.md`. - Coverage reports and measurement commands are both optional. First confirm whether the available material is a complete project or only a diff / partial source / minimal snapshot. With only partial material, you can only say "no relevant tests found in the current material"; the missing part is recorded as unverified, and you must not assert that the original project has no tests. ## 2. Test quality checks (every round) First compare against expected behavior to distinguish implementation regressions from problems in the tests themselves. When existing tests correctly reveal this regression, record their coverage and hand the behavioral suspicion and evidence back to the main executor; do not conclude that the tests are defective merely because they were not modified. Using the relevant tests and the code under test, check the tests themselves: - **Assertions**: do they verify real results and distinguish a correct implementation from the relevant errors, rather than merely executing code or unconditionally asserting `true`? For key changes, choose a plausible incorrect behavior (such as a wrong return value, an incorrect boundary condition, or a missing state update) and identify the specific test and assertion that would detect it. If none is found, assess whether the tests actually inspected have a scenario or assertion gap. Against expected behavior, check whether mocks bypass the logic under test, exceptions are swallowed, and important side effects such as state changes or call counts are verified; using mocks alone does not make a test ineffective. - **Scenarios**: do they cover the important boundaries and failure paths of this change; check retries and concurrency according to actual applicability. - **Stability**: do they depend on execution order, unstable time, or an uncontrollable external environment? - **Test changes**: were relevant tests deleted, skipped, commented out, or weakened? Judge with the reason and any substitute verification whether valid checks were lost; do not mark it high based merely on a deletion or a skip / pending marker. These checks are static judgments: do not modify the implementation or automatically run mutation testing, and do not present them as executed verification. Meeting coverage targets does not establish test effectiveness. Produce findings only for proven defects in the tests themselves or for scenario / assertion gaps, stating the location, expected-behavior basis, and risk; describe the gap within the actually checked test scope. When expected behavior is still under contract dispute, record an unverified item and do not presuppose assertion outcomes. Grade per `references/output-schema.md`; a missing test is not automatically high. ## 3. Optional coverage check and reporting Use effective `line_target` / `branch_target` handed off by init; project policy takes precedence, defaults are 85 / 80. Compare against these effective targets, not a separate fixed 85% baseline. 1. Prefer reusing an existing coverage report that corresponds to the review's target version and modules. 2. When no corresponding report exists, run measurement only if the project already has a measurement method and this round's task allows it; otherwise record "not measured" and still complete the test quality check. 3. With a valid report, compute the executable-line coverage of production source lines added or modified by this diff: `diff line coverage = covered executable changed lines / total executable changed lines × 100%`. The report must state the numerator, denominator, and result; do not use whole-file coverage; when processing JaCoCo XML, read the details at the end. | Coverage situation | Record and handling | |---|---| | Measured diff line coverage ≥ line_target | Meets target | | Measured diff line coverage < line_target | high finding, blocks merge | | Branch coverage | Target branch_target (default 80%); falling short is advisory only and does not block or lower scores | | Confirmed no executable incremental lines | Record coverage as "not applicable", explain why, and do not fill in 0% or 100% | | A specified report is unreadable, unparsable, incomplete, or version-mismatched, or the measurement command fails | `error`; must not be recorded as "not measured" or "not applicable" | Return the report path, target version, numerator/denominator, and percentage for valid coverage; the main executor reports them per `SKILL.md`. Do not fill unmeasured or inapplicable metrics with 0. ## 4. Reporting Record test quality, coverage status, and unverified items separately. `passed` does not mean the tests were run or coverage was measured. ## 5. JaCoCo report parsing details (read when processing XML) 1. Identify production and test source per the project source sets, extract added or modified lines of the target version's production source, and exclude deleted lines. 2. Match `<sourcefile>` / `<line nr="…" mi="…" ci="…">` by module, source path, and changed line numbers. `mi + ci > 0` counts toward the denominator, and `ci > 0` counts as covered. 3. Count each "module + source path + line number" only once; compute the ratio after aggregating, and do not average per-file percentages. 4. Record `error` when required data is missing; do not exclude it as non-executable or record it as "not applicable". This is [JaCoCo's line coverage definition](https://www.jacoco.org/jacoco/trunk/doc/counters.html): a line counts as covered if at least one instruction in it executed, which does not mean all branches on that line were covered.skills/code-review/references/rules/baseline-java.md2.5 KBa4c8d79add4aupstream ↗
# Common baseline (Java) A minimal usable rule set covering three high-frequency categories: security, concurrency, and exceptions. Add or remove rules according to the project's actual stack; replacing this file does not affect the orchestration logic in `SKILL.md`. ## Security ### SEC-001 [HIGH] SQL string concatenation SQL must use parameter binding; user input must never be concatenated into statements. **Detection pattern** (any hit is a violation): - `Statement[^;]*execute(Query|Update)?\s*\(\s*["'][^"']*["']\s*\+` - `create(Query|NativeQuery)\s*\(\s*["'][^"']*["']\s*\+` - `["'][^"']*(SELECT|INSERT|UPDATE|DELETE)[^"']*["']\s*\+` ### SEC-002 [HIGH] Hard-coded credentials Passwords, keys, and tokens must not be written in source code. **Detection pattern**: - `(password|passwd|secret|token|apiKey|accessKey|privateKey)\s*=\s*"[^"]{6,}"` ### SEC-003 [HIGH] Dangerous XSS rendering Untrusted content must not be passed directly to HTML rendering. **Detection pattern**: - `v-html|dangerouslySetInnerHTML|innerHTML\s*=` ### SEC-004 [HIGH] Command execution Confirm that arguments do not come from user input before invoking a system command. **Detection pattern**: - `Runtime\.getRuntime\(\)\.exec\s*\(|new\s+ProcessBuilder\s*\(` ### AUTH-001 [HIGH] Missing authorization on queries **Detection mode**: semantic When querying user data by business ID, include the current user or tenant condition as well. **Detection pattern**: none (manual check: whether the query condition contains only orderId / id, with no userId / tenantId) ## Logging and data ### LOG-001 [HIGH] Sensitive fields in logs ID card numbers, phone numbers, bank card numbers, passwords, and tokens must not appear in logs in plaintext. **Detection pattern**: - `log(ger)?\.(info|debug|warn|error)\([^)]*(idCard|idNo|phone|mobile|bankCard|password|token)` ## Concurrency and exceptions ### CONC-001 [HIGH] Shared SimpleDateFormat `SimpleDateFormat` is not thread-safe and must not be used as a static shared instance. **Detection pattern**: - `static\s+(final\s+)?SimpleDateFormat` ### CONC-002 [MEDIUM] Thread pool without explicit bounds `Executors` factory methods can easily create unbounded queues or unlimited threads. **Detection pattern**: - `Executors\.(newCachedThreadPool|newFixedThreadPool)\s*\(` ### ERR-001 [HIGH] Empty catch swallows exceptions An empty catch block fails silently; it must at least log the exception or rethrow it. **Detection pattern**: - `catch\s*\([^)]*\)\s*\{\s*\}`skills/code-review/references/rules/concurrency.md4.4 KBb7cbf09e6610upstream ↗
# Concurrency guidelines ## Thread pool creation **Detection mode**: semantic **Do not create thread pools with Executors (MULT-CONC-001):** ```java // Wrong: may cause OOM ExecutorService executor = Executors.newFixedThreadPool(10); // Correct: set all parameters explicitly ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // corePoolSize 20, // maximumPoolSize 60L, // keepAliveTime TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("worker-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() ); ``` ## SimpleDateFormat thread safety **Detection mode**: semantic ```java // Wrong: not thread-safe private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // Correct option 1: ThreadLocal private static final ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); // Correct option 2: use Java 8 DateTimeFormatter (recommended) private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); ``` ## Lock ordering **Detection mode**: semantic **Locking multiple resources must follow a consistent order to prevent deadlocks (MULT-CONC-002):** ```java // Wrong: may deadlock public void transfer(Account from, Account to, int amount) { synchronized (from) { synchronized (to) { from.debit(amount); to.credit(amount); } } } // Correct: lock in a fixed order public void transfer(Account from, Account to, int amount) { Account first = from.getId() < to.getId() ? from : to; Account second = from.getId() < to.getId() ? to : from; synchronized (first) { synchronized (second) { from.debit(amount); to.credit(amount); } } } ``` ## Concurrent modification **Detection mode**: semantic **Concurrent modification of the same record must use a lock or optimistic locking (MULT-CONC-003):** ```java // Optimistic locking approach @Update("UPDATE account SET balance = #{balance}, version = version + 1 WHERE id = #{id} AND version = #{version}") int updateWithVersion(Account account); // Retry logic (at least 3 times) int retry = 0; while (retry < 3) { Account account = accountDao.getById(id); int updated = accountDao.updateWithVersion(account); if (updated > 0) { break; } retry++; } ``` ## TOCTOU protection **Detection mode**: semantic **Check-Then-Act operations must be atomic (MULT-CONC-012):** ```java // Wrong: non-atomic operation if (account.getBalance() >= amount) { // Check account.debit(amount); // Act - the balance may have changed! } // Correct: use a database optimistic lock or distributed lock @Update("UPDATE account SET balance = balance - #{amount} WHERE id = #{id} AND balance >= #{amount}") int debitWithCheck(@Param("id") Long id, @Param("amount") Long amount); ``` ## High-concurrency optimization **Detection mode**: semantic **In high-concurrency code, do not use equality checks as interruption/exit conditions (MULT-CONC-004):** ```java // Wrong: may loop forever while (count == 0) { // may never exit } // Correct: use a range check while (count > 0) { // explicit exit condition } ``` ## Singleton thread safety **Detection mode**: semantic **Singleton objects must be thread-safe (MULT-CONC-005):** ```java // Double-checked locking (requires volatile) public class Singleton { private static volatile Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` ## Thread resources **Detection mode**: semantic **Thread resources must be provided through thread pools; do not create threads explicitly (MULT-CONC-007):** ```java // Wrong new Thread(new Runnable() { @Override public void run() { } }).start(); // Correct executor.execute(() -> { // ... }); ``` ## Random in multithreaded code **Detection mode**: semantic **Avoid sharing Random across threads; on JDK 7+ use ThreadLocalRandom (MULT-CONC-011):** ```java // Wrong Random random = new Random(); int value = random.nextInt(100); // Correct int value = ThreadLocalRandom.current().nextInt(100); ```skills/code-review/references/rules/exception-logging.md4.9 KB217ba0b68841upstream ↗
# Exception handling and logging guidelines ## Exception handling ### Exception catching **Detection mode**: semantic **Empty catch blocks are forbidden (MULT-EXC-001):** ```java // Wrong: swallows the exception try { doSomething(); } catch (Exception e) { // empty block } // Correct: at least log it try { doSomething(); } catch (Exception e) { log.error("Failed to do something", e); throw new BusinessException("operation failed", e); } ``` ### Transaction rollback **Detection mode**: semantic **Catching an exception in transactional code requires a manual rollback (MULT-EXC-002):** ```java @Transactional(rollbackFor = Exception.class) public void updateUser(User user) { try { userDao.update(user); userPhoneDao.update(user.getPhone()); } catch (Exception e) { log.error("failed to update user", e); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new BusinessException("update failed"); } } ``` ### finally rules **Detection mode**: semantic **Do not return from finally (MULT-EXC-003):** ```java // Wrong try { return 1; } finally { return 2; // Forbidden! Overrides the return in try } // Correct try { return 1; } finally { // Cleanup is allowed here, but no return } ``` ### Resource cleanup **Detection mode**: semantic **The finally block must close resources; on JDK 7+ use try-with-resources (MULT-EXC-004):** ```java // Correct try (InputStream is = new FileInputStream(file); OutputStream os = new FileOutputStream(output)) { // use the resource } catch (IOException e) { log.error("IO error", e); } ``` ### Pre-check principle **Detection mode**: semantic **Pre-checks are better than exception catching (MULT-EXC-005):** ```java // Wrong try { return user.getName().toUpperCase(); } catch (NullPointerException e) { return null; } // Correct if (user != null && user.getName() != null) { return user.getName().toUpperCase(); } return null; ``` ### Exception classification **Detection mode**: semantic **Distinguish stable code from unstable code (MULT-EXC-007):** ```java // Stable code: catch a specific exception try { userDao.insert(user); } catch (DuplicateKeyException e) { log.warn("Duplicate user: {}", user.getId()); throw new BusinessException("user already exists"); } // Unstable code: catch a broader exception try { thirdPartyService.call(); } catch (Exception e) { log.error("Third party call failed", e); throw new ServiceException("third-party service call failed", e); } ``` ### Cascading-call NPEs **Detection mode**: semantic **Cascading calls are prone to NPEs; use Optional (MULT-EXC-008):** ```java // Wrong String cityName = user.getAddress().getCity().getName(); // Correct String cityName = Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCity) .map(City::getName) .orElse("unknown"); ``` ## Logging ### Logging framework **Detection mode**: semantic **Depend on the SLF4J facade; do not use Log4j/Logback APIs directly (MULT-LOG-140):** ```java // Wrong: use Log4j/Logback directly import org.apache.log4j.Logger; // Correct: use SLF4J import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger log = LoggerFactory.getLogger(UserService.class); ``` ### Log content **Detection mode**: semantic **Exception logs must include context and the stack trace (MULT-LOG-141):** ```java // Correct log.error("params: {}, error: {}", userId, e.getMessage(), e); // Or use placeholders log.error("failed to update user, userId={}, error={}", userId, e.getMessage(), e); ``` ### Conditional output **Detection mode**: semantic **Use conditional output or placeholders for trace/debug/info (MULT-LOG-142):** ```java // Wrong: string concatenation log.debug("Processing user: " + userId); // Correct: placeholder log.debug("Processing user: {}", userId); // Or conditional output if (log.isDebugEnabled()) { log.debug("Processing user: {}", userId); } ``` ### Sensitive-data masking **Detection mode**: semantic **Logs must be masked (MULT-SEC-025/026):** ```java // Wrong: log sensitive information directly log.info("User login: phone={}, idCard={}", phone, idCard); // Correct: mask it log.info("User login: phone={}, idCard={}", SensitiveDataMasker.maskPhone(phone), SensitiveDataMasker.maskIdCard(idCard)); // Or use the masking helper log.info("User login: {}", SensitiveDataMasker.maskUserInfo(user)); ``` ### Log levels **Detection mode**: semantic **Disable debug in production; enable info selectively (MULT-LOG-146):** ```java // Wrong log.debug("Detailed info"); // Correct log.info("Operation info"); log.warn("Warning info"); log.error("Error info", e); ``` ### Duplicate logs **Detection mode**: semantic **Avoid duplicate log output; set additivity=false (MULT-LOG-143):** ```xml <!-- logback.xml --> <logger name="com.example.service" level="INFO" additivity="false"> <appender-ref ref="FILE"/> </logger> ```skills/code-review/references/rules/index.md2.0 KBae1f67f8fcbaupstream ↗
# Rule pack This index lists the built-in rule files and their purposes; see the [repo-guard probe](../probes/repo-guard.md) for the loading and checking flow. ## Location priority 1. `$CODE_REVIEW_RULES_DIR` 2. Project root `.code-review/rules/` 3. Skill built-in: `references/rules/` A project-provided rule pack replaces the built-in pack entirely (it is not merged). `bash "$CODE_REVIEW_SKILL_DIR/scripts/rule-grep"` uses the same priority order. ## Built-in files | File | Type | Content | |---|---|---| | `baseline-java.md` | Machine-checkable / semantic check | 9 baseline rules | | `concurrency.md` | Prose | Thread pools, SimpleDateFormat, lock ordering | | `exception-logging.md` | Prose | Exception catching, transaction rollback, logging | | `naming-format.md` | Prose | Naming and formatting | | `oop-collection.md` | Prose | Immutability, defensive programming, collections | | `spring-performance.md` | Prose | Spring / JPA / transactions / performance | | `testing-engineering.md` | Prose | Unit-test conventions | | `security.md` | Prose | Security baseline | ## Rule file format Each rule has one heading, followed by an optional `Detection pattern` line: ```markdown ### SEC-001 [HIGH] SQL string concatenation Why this rule matters. **Detection pattern** (any hit is a violation): - `regex-one` - `regex-two` ``` - The heading must be `### <rule-id> [<severity>] <title>`; the severity is one of `HIGH` / `MEDIUM` / `LOW` - Backtick fragments in `Detection pattern` are extracted by `bash "$CODE_REVIEW_SKILL_DIR/scripts/rule-grep"` and run as regexes - Rules without a detection pattern do not participate in regex scanning - **The rule ID is the finding's evidence**; a judgment without an ID does not count - Legacy localized rule packs may use their own detection-marker label; `rule-grep` accepts the supported aliases ## Constraints - Only write your team's own rules; do not copy internal standards of unknown origin - Keep regexes as narrow as possible; prefer missing a hit over producing many false positivesskills/code-review/references/rules/naming-format.md5.4 KBe5679ae80861upstream ↗
# Naming and formatting guidelines ## Naming conventions ### Basic naming rules **Detection mode**: semantic | Type | Rule ID | Convention | Example | Forbidden example | |------|--------|------|------|---------| | Class name | MULT-NAME-003 | UpperCamelCase | `UserDO`, `HtmlUtil`, `OrderService` | `userDo`, `htmlutil` | | Method name | MULT-NAME-004 | lowerCamelCase | `getUserById()`, `calculateTotal()` | `GetUserById()`, `calculate_total()` | | Constant | MULT-CONST-005 | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT` | `maxRetryCount`, `Max_Retry_Count` | | Package name | MULT-NAME-009 | all lowercase, singular | `com.example.user` | `com.example.Users`, `com.example.userMgmt` | | Variable | MULT-NAME-004 | lowerCamelCase | `userName`, `orderList` | `user_name`, `UserName` | | Array | MULT-NAME-007 | `String[] args` | `String[] args` | `String args[]` | ### Special naming rules **Detection mode**: semantic | Rule ID | Rule | Description | Example | |--------|------|------|------| | MULT-NAME-006 | Abstract class | Must start with Abstract or Base | `AbstractProcessor`, `BaseServiceImpl` | | MULT-NAME-006 | Exception class | Must end with Exception | `UserNotFoundException`, `InvalidParameterException` | | MULT-NAME-006 | Test class | Name of the class under test + Test | `UserServiceTest`, `OrderControllerTest` | | MULT-NAME-011 | Interface implementation | Impl suffix | `UserServiceImpl`, `OrderServiceImpl` | | MULT-NAME-017 | Domain model | DO/DTO/VO/BO suffix | `UserDO`, `UserDTO`, `UserVO`, `UserBO` | | MULT-NAME-013 | Enum class | Enum suffix | `StatusEnum`, `TypeEnum` | ### Boolean variable naming **Detection mode**: semantic **Boolean properties in POJOs must not use the is prefix (MULT-NAME-008):** ```java // Wrong: causes serialization problems public class User { private Boolean isSuccess; // Forbidden! private boolean isActive; // Forbidden! } // Correct public class User { private Boolean success; private boolean active; // getter methods may use the is prefix public boolean isSuccess() { return success; } } ``` ### Method naming conventions **Detection mode**: semantic | Operation | Rule ID | Naming prefix | Example | |---------|--------|---------|------| | Get one | MULT-NAME-014 | get | `getUserById()`, `getOrderByNo()` | | Get many | MULT-NAME-014 | list | `listUsersByStatus()`, `listOrdersByUser()` | | Get count | MULT-NAME-014 | count | `countActiveUsers()`, `countOrders()` | | Insert | MULT-NAME-014 | save/insert | `saveUser()`, `insertOrder()` | | Delete | MULT-NAME-014 | remove/delete | `removeUser()`, `deleteOrder()` | | Update | MULT-NAME-014 | update | `updateUserStatus()`, `updateOrderAmount()` | | Predicate | - | is/has/can | `isActive()`, `hasPermission()`, `canDelete()` | ### Constant conventions **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | MULT-CONST-001 | No magic values | Undefined constants must not appear directly | | MULT-CONST-002 | Use uppercase L for long literals | `long amount = 2L` (lowercase l is forbidden) | | MULT-CONST-003 | Group constants by function | Do not keep all constants in one class | | MULT-CONST-004 | Use enum for fixed ranges | Use an enum when a variable's value changes only within a fixed range | ## Code formatting conventions ### Indentation and spacing **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | MULT-FMT-001 | Indentation | 4 spaces; tab characters are forbidden | | MULT-FMT-002 | Line length | No more than 120 characters | | MULT-FMT-181 | Operator spacing | One space on each side: `a + b`, `a == b` | | MULT-FMT-183 | Comma spacing | One space after a comma: `func(a, b, c)` | | MULT-FMT-181 | Parenthesis spacing | One space between if/for/while and the parenthesis: `if (condition)` | | MULT-FMT-184 | File encoding | UTF-8, Unix line endings | ### Brace rules **Detection mode**: semantic ```java // Correct: opening brace on the same line, body on the next line if (condition) { doSomething(); } else { doOther(); } // Wrong: opening brace on a new line if (condition) { doSomething(); } ``` ### Control statements **Detection mode**: semantic **All control statements must use braces, even for a single line (MULT-CTRL-133):** ```java // Wrong if (condition) doSomething(); // Correct if (condition) { doSomething(); } ``` **switch must have a default branch (MULT-CTRL-132):** ```java switch (status) { case Status.ACTIVE: return "active"; case Status.INACTIVE: return "inactive"; default: return "unknown"; } ``` **if-else nesting must not exceed 3 levels; use guard clauses beyond that (MULT-CTRL-134):** ```java // Wrong: nesting too deep if (user != null) { if (user.isActive()) { if (user.hasPermission()) { // ... } } } // Correct: guard clauses if (user == null) { return; } if (!user.isActive()) { return; } if (!user.hasPermission()) { return; } // Normal logic ``` ### Method length limit **Detection mode**: semantic **A single method must not exceed 80 lines in total (MULT-FMT-186):** ```java // Correct: single responsibility, manageable length public UserDTO convertToDTO(UserDO userDO) { if (userDO == null) { return null; } UserDTO dto = new UserDTO(); dto.setId(userDO.getId()); dto.setName(userDO.getName()); dto.setStatus(userDO.getStatus()); return dto; } ```skills/code-review/references/rules/oop-collection.md7.0 KB9d63d58f8a69upstream ↗
# OOP and collection guidelines ## Core principles ### Immutability principle **Detection mode**: semantic **Prefer immutable data structures and side-effect-free operations:** ```java // Wrong: mutable operation public List<User> processUsers(List<User> users) { List<User> results = new ArrayList<>(); for (User user : users) { user.setVerified(true); // Modifies the original object! results.add(user); } return results; } // Correct: immutable operation (create new objects) public List<User> processUsers(List<User> users) { return users.stream() .map(u -> { User copy = new User(u); // Create a copy copy.setVerified(true); return copy; }) .collect(Collectors.toList()); } ``` ### Defensive programming principle **Detection mode**: semantic **Every place that may be null must be null-checked:** ```java // Wrong: may throw NPE public String getUserName(User user) { return user.getName().toUpperCase(); } // Correct: use Optional public String getUserName(User user) { return Optional.ofNullable(user) .map(User::getName) .map(String::toUpperCase) .orElse(""); } // Or use Objects.equals (recommended for comparisons) public boolean isEqual(User u1, User u2) { return Objects.equals(u1, u2); } ``` ## OOP conventions ### Overridden methods **Detection mode**: semantic **All overridden methods must have the @Override annotation (MULT-OOP-001):** ```java // Correct @Override public boolean equals(Object obj) { // ... } @Override public String toString() { // ... } ``` ### Object comparison **Detection mode**: semantic **Wrapper objects must be compared with equals (MULT-OOP-003):** ```java // Wrong: may compare references Integer a = 128; Integer b = 128; if (a == b) { } // false! // Correct if (a.equals(b)) { } // Safer: use Objects.equals (recommended) if (Objects.equals(a, b)) { } ``` **Call equals on a constant or an object known to be non-null (MULT-OOP-002):** ```java // Wrong: may throw NPE if (user.getName().equals("admin")) { } // Correct if ("admin".equals(user.getName())) { } // Or use Objects.equals if (Objects.equals("admin", user.getName())) { } ``` ### POJO conventions **Detection mode**: semantic ```java // Correct POJO class definition public class UserDTO { // Use wrapper types (MULT-OOP-004) private Long id; private String name; private Integer age; private Boolean active; // Do not use the is prefix (MULT-NAME-008) // Do not set default values (MULT-OOP-006) // private Integer status = 0; // Forbidden // Must have a toString method (MULT-OOP-011) @Override public String toString() { return "UserDTO{id=" + id + ", name='" + name + "'}"; } } ``` ### Access control **Detection mode**: semantic **Use the most restrictive access level: private > protected > default > public (MULT-OOP-121):** ```java // Utility classes must not have public constructors public class StringUtils { private StringUtils() { throw new UnsupportedOperationException("Utility class"); } public static boolean isEmpty(String str) { return str == null || str.isEmpty(); } } ``` ### Serialization conventions **Detection mode**: semantic **Do not change serialVersionUID when adding fields to a serializable class (MULT-OOP-007):** ```java // Correct private static final long serialVersionUID = 1L; ``` ### Constructor conventions **Detection mode**: semantic **Constructors must not contain business logic; put initialization in an init method (MULT-OOP-008):** ```java // Wrong: constructor contains business logic public UserService() { loadConfig(); // Forbidden initCache(); // Forbidden } // Correct public UserService() { // Initialize only necessary properties } @PostConstruct public void init() { loadConfig(); initCache(); } ``` ## Collection guidelines ### Collection initialization **Detection mode**: semantic **Specify the initial capacity to avoid resizing (MULT-COLL-130):** ```java // Correct List<String> list = new ArrayList<>(expectedSize); Map<String, Object> map = new HashMap<>(expectedSize); // Calculation formula int initialCapacity = (int) (expectedSize / 0.75) + 1; ``` ### equals/hashCode **Detection mode**: semantic **Overriding equals requires overriding hashCode (MULT-COLL-122):** ```java // Correct @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); } @Override public int hashCode() { return Objects.hash(id); } ``` ### Collection iteration **Detection mode**: semantic **Do not remove/add inside foreach (MULT-COLL-123):** ```java // Wrong: ConcurrentModificationException for (String item : list) { if (condition) { list.remove(item); // Forbidden! } } // Correct: use Iterator Iterator<String> iter = list.iterator(); while (iter.hasNext()) { String item = iter.next(); if (condition) { iter.remove(); } } // Or use removeIf (Java 8+), recommended list.removeIf(item -> condition); ``` ### Arrays.asList **Detection mode**: semantic **The collection returned by Arrays.asList() must not be add/remove/clear'd (MULT-COLL-125):** ```java // Wrong List<String> list = Arrays.asList("a", "b"); list.add("c"); // throws UnsupportedOperationException // Correct: create a new list List<String> list = new ArrayList<>(Arrays.asList("a", "b")); list.add("c"); ``` ### subList caveats **Detection mode**: semantic **subList is a view of the original collection; modifying the original causes ConcurrentModificationException (MULT-COLL-126):** ```java // Wrong List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); List<String> sub = list.subList(0, 2); list.add("d"); // causes sub iteration to fail // Correct: create an independent copy List<String> sub = new ArrayList<>(list.subList(0, 2)); ``` ### Collection to array **Detection mode**: semantic ```java // Wrong String[] arr = list.toArray(); // returns Object[] // Correct String[] arr = list.toArray(new String[0]); ``` ### Map iteration **Detection mode**: semantic ```java // Recommended: use entrySet (MULT-COLL-131) for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); } // Java 8+: use forEach map.forEach((key, value) -> { // ... }); ``` ### Empty collection checks **Detection mode**: semantic ```java // Wrong if (list.size() == 0) { } // Correct if (list.isEmpty()) { } // Safe empty check (recommended) if (CollectionUtils.isEmpty(list)) { } ``` ### ConcurrentHashMap **Detection mode**: semantic **ConcurrentHashMap does not allow null keys or values (MULT-COLL-129):** ```java // Wrong ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>(); map.put("key", null); // Forbidden // Correct: use HashMap (nulls allowed) Map<String, Object> map = new HashMap<>(); map.put("key", null); ```skills/code-review/references/rules/security.md3.4 KB0333ba2ad130upstream ↗
# Security guidelines ## HIGH security rules **Detection mode**: semantic | Rule ID | Rule | Description | Wrong example | Correct example | |--------|------|------|---------|---------| | SEC-101 | SQL parameter binding | String-concatenated SQL is forbidden | `"SELECT * FROM users WHERE id = " + userId` | `"SELECT * FROM users WHERE id = ?"` + PreparedStatement | | SEC-102 | No hard-coded credentials | API keys / passwords / tokens must not be hard-coded | `String password = "123456"` | `System.getenv("DB_PASSWORD")` | | SEC-103 | Input validation | All user input must be validated | Use request parameters directly | Use `@Valid` / `@Validated` | | SEC-104 | Sensitive-data masking | Logs must not print sensitive information | `log.info("user: {}", user)` | `log.info("phone: {}", SensitiveDataMasker.maskPhone(phone))` | | SEC-105 | CSRF protection | State-changing endpoints must verify CSRF | Direct POST | Add CSRF token verification | | SEC-106 | Permission checks | All user-data endpoints must enforce authorization | Return data directly | Verify that userId matches | | SEC-107 | XSS protection | v-html / dangerouslySetInnerHTML are forbidden | `<div v-html="content">` | Use a safe rendering method | ## Authentication and authorization **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | AUTH-101 | Permission checks | All user-data endpoints must enforce authorization and prevent horizontal privilege escalation | | AUTH-102 | Identity verification | External systems use unified authentication; internal systems use SSO | | AUTH-103 | Token storage | Must not be placed in URL parameters; use Cookie / POST body | | AUTH-104 | Cookie security | HttpOnly=true, Secure=true (HTTPS) | | AUTH-105 | Do not trust the client | Do not use client-supplied userId / roleId directly for authorization | ## Data security **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | DATA-101 | Sensitive-data masking | Phone numbers, ID numbers, bank card numbers, etc. must be masked in displays | | DATA-102 | Data classification | Decide whether to mask / watermark according to the project's data-classification standard | | DATA-103 | Number protection | Scenarios involving the real numbers of both parties must use the number-protection service | | DATA-104 | Query authorization | Do not query by business ID alone; always join userId / tenantId | ## API security **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | API-101 | Replay protection | Platform resources such as SMS and payments must be replay-protected | | API-102 | Rate limiting | Endpoints with enumerable parameters must be rate-limited to prevent abuse | | API-103 | Real client IP | Use trusted proxy headers; do not trust X-Forwarded-For directly | | API-104 | CORS | Access-Control-Allow-Origin must not be `*` | ## Frontend security **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | WEB-101 | XSS protection | v-html and dangerouslySetInnerHTML are forbidden | | WEB-102 | CSRF protection | Forms / AJAX must verify CSRF | | WEB-103 | Request methods | Non-query operations must not use GET | ## Key management **Detection mode**: semantic | Rule ID | Rule | Description | |--------|------|------| | KEY-101 | Key configuration | No hard-coding; use KMS / a key-management service | | KEY-102 | Private-key protection | Hard-coding `PRIVATE_KEY = "..."` is forbidden |skills/code-review/references/rules/spring-performance.md3.5 KB5e721a8433e3upstream ↗
Contents are not inlined in the catalog. Open the upstream file.
skills/code-review/references/rules/testing-engineering.md5.5 KBb1397a18fa11upstream ↗
Contents are not inlined in the catalog. Open the upstream file.
skills/code-review/assets/code-review.md2.3 KB46cefb65a206upstream ↗
Contents are not inlined in the catalog. Open the upstream file.
skills/code-review/assets/report-labels.json2.1 KB3b7135e90ae3upstream ↗
Contents are not inlined in the catalog. Open the upstream file.
README.mdupstream ↗
Contents are not inlined in the catalog. Open the upstream file.
Plain-text copy, no login: /skill-bundles/kyy-logs-code-review-skill-skills-code-review.md