Skip to content

feat(frontend): 覆盖率 include 合同 — 未导入生产代码进分母 + 绝对 floors + uncovered ratchet (#1535) - #1552

Merged
DeliciousBuding merged 4 commits into
masterfrom
test/frontend-coverage-truth
Aug 2, 2026
Merged

feat(frontend): 覆盖率 include 合同 — 未导入生产代码进分母 + 绝对 floors + uncovered ratchet (#1535)#1552
DeliciousBuding merged 4 commits into
masterfrom
test/frontend-coverage-truth

Conversation

@DeliciousBuding

@DeliciousBuding DeliciousBuding commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

目标(#1535

Web/Desktop/Mobile/Shared 均覆盖未被测试 import 的生产模块,叠加绝对阈值 + master baseline 双重约束;删除/遗忘模块后覆盖率数字不再好看。

改动

共享 coverage factory(app/test-config/coverage.ts

  • 四包统一 include: ['src/**/*.ts', 'src/**/*.tsx'](生产源码全量进分母)
  • 默认排除仅测试类别:*.test.* / __tests__ / __e2e__(Playwright spec)
  • 窄排除带理由:web/desktop main.tsx(纯入口,shared index.ts 先例);shared 保留 5 个既有文件排除(factory 重写行为等价:数字与旧 baseline 完全一致)

绝对 floors(2026-08-03 实测,floor(实测)-1 留 v8 抖动缓冲)

package lines stmt fn br
shared 60 60 60 60(既有)
web 63 62 53 57
desktop 48 46 40 39
mobile 35 34 26 21

低于 60 的维度为提升目标(记录在 baseline note),非永久豁免。

门禁(verify-coverage-baseline.ps1)

  • 输出 package coverage: production_files=N uncovered_files=M
  • uncovered_files(0% 行数的生产模块)不得增长(ratchet:新增未测试代码/删除测试都会触发)
  • production_files == 0(include glob 失效)→ fail-closed
  • baseline 重测(master 1a9f377,四包测试数 1738/220/587/339)

负向自测(validate job)

coverage-include.Tests.ps1:注入未导入 probe 模块 → 必须计 0% 且 uncovered 26 > baseline 25(ratchet 会响)→ 清理。

揭示的真相(分母扩大)

web 66.96→64.69、desktop 71.82→49.29、mobile 80.5→36.19 lines —— 之前未导入文件不进分母,数字虚高。

验证

  • 四包 coverage 本地全量:数字与 baseline 一致,0 skipped,0 排除警告
  • 负向自测 PASS(probe 计 0%、ratchet 触发)
  • workspace typecheck 全过
  • checks.yml:删 frontend-desktop 冗余 shared coverage step;mobile job 删 add -D 与 CLI 阈值(floors 在 config);validate 加负向自测 step;verify-ci-gates.ps1 断言同步

禁止清单遵守

  • 无 include 扩大化排除(仅测试类别 + main.tsx 入口)
  • 无测试删除、无 baseline 降低(新数字是分母扩大的真相,非放宽)

Summary by CodeRabbit

  • Bug Fixes

    • Improved frontend coverage reporting to include all production source files, including unimported files.
    • Added safeguards to detect coverage regressions and increases in uncovered files.
    • Extended coverage validation across desktop, web, shared, and mobile applications.
  • Documentation

    • Updated coverage baselines, thresholds, metrics, and progress tracking to reflect the expanded coverage policy.

Copilot AI review requested due to automatic review settings August 2, 2026 20:33

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@DeliciousBuding, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 329643f5-dce6-4b5f-a658-eedbd1117b2b

📥 Commits

Reviewing files that changed from the base of the PR and between a672d94 and c7dc372.

📒 Files selected for processing (11)
  • .github/workflows/checks.yml
  • app/desktop/vitest.desktop-ci.config.ts
  • app/mobile-rn/vitest.config.ts
  • app/shared/vitest.config.ts
  • app/test-config/coverage.ts
  • app/web/vitest.config.ts
  • docs/progress/MASTER.md
  • scripts/verify/coverage-baseline.json
  • scripts/verify/tests/coverage-include.Tests.ps1
  • scripts/verify/verify-ci-gates.ps1
  • scripts/verify/verify-coverage-baseline.ps1
📝 Walkthrough

Walkthrough

Frontend Vitest projects now use shared coverage configuration. CI validates production-source inclusion, coverage thresholds, and uncovered-file baselines across four packages. A negative self-test verifies that unreferenced production files appear as uncovered.

Changes

Frontend coverage configuration

Layer / File(s) Summary
Shared coverage configuration
app/test-config/coverage.ts, app/*/vitest*.config.ts
Added createCoverage for production-source inclusion, default exclusions, package exclusions, and per-metric thresholds. Desktop, mobile, shared, and web configurations now use it.
Coverage baseline and package checks
scripts/verify/verify-coverage-baseline.ps1, scripts/verify/coverage-baseline.json, docs/progress/MASTER.md
Added production-file matching and uncovered-file ratchet checks. Updated package metrics, floors, uncovered-file counts, and coverage policy documentation.
CI coverage validation
.github/workflows/checks.yml, scripts/verify/tests/coverage-include.Tests.ps1, scripts/verify/verify-ci-gates.ps1
Updated CI coverage commands and gate validation. Added a negative self-test for unreferenced production files. Removed the desktop shared coverage gate.

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

Possibly related issues

  • TokenDanceLab/AgentHub issue 1535: The PR implements the issue’s frontend coverage configuration, thresholds, uncovered-file detection, baseline enforcement, and CI validation.

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main coverage changes, including production-file inclusion, absolute floors, and the uncovered-file ratchet.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/frontend-coverage-truth

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.

@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: 2

🧹 Nitpick comments (2)
scripts/verify/verify-coverage-baseline.ps1 (1)

176-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared uncovered-file counting rule into scripts/lib/. Both scripts independently implement the same rule — a file with lines.total > 0 and lines.pct == 0.0 counts as an uncovered production module — for tallying production_files/uncovered_files from a coverage-summary.json.

  • scripts/verify/verify-coverage-baseline.ps1#L176-L186: replace this inline loop with a call to a shared helper function.
  • scripts/verify/tests/coverage-include.Tests.ps1#L51-L70: replace Get-CoverageStats's loop body with a call to the same shared helper, keeping this file's probe-specific matching on top.

As per coding guidelines, "脚本必须放在 scripts/verify/、scripts/dev/、scripts/release/、scripts/smoke/ 或 scripts/lib/", which designates scripts/lib/ as the location for shared script logic such as this.

🤖 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 `@scripts/verify/verify-coverage-baseline.ps1` around lines 176 - 186, Extract
the shared uncovered-production-file rule into a helper under scripts/lib/,
counting files whose lines.total is greater than zero and lines.pct equals 0.0.
In scripts/verify/verify-coverage-baseline.ps1 lines 176-186, replace the inline
counting loop with the helper call; in
scripts/verify/tests/coverage-include.Tests.ps1 lines 51-70, update
Get-CoverageStats to use the same helper while preserving its probe-specific
matching.

Source: Coding guidelines

.github/workflows/checks.yml (1)

923-931: 🚀 Performance & Scalability | 🔵 Trivial

Two full agenthub-mobile-rn coverage runs execute back-to-back in the same job.

Verify coverage baseline (Line 925) and Self-test coverage include contract (negative) (Line 931) both run the full 339-test agenthub-mobile-rn suite with coverage instrumentation, unconditionally, in the same validate job. This is an accepted tradeoff per the adjacent comment ("Cheap — mobile only"), but confirm the added wall-clock cost on every PR (including PRs that touch neither app/mobile-rn nor app/test-config/coverage.ts) is acceptable, since validate does not appear to be path-filtered.

🤖 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 @.github/workflows/checks.yml around lines 923 - 931, Update the validate
workflow around “Verify coverage baseline” and “Self-test coverage include
contract (negative)” so the full mobile coverage suite is not executed twice
unconditionally on every pull request. Reuse the existing coverage run or gate
the negative self-test to the relevant mobile/coverage configuration changes
while preserving the required baseline verification and negative
include-contract validation.
🤖 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 `@scripts/verify/coverage-baseline.json`:
- Around line 4-5: Update the masterSha field in coverage-baseline.json to the
correct 40-character commit SHA corresponding to masterShaShort 1a9f3774,
preserving the existing baseline provenance fields.

In `@scripts/verify/verify-coverage-baseline.ps1`:
- Around line 187-195: Update the uncovered-files validation around
$pkg.uncoveredFiles to fail the gate when the baseline field is missing, instead
of skipping the comparison. Add a failure message identifying $pkgFilter and the
missing uncoveredFiles baseline, while retaining the existing growth check when
the field is present.

---

Nitpick comments:
In @.github/workflows/checks.yml:
- Around line 923-931: Update the validate workflow around “Verify coverage
baseline” and “Self-test coverage include contract (negative)” so the full
mobile coverage suite is not executed twice unconditionally on every pull
request. Reuse the existing coverage run or gate the negative self-test to the
relevant mobile/coverage configuration changes while preserving the required
baseline verification and negative include-contract validation.

In `@scripts/verify/verify-coverage-baseline.ps1`:
- Around line 176-186: Extract the shared uncovered-production-file rule into a
helper under scripts/lib/, counting files whose lines.total is greater than zero
and lines.pct equals 0.0. In scripts/verify/verify-coverage-baseline.ps1 lines
176-186, replace the inline counting loop with the helper call; in
scripts/verify/tests/coverage-include.Tests.ps1 lines 51-70, update
Get-CoverageStats to use the same helper while preserving its probe-specific
matching.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 686d0369-2988-40ab-a21a-c20ba735cd94

📥 Commits

Reviewing files that changed from the base of the PR and between 71b39b2 and a672d94.

📒 Files selected for processing (11)
  • .github/workflows/checks.yml
  • app/desktop/vitest.desktop-ci.config.ts
  • app/mobile-rn/vitest.config.ts
  • app/shared/vitest.config.ts
  • app/test-config/coverage.ts
  • app/web/vitest.config.ts
  • docs/progress/MASTER.md
  • scripts/verify/coverage-baseline.json
  • scripts/verify/tests/coverage-include.Tests.ps1
  • scripts/verify/verify-ci-gates.ps1
  • scripts/verify/verify-coverage-baseline.ps1

Comment thread scripts/verify/coverage-baseline.json
Comment on lines +187 to +195
if ($productionFiles -eq 0) {
$failures.Add("[$pkgFilter] coverage include matched 0 production files — include glob broken (fail-closed)")
}
if ($null -ne $pkg.uncoveredFiles) {
$baseUncovered = [int]$pkg.uncoveredFiles
if ($uncoveredFiles -gt $baseUncovered) {
$failures.Add("[$pkgFilter] uncovered (0%) production modules grew: $uncoveredFiles > baseline $baseUncovered (new untested code or deleted test)")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing uncoveredFiles baseline field silently disables the ratchet.

The production_files == 0 check fails closed (Line 187-189). The uncoveredFiles ratchet does not: Line 190 only runs the comparison if ($null -ne $pkg.uncoveredFiles). If a package's baseline entry ever loses this field, the gate silently stops enforcing the uncovered-file ratchet for that package instead of failing.

This contradicts the script's own stated policy (Lines 12-16: "production_files == 0 ... is a failure") and the sibling self-test coverage-include.Tests.ps1, which throws when uncoveredFiles is missing (Line 47 of that file). Fail the gate when the field is absent, to keep both scripts fail-closed for the same contract.

🛡️ Proposed fix to fail closed on a missing baseline field
-        if ($null -ne $pkg.uncoveredFiles) {
-            $baseUncovered = [int]$pkg.uncoveredFiles
-            if ($uncoveredFiles -gt $baseUncovered) {
-                $failures.Add("[$pkgFilter] uncovered (0%) production modules grew: $uncoveredFiles > baseline $baseUncovered (new untested code or deleted test)")
-            }
-        }
+        if ($null -eq $pkg.uncoveredFiles) {
+            $failures.Add("[$pkgFilter] baseline entry is missing 'uncoveredFiles' — ratchet cannot be enforced (fail-closed)")
+        } else {
+            $baseUncovered = [int]$pkg.uncoveredFiles
+            if ($uncoveredFiles -gt $baseUncovered) {
+                $failures.Add("[$pkgFilter] uncovered (0%) production modules grew: $uncoveredFiles > baseline $baseUncovered (new untested code or deleted test)")
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($productionFiles -eq 0) {
$failures.Add("[$pkgFilter] coverage include matched 0 production files — include glob broken (fail-closed)")
}
if ($null -ne $pkg.uncoveredFiles) {
$baseUncovered = [int]$pkg.uncoveredFiles
if ($uncoveredFiles -gt $baseUncovered) {
$failures.Add("[$pkgFilter] uncovered (0%) production modules grew: $uncoveredFiles > baseline $baseUncovered (new untested code or deleted test)")
}
}
if ($productionFiles -eq 0) {
$failures.Add("[$pkgFilter] coverage include matched 0 production files — include glob broken (fail-closed)")
}
if ($null -eq $pkg.uncoveredFiles) {
$failures.Add("[$pkgFilter] baseline entry is missing 'uncoveredFiles' — ratchet cannot be enforced (fail-closed)")
} else {
$baseUncovered = [int]$pkg.uncoveredFiles
if ($uncoveredFiles -gt $baseUncovered) {
$failures.Add("[$pkgFilter] uncovered (0%) production modules grew: $uncoveredFiles > baseline $baseUncovered (new untested code or deleted test)")
}
}
🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)

[warning] Missing BOM encoding for non-ASCII encoded file 'verify-coverage-baseline.ps1'

(PSUseBOMForUnicodeEncodedFile)

🤖 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 `@scripts/verify/verify-coverage-baseline.ps1` around lines 187 - 195, Update
the uncovered-files validation around $pkg.uncoveredFiles to fail the gate when
the baseline field is missing, instead of skipping the comparison. Add a failure
message identifying $pkgFilter and the missing uncoveredFiles baseline, while
retaining the existing growth check when the field is present.

Codex (Delicious233) added 4 commits August 3, 2026 04:51
New app/test-config/coverage.ts factory: include all production
src/**/*.ts(x) in the coverage denominator for all four packages. Before
this, web/desktop/mobile had no coverage.include, so v8 only counted files
loaded by tests — unimported production modules vanished from the
denominator and coverage looked great while modules were untested.

- factory defaults exclude only test categories (spec/__tests__/__e2e__);
  narrow per-package exclusions need explicit reasons (web/desktop main.tsx
  pure entry, shared keeps its 5 legacy file exclusions)
- shared config rewritten via factory; numbers byte-identical to previous
  baseline (76.43/74.82/74.01/70.48) — behavior-equivalent refactor
- absolute floors per package: shared 60/60/60/60 (kept), web 63/62/53/57,
  desktop 48/46/40/39, mobile 35/34/26/21 (floor(measured)-1, measured
  2026-08-03 with full denominator)
- uncovered modules now visible: web 5, desktop 46, mobile 25 0%-line
  production files (was: invisible)
verify-coverage-baseline.ps1 now reports production_files / uncovered_files
per package (Issue format) and fails when:
- production_files == 0 (include glob broken — fail-closed)
- uncovered_files grows past baseline.uncoveredFiles (new untested code or
  deleted test both trip the 0% ratchet)

coverage-include.Tests.ps1 (negative self-test, runs in validate job):
creates an imported-by-nobody probe module, proves coverage-summary counts
it as 0% (not silently excluded), proves the ratchet would trip
(uncovered 26 > baseline 25), then cleans up.

Baseline remeasured 2026-08-03 on master 1a9f377: shared 76.43 (unchanged),
web 64.69/63.66/54.11/58.75, desktop 49.29/47.44/41.79/40.96, mobile
36.19/35.81/27.82/22.72 + uncoveredFiles + improvement targets (all dims
toward 60, recorded in baseline note).
checks.yml:
- remove dead 'Shared coverage (60% thresholds)' step from frontend-desktop
  (fully redundant with the validate job's coverage baseline gate)
- mobile job: drop 'Install coverage provider' (workspace-root hoisted
  provider) and CLI threshold overrides (floors now live in config)
- validate job: add negative self-test step
verify-ci-gates.ps1 asserts the new gate + self-test steps exist.
Get-StepBlock anchors on '- name: <exact>' — the step is named
'Self-test coverage include contract (negative)'.
@DeliciousBuding
DeliciousBuding force-pushed the test/frontend-coverage-truth branch from de97737 to c7dc372 Compare August 2, 2026 20:51
@DeliciousBuding
DeliciousBuding merged commit 5c6091a into master Aug 2, 2026
21 checks passed
@DeliciousBuding
DeliciousBuding deleted the test/frontend-coverage-truth branch August 2, 2026 21:20
DeliciousBuding added a commit that referenced this pull request Aug 4, 2026
DeliciousBuding added a commit that referenced this pull request Aug 11, 2026
…atchet (#1535) (#1552)

* feat(frontend): coverage include contract via shared factory (#1535)

New app/test-config/coverage.ts factory: include all production
src/**/*.ts(x) in the coverage denominator for all four packages. Before
this, web/desktop/mobile had no coverage.include, so v8 only counted files
loaded by tests — unimported production modules vanished from the
denominator and coverage looked great while modules were untested.

- factory defaults exclude only test categories (spec/__tests__/__e2e__);
  narrow per-package exclusions need explicit reasons (web/desktop main.tsx
  pure entry, shared keeps its 5 legacy file exclusions)
- shared config rewritten via factory; numbers byte-identical to previous
  baseline (76.43/74.82/74.01/70.48) — behavior-equivalent refactor
- absolute floors per package: shared 60/60/60/60 (kept), web 63/62/53/57,
  desktop 48/46/40/39, mobile 35/34/26/21 (floor(measured)-1, measured
  2026-08-03 with full denominator)
- uncovered modules now visible: web 5, desktop 46, mobile 25 0%-line
  production files (was: invisible)

* feat(verify): uncovered-files ratchet + negative self-test (#1535)

verify-coverage-baseline.ps1 now reports production_files / uncovered_files
per package (Issue format) and fails when:
- production_files == 0 (include glob broken — fail-closed)
- uncovered_files grows past baseline.uncoveredFiles (new untested code or
  deleted test both trip the 0% ratchet)

coverage-include.Tests.ps1 (negative self-test, runs in validate job):
creates an imported-by-nobody probe module, proves coverage-summary counts
it as 0% (not silently excluded), proves the ratchet would trip
(uncovered 26 > baseline 25), then cleans up.

Baseline remeasured 2026-08-03 on master 1135d58: shared 76.43 (unchanged),
web 64.69/63.66/54.11/58.75, desktop 49.29/47.44/41.79/40.96, mobile
36.19/35.81/27.82/22.72 + uncoveredFiles + improvement targets (all dims
toward 60, recorded in baseline note).

* ci: coverage lane sync + MASTER record (#1535)

checks.yml:
- remove dead 'Shared coverage (60% thresholds)' step from frontend-desktop
  (fully redundant with the validate job's coverage baseline gate)
- mobile job: drop 'Install coverage provider' (workspace-root hoisted
  provider) and CLI threshold overrides (floors now live in config)
- validate job: add negative self-test step
verify-ci-gates.ps1 asserts the new gate + self-test steps exist.

* fix(verify): exact step name for coverage self-test assertion (#1535)

Get-StepBlock anchors on '- name: <exact>' — the step is named
'Self-test coverage include contract (negative)'.

---------

Co-authored-by: Codex <codex@vectorcontrol.tech>
DeliciousBuding added a commit that referenced this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants