Просмотр исходного кода

feat(ci): cap blame approval weight at quarter ownership

turtle1999 3 недель назад
Родитель
Сommit
6e60940ded

+ 2 - 2
.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md
-2026-09-11-production-blame-approval-weight.md: 5e0b767ba60ea92a0c510436f87ba21059c0f9e5
-2026-09-11-production-blame-approval-weight.zh.md: ce6afa0a8ad9cdd910e7fff1ffbeffddbd513f5e
+2026-09-11-production-blame-approval-weight.md: 047525734865866acc107508ca577e68b71e443c
+2026-09-11-production-blame-approval-weight.zh.md: 520c20b5361a150ca3919f5956fd3eef1a860e70

+ 1 - 1
.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md

@@ -10,7 +10,7 @@ A fixed one-point reviewer weight does not reflect authorship of the code a pull
 
 ## Decision
 
-The [approval policy](../../../../.github/review-ownership/README.md) scales a one-point approval by `min(2, 1 + 2 × ownedLines / totalLines)` over changed old production lines. Ownership of 0% gives one point, 25% gives 1.5 points, and 50% or more gives two points. Scores are not rounded before comparison with the two-point success threshold. The merge base supplies both classification and blame, while GitHub associates blame commits with reviewer accounts. Unlinked authors remain in the denominator. Additions have no prior owner and contribute no lines; an empty denominator produces no boost.
+The [approval policy](../../../../.github/review-ownership/README.md) scales a one-point approval by `min(2, 1 + 4 × ownedLines / totalLines)` over changed old production lines. Ownership of 0% gives one point, 12.5% gives 1.5 points, and 25% or more gives two points. Scores are not rounded before comparison with the two-point success threshold. The merge base supplies both classification and blame, while GitHub associates blame commits with reviewer accounts. Unlinked authors remain in the denominator. Additions have no prior owner and contribute no lines; an empty denominator produces no boost.
 
 The publisher marks the head pending before evaluation, so an interrupted history fetch cannot preserve an earlier success. It reads complete Git history without checking out PR code. A maintained lexer separates comments from code across the repository’s source languages. Author lookups batch commits and all reviewers share one measurement. Existing [pending-status semantics](2026-09-09-blocked-weighted-approvals-remain-pending.md) and [review-event validation](2026-09-10-approval-review-workflow-identity.md) remain independent requirements.
 

+ 1 - 1
.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.zh.md

@@ -10,7 +10,7 @@ Status: implemented
 
 ## Decision
 
-[审批策略](../../../../.github/review-ownership/README.md) 按变更旧生产代码行的归属比例,以 `min(2, 1 + 2 × ownedLines / totalLines)` 调整一分批准的权重。归属比例为 0% 时计一分,25% 时计 1.5 分,50% 及以上时计两分。分数在与两分通过线比较前不做舍入。合并基点同时提供代码分类和 blame 依据,GitHub 将归属提交关联到评审者账号。无法关联账号的作者仍计入分母。新增行没有原作者,不计入行数;分母为空时不提升权重。
+[审批策略](../../../../.github/review-ownership/README.md) 按变更旧生产代码行的归属比例,以 `min(2, 1 + 4 × ownedLines / totalLines)` 调整一分批准的权重。归属比例为 0% 时计一分,12.5% 时计 1.5 分,25% 及以上时计两分。分数在与两分通过线比较前不做舍入。合并基点同时提供代码分类和 blame 依据,GitHub 将归属提交关联到评审者账号。无法关联账号的作者仍计入分母。新增行没有原作者,不计入行数;分母为空时不提升权重。
 
 发布器在评估前将头提交标记为待定,避免历史拉取中断后保留此前的成功状态。它读取完整 Git 历史,不检出 PR 代码。维护中的词法分析器区分仓库各源码语言中的注释与代码。作者查询按提交批量执行,所有评审者共用一次统计。现有的[待定状态语义](2026-09-09-blocked-weighted-approvals-remain-pending.zh.md)和[评审事件验证](2026-09-10-approval-review-workflow-identity.zh.md)仍是独立要求。
 

+ 1 - 1
.github/review-ownership/README.md

@@ -19,7 +19,7 @@ The weighted approval workflow exposes two pull-request checks. The `weighted ap
 
 Reviewers whose calculated base repository permission is `write` or `admin` count. The [approval policy](approval-policy.json) gives `@07akioni`, `@imccyu`, `@tianyicui`, `@tianyicui-bot`, `@turtle1999`, and `@turtle2099` two points each; every other write-capable reviewer gets one point. The pull-request author and reviewers without write permission do not count.
 
-A one-point approval receives weight `min(2, 1 + 2 × ownedLines / totalLines)` from modified or deleted old production-code lines, attributed by `git blame` at the merge base of the PR base and head. Ownership of 0%, 25%, and 50% gives 1, 1.5, and 2 points; higher ownership remains capped at 2. The success threshold remains 2 total points, without rounding the score. New lines do not enter the denominator, and an empty denominator gives no boost. Existing two-point weights remain unchanged. GitHub commit-author accounts identify reviewers across author emails; unlinked authors remain in the denominator without contributing to a reviewer. The publisher logs each eligible reviewer’s owned and total line counts.
+A one-point approval receives weight `min(2, 1 + 4 × ownedLines / totalLines)` from modified or deleted old production-code lines, attributed by `git blame` at the merge base of the PR base and head. Ownership of 0%, 12.5%, and 25% gives 1, 1.5, and 2 points; higher ownership remains capped at 2. The success threshold remains 2 total points, without rounding the score. New lines do not enter the denominator, and an empty denominator gives no boost. Existing two-point weights remain unchanged. GitHub commit-author accounts identify reviewers across author emails; unlinked authors remain in the denominator without contributing to a reviewer. The publisher logs each eligible reviewer’s owned and total line counts.
 
 Production source means supported code files under `src/` in `packages/`, `apps/`, `python/`, and `native/`, plus the Desktop renderer, Python interpreter scripts, and committed runtime/packer launchers. The [classifier](blame-production.py) excludes documentation, tests, fixtures, snapshots, test support, examples, generated source, dependencies, vendored code, declarations, comments, and blank lines. Pygments lexers distinguish comments from strings; mixed code/comment lines count, as do C preprocessor directives. Classification uses the old path and content, so changes to the PR’s file locations or generated headers cannot remove old lines from the denominator. Pure renames have no changed lines; renames with edits use the old path for blame.
 

+ 1 - 1
.github/review-ownership/check-approval.mjs

@@ -168,7 +168,7 @@ export async function evaluateApproval({ event, policySource, api, getOwnership
       if (approval.points !== 1) continue
       const ownedLines = ownership.reviewerLines[approval.login.toLowerCase()] ?? 0
       approval.ownership = { ownedLines, totalLines: ownership.totalLines }
-      if (ownership.totalLines > 0) approval.points = Math.min(2, 1 + 2 * ownedLines / ownership.totalLines)
+      if (ownership.totalLines > 0) approval.points = Math.min(2, 1 + 4 * ownedLines / ownership.totalLines)
     }
   }
   approvals.sort((left, right) => left.login.localeCompare(right.login, 'en'))

+ 2 - 2
.github/review-ownership/check-approval.test.mjs

@@ -360,7 +360,7 @@ test('sends authenticated JSON and escapes an API error body', async () => {
   await assert.rejects(failing('/failure'), /"::error::untrusted\\nbody"/u)
 })
 
-for (const [ownedLines, totalLines, expectedPoints] of [[0, 100, 1], [25, 100, 1.5], [49, 100, 1.98], [50, 100, 2], [51, 100, 2], [100, 100, 2], [0, 0, 1]]) {
+for (const [ownedLines, totalLines, expectedPoints] of [[0, 100, 1], [1, 8, 1.5], [24, 100, 1.96], [1, 4, 2], [25, 100, 2], [26, 100, 2], [100, 100, 2], [0, 0, 1]]) {
   test(`scores ${ownedLines}/${totalLines} old production lines as ${expectedPoints} points`, async () => {
     let measurements = 0
     const result = await evaluateApproval({
@@ -437,5 +437,5 @@ test('revokes a previous success before starting expensive attribution', async (
       return {}
     },
   })
-  assert.deepEqual(states, ['pending', 'pending'])
+  assert.deepEqual(states, ['pending', 'success'])
 })