Skip to content

feat(gate): change risk 子命令 + PR 门禁——percentile≥90 拦截,risk-accepted 放行 - #102

Merged
2233admin merged 4 commits into
mainfrom
task/pr-gate-95
Aug 1, 2026
Merged

feat(gate): change risk 子命令 + PR 门禁——percentile≥90 拦截,risk-accepted 放行#102
2233admin merged 4 commits into
mainfrom
task/pr-gate-95

Conversation

@2233admin

Copy link
Copy Markdown
Owner

干什么

给每张 PR 装上机器风险门禁(#95 最小可行版),自家 CLI 吃自家狗粮,不依赖第三方 repowise:

  1. code-intel change risk <revspec>(新子命令,crates/code-intel-cli/src/change_risk.rs):确定性、纯 git、免索引、免网络、免 LLM 的 0–100 缺陷风险分。四信号定权重(30/25/25/20):diff 形状、测试不对称、bug 磁铁重叠(180d fix 提交)、churn 重叠(90d)。百分位对照最近 50 个非 merge 提交(各自锚定提交时刻,永久可复现)。空 diff / 坏 revspec 退出码 0 + "warning":"empty_diff",永不 panic。输出 machine-first JSON(schema: code-intel-change-risk.v1),--format text 从同一 JSON 派生。
  2. .github/workflows/pr-gate.yml:PR 上构建 CLI → 打分 → 单条 sticky 评论(marker 更新,不刷屏)→ risk_percentile >= 90 且无 risk-accepted 标签则红检查,阻断 auto-merge。labeled/unlabeled 事件也触发,补挂标签即可解锁。actions 均按 SHA 钉死,与 ci.yml 同源。

狗粮时刻

本 PR 用自己的门禁给自己打分:score 82,90 分位,high——会被自己拦下。属实(1310 行新增、bug 磁铁区注册路由)。按设计走 risk-accepted 标签放行,这本身就是门禁流程的首次真实演练。

测试

  • 9 个新单测(含确定性、空 diff、权重合规、"评分范围内提交不计入自身历史"的有牙齿测试)
  • 全套 cargo test -p code-intel --locked:382/382 主套件,聚合 2832/2832 全绿
  • cargo fmt --check 干净;新增代码 clippy 干净(crate 级 110+ 存量告警与本变更无关)

已知局限(如实记录)

  • 测试不对称信号按路径启发式判定(per spec),看不见行内 #[cfg(test)] mod tests——本 PR 自己就是例子(带 9 测试仍被记"未动测试")。后续可做 hunk 级检测。
  • fork PR 上 GITHUB_TOKEN 只读,评论步骤会失败(本仓单人分支流,暂不影响)。

Refs #95

Adds `code-intel change risk <revspec>`: a deterministic, git-only
defect-risk score (diff shape, test asymmetry, bug-magnet overlap,
churn overlap; fixed documented weights) plus its percentile against
the last N sampled non-merge commits. No index, no network, no LLM —
dogfoods our own CLI instead of the third-party repowise MCP.

Adds .github/workflows/pr-gate.yml: builds the CLI, scores every PR
against origin/<base>..HEAD, posts/updates one sticky PR comment, and
fails the job when risk_percentile >= 90 unless the PR carries the
risk-accepted label. Mirrors ci.yml's toolchain/action choices (same
actions/checkout and actions/upload-artifact pins, no new third-party
actions, no dependency cache since ci.yml itself has none).

Refs #95
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@repowise-bot

repowise-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

🔒 Repowise is not analyzing this repository

The PR bot is free on public repositories. This one is private, which needs a Pro plan.

See plans · Manage this repository

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a change risk command that analyzes code changes and reports risk scores, percentile rankings, contributing signals, and affected files.
    • Supports human-readable and JSON report formats, configurable sampling, revision ranges, and warning reports for empty or unavailable diffs.
    • Added automated pull-request risk reports with updated comments and configurable gating for high-risk changes.
    • Pull requests can bypass high-risk blocking with the risk-accepted label.

Walkthrough

The change adds a Git-based change risk CLI command and integrates it into a pull-request workflow. The workflow publishes risk reports, updates sticky comments, and blocks high-percentile changes without the risk-accepted label.

Changes

Change risk scoring and PR enforcement

Layer / File(s) Summary
CLI route and execution contract
crates/code-intel-cli/src/main.rs, crates/code-intel-cli/src/change_risk/mod.rs
The CLI exposes change risk, validates revisions and options, maps errors to exit codes, and renders JSON or text output.
Git diff and history collection
crates/code-intel-cli/src/change_risk/git.rs, crates/code-intel-cli/src/change_risk/mod.rs
Git helpers resolve revisions, parse diffs, handle root commits, anchor timestamps, sample history, and collect per-file history.
Git risk scoring pipeline
crates/code-intel-cli/src/change_risk/signals.rs, crates/code-intel-cli/src/change_risk/scoring.rs, crates/code-intel-cli/src/change_risk/render.rs, crates/code-intel-cli/src/change_risk/mod.rs
The command computes diff-shape, test-asymmetry, bug-magnet, and churn signals, combines weighted scores, computes percentiles, and builds reports.
Scoring edge-case validation
crates/code-intel-cli/src/change_risk/tests.rs
Tests cover classification, deterministic results, empty and root-commit diffs, range normalization, and exclusion of scored commits from history and baselines.
Pull-request reporting and enforcement
.github/workflows/pr-gate.yml
The workflow builds and runs the CLI, uploads JSON output, updates comments, skips fork comments, and blocks high-percentile changes unless accepted.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Poem

A rabbit reviews the risk report bright,
Git hops through history left and right.
Four signals weave a careful trail,
Sticky comments share the tale.
High scores meet a labeled gate—
“Ship with care!” says Bunny Eight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the new change-risk CLI subcommand and PR gate behavior, including the percentile threshold and override label.
Description check ✅ Passed The description directly explains the CLI subcommand, workflow gate, scoring behavior, tests, and known limitations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@2233admin 2233admin added the risk-accepted 人已审阅并接受高风险评分,放行 pr-gate label Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Intel change risk

Score Percentile Level
78/100 91th (vs last 46 commits) 🔴 high

Top signals

  • Diff shape: 8 file(s), +1535/-0 (max file share 0.23)
  • Test asymmetry: source changed, no tests touched
  • Bug-magnet: 4 fix commit(s) in touched files (180d)
  • Churn: 25 commit(s) touching these files (90d)

revspec: origin/main..HEAD · threshold: percentile >= 90 blocks unless labeled risk-accepted · code-intel change risk

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
crates/code-intel-cli/src/change_risk.rs (1)

811-893: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deduplicating the Some/None report branches.

The Some(scored) and None branches build the same nested JSON shape by hand, once from real values and once from hardcoded zero defaults. The key sets currently match exactly, but nothing enforces that they stay in sync if a field is added later to one branch and forgotten in the other.

Introduce a zero-valued default for Scored (or an intermediate struct) and always fill the JSON from it, only overriding fields when scored is Some, so there is a single place that defines the signal shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/code-intel-cli/src/change_risk.rs` around lines 811 - 893, Refactor
build_report so the Some(scored) and None branches no longer duplicate the
signals JSON shape. Introduce a zero-valued Scored or intermediate
representation, select real values when scored is present and defaults
otherwise, then construct the nested signals object once while preserving all
existing fields and values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pr-gate.yml:
- Around line 108-120: Add the pull-request fork guard to the “Post or update
sticky comment” step using the condition
github.event.pull_request.head.repo.fork == false. Keep the existing comment
lookup and POST/PATCH logic unchanged, ensuring fork PRs skip only this step
while the gate evaluation still runs.
- Around line 114-120: Update the workflow job containing the sticky-comment
lookup and PATCH/POST logic to define a shared concurrency group keyed by pull
request number, with cancel-in-progress enabled. Ensure all supported trigger
types for the same PR use that group so only the latest run can execute the
EXISTING_ID comment-update path.
- Around line 122-137: Update the Evaluate gate step to inspect the report
status in risk.json before reading risk_percentile; when the status is warning,
write blocked=false to GITHUB_OUTPUT and exit the step successfully. Keep the
existing percentile and label-based blocking logic unchanged for non-warning
reports.
- Around line 39-42: Update the Checkout action configuration to explicitly set
persist-credentials to false alongside fetch-depth, while leaving GH_TOKEN usage
unchanged wherever authentication is required.

In `@crates/code-intel-cli/src/change_risk.rs`:
- Around line 958-1157: Extend the tests around execute with focused coverage
for baseline contamination by creating several commits, scoring a multi-commit
range with a sample large enough to include the target commits, and asserting
those commits are excluded from the risk_percentile baseline. Add a separate
triple-dot range test with a diverging branch and verify commits_in_range
matches exactly the commits represented by the diff. Run the focused cargo tests
and relevant integration-contract checks.
- Around line 301-345: Filter the commits returned by sample_history in execute
before scoring them, skipping any SHA present in the existing exclude BTreeSet.
Keep the sample history traversal and scoring unchanged for commits outside the
scored range, so baseline_scores contains no commits from the target range.
- Around line 438-473: Update commits_in_range to normalize revspecs containing
“...” to the equivalent “..” form before passing them to git rev-list,
preserving other range inputs unchanged. Use the normalized range in the
existing hardened_git command while retaining the current best-effort
empty-result behavior.

---

Nitpick comments:
In `@crates/code-intel-cli/src/change_risk.rs`:
- Around line 811-893: Refactor build_report so the Some(scored) and None
branches no longer duplicate the signals JSON shape. Introduce a zero-valued
Scored or intermediate representation, select real values when scored is present
and defaults otherwise, then construct the nested signals object once while
preserving all existing fields and values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 87e22a84-a8ce-441b-8e8a-a4ab67b3bdf7

📥 Commits

Reviewing files that changed from the base of the PR and between 8a79814 and 724b95d.

📒 Files selected for processing (3)
  • .github/workflows/pr-gate.yml
  • crates/code-intel-cli/src/change_risk.rs
  • crates/code-intel-cli/src/main.rs

Comment thread .github/workflows/pr-gate.yml
Comment thread .github/workflows/pr-gate.yml
Comment thread .github/workflows/pr-gate.yml
Comment thread .github/workflows/pr-gate.yml
Comment thread crates/code-intel-cli/src/change_risk.rs Outdated
Comment thread crates/code-intel-cli/src/change_risk.rs Outdated
Comment thread crates/code-intel-cli/src/change_risk.rs Outdated
Refs #95

sentrux_gate's god-file rule (sentrux_gate.rs:738, `loc > 800 || (functions > 25 && loc > 400)`) flagged the new change_risk.rs at 1157 lines; split into change_risk/{mod,git,signals,scoring,render,tests}.rs, each well under threshold, behavior/JSON/CLI/tests unchanged.
Refs #95

- change_risk/mod.rs: execute()'s baseline loop now skips sampled commits already in the target's own `exclude` set (baseline self-contamination inflated risk_percentile).
- change_risk/git.rs: commits_in_range() normalizes a...b to a..b before rev-list, matching git diff's merge-base(a,b)..b semantics instead of rev-list's symmetric difference.
- change_risk/tests.rs: 2 new tests covering both fixes (11 total, up from 9).
- change_risk/mod.rs + render.rs: Scored and its signal structs derive Default; build_report's Some/None match collapses to one code path (nitpick 4835249459).
- pr-gate.yml: Evaluate gate checks has("warning") before the numeric comparison; checkout gets persist-credentials: false; sticky-comment step skips on fork PRs; workflow-level concurrency group added.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
crates/code-intel-cli/src/change_risk/mod.rs (1)

50-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep one canonical hardened_git module declaration.

crates/code-intel-cli/src/main.rs declares mod hardened_git;, so this #[path = "../hardened_git.rs"] mod hardened_git; in crates/code-intel-cli/src/change_risk/mod.rs creates a second crate::change_risk::hardened_git module from the same file. Reuse crate::hardened_git from the sibling modules instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/code-intel-cli/src/change_risk/mod.rs` around lines 50 - 51, Remove
the local `#[path] mod hardened_git` declaration from `change_risk` and update
its references to use the canonical `crate::hardened_git` module declared in
`main.rs`, matching the sibling modules’ usage.
crates/code-intel-cli/src/change_risk/tests.rs (1)

10-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard against leaked temp directories on test failure.

Every integration test in this file calls std::fs::remove_dir_all(&repo).ok() only at the end of the test body, after all assertions. If any assert!/assert_eq! call panics (in init_repo, commit, or the test itself), the temp repository directory under std::env::temp_dir() is never removed. This affects empty_diff_reports_a_warning_without_erroring, scoring_is_deterministic_for_the_same_revspec, root_commit_diffs_against_the_empty_tree, a_commit_inside_the_scored_range_does_not_count_toward_its_own_files_history, baseline_sampling_excludes_commits_inside_the_scored_range, and commits_in_range_normalizes_triple_dot_to_the_diffed_commit_set.

Use a Drop-based guard (or the tempfile crate's TempDir) so cleanup runs even on panic.

♻️ Suggested direction
-fn init_repo(name: &str) -> PathBuf {
+struct TempRepo(PathBuf);
+
+impl std::ops::Deref for TempRepo {
+    type Target = Path;
+    fn deref(&self) -> &Path { &self.0 }
+}
+
+impl Drop for TempRepo {
+    fn drop(&mut self) {
+        std::fs::remove_dir_all(&self.0).ok();
+    }
+}
+
+fn init_repo(name: &str) -> TempRepo {
     ...
-    repo
+    TempRepo(repo)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/code-intel-cli/src/change_risk/tests.rs` around lines 10 - 34, Update
init_repo and the listed integration tests to manage temporary repositories with
panic-safe cleanup, using a Drop-based guard or tempfile::TempDir instead of
relying on end-of-test remove_dir_all calls. Ensure cleanup still occurs when
assertions panic in repository setup, commits, or test bodies, while preserving
each test’s existing repository path usage.
crates/code-intel-cli/src/change_risk/signals.rs (1)

127-132: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Tighten the fix-commit heuristic to avoid substring false positives.

looks_like_fix_subject uses "subject.to_lowercase().contains("fix") || subject.contains("修复") || subject.contains("修正")". This matches "fix" as a substring, so commit subjects like "add prefix support", "update fixture data", or "refactor suffix handling" count as bug fixes. bug_magnet_signal and build_scored_files both derive bugFixCommits180d directly from this function, and that count feeds the weighted risk score that gates PRs. Unrelated commits inflate the bug-magnet subscore and, in turn, the overall risk score and percentile.

Match "fix" and its common conjugations as whole words instead of as a substring anywhere in the subject.

♻️ Proposed fix
 pub(super) fn looks_like_fix_subject(subject: &str) -> bool {
-    subject.to_lowercase().contains("fix") || subject.contains("修复") || subject.contains("修正")
+    let lower = subject.to_lowercase();
+    lower
+        .split(|c: char| !c.is_alphanumeric())
+        .any(|word| matches!(word, "fix" | "fixed" | "fixes" | "fixing" | "fixup"))
+        || subject.contains("修复")
+        || subject.contains("修正")
 }

As per coding guidelines, "Rust changes require focused cargo test coverage plus the relevant integration-contract checks," so add unit tests covering both the true positives (e.g., "fix: null check", "fixed race") and the previously mis-scored false positives (e.g., "add prefix support") when applying this change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/code-intel-cli/src/change_risk/signals.rs` around lines 127 - 132,
Update looks_like_fix_subject to match “fix” and common conjugations such as
“fixed” only as whole words, while preserving detection of the Chinese markers
修复 and 修正. Add focused unit tests covering true positives like “fix: null check”
and “fixed race”, plus false positives such as “add prefix support”, and run the
relevant Rust integration-contract checks.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/code-intel-cli/src/change_risk/git.rs`:
- Around line 90-126: Disable Git path quoting in both run_git_diff_numstat and
file_commit_history by adding the core.quotePath=false configuration to their
subprocess commands before parsing --numstat/--name-only output, ensuring
non-ASCII paths remain usable by normalize_path and subsequent lookups.

---

Nitpick comments:
In `@crates/code-intel-cli/src/change_risk/mod.rs`:
- Around line 50-51: Remove the local `#[path] mod hardened_git` declaration
from `change_risk` and update its references to use the canonical
`crate::hardened_git` module declared in `main.rs`, matching the sibling
modules’ usage.

In `@crates/code-intel-cli/src/change_risk/signals.rs`:
- Around line 127-132: Update looks_like_fix_subject to match “fix” and common
conjugations such as “fixed” only as whole words, while preserving detection of
the Chinese markers 修复 and 修正. Add focused unit tests covering true positives
like “fix: null check” and “fixed race”, plus false positives such as “add
prefix support”, and run the relevant Rust integration-contract checks.

In `@crates/code-intel-cli/src/change_risk/tests.rs`:
- Around line 10-34: Update init_repo and the listed integration tests to manage
temporary repositories with panic-safe cleanup, using a Drop-based guard or
tempfile::TempDir instead of relying on end-of-test remove_dir_all calls. Ensure
cleanup still occurs when assertions panic in repository setup, commits, or test
bodies, while preserving each test’s existing repository path usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 427085cb-e73c-4907-a3bd-56c397b65eb8

📥 Commits

Reviewing files that changed from the base of the PR and between 724b95d and d826021.

📒 Files selected for processing (7)
  • .github/workflows/pr-gate.yml
  • crates/code-intel-cli/src/change_risk/git.rs
  • crates/code-intel-cli/src/change_risk/mod.rs
  • crates/code-intel-cli/src/change_risk/render.rs
  • crates/code-intel-cli/src/change_risk/scoring.rs
  • crates/code-intel-cli/src/change_risk/signals.rs
  • crates/code-intel-cli/src/change_risk/tests.rs

Comment thread crates/code-intel-cli/src/change_risk/git.rs
@2233admin
2233admin merged commit 47e5941 into main Aug 1, 2026
8 checks passed
@2233admin
2233admin deleted the task/pr-gate-95 branch August 1, 2026 18:43
2233admin added a commit that referenced this pull request Aug 1, 2026
CodeRabbit 追审两条(PR #102 合并后到达):
- run_git_diff_numstat / file_commit_history 加 -c core.quotePath=false——非 ASCII 路径默认被 C 引号转义,永远匹配不上仓库相对路径键,历史/统计会漏文件;带回归测试(中文文件名 fixture)。
- change_risk/mod.rs 移除 #[path] 二次声明,统一走 crate::hardened_git,消除同文件双模块实例。

Refs #95
2233admin added a commit that referenced this pull request Aug 1, 2026
北极星落地批次:PR 门禁占位(#102/#108)、eval 双臂基准立裁判(#107/#109)、docs 生命周期首扫 + out-of-scope 注册表(#109/#116)、第二仓冷启动出自举圈(#110,摩擦 #111-#115 立案)。

版本绑定点同步:Cargo.toml + orchestration/toolchain-versions.v1.json + Cargo.lock。
门禁证据:cargo test 无失败、权威 self-scan exit 0、retirement packet 套件 8/8、repin clean。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk-accepted 人已审阅并接受高风险评分,放行 pr-gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant