ci: Phase 1 test quality — reusable Go + Node quality workflows#55
ci: Phase 1 test quality — reusable Go + Node quality workflows#55lml2468 wants to merge 5 commits into
Conversation
Phase 1 of test quality CI improvements: - reusable-go-quality.yml: gotestsum + JUnit XML + coverage + octocov diff coverage + time.Sleep test lint - reusable-node-quality.yml: typecheck + lint + test (optional) + build for pnpm/npm projects These reusable workflows will be called by individual repo CI pipelines.
Jerry-Xin
left a comment
There was a problem hiding this comment.
The PR is in scope for this repository, but the Go quality workflow has false-green coverage gates that should be fixed before merge.
🔴 Blocking
-
🔴 Critical — Core package coverage threshold is not enforced. In
.github/workflows/reusable-go-quality.yml:369-380, packages belowinputs.core-coverage-thresholdonly write a warning to$GITHUB_STEP_SUMMARY; the step never exits non-zero. That means callers can configure a required core coverage threshold and still get a successful workflow when it is violated. -
🔴 Critical —
coverage-excludeis computed but never applied to octocov..github/workflows/reusable-go-quality.yml:313-321buildsEXCLUDES, but.github/workflows/reusable-go-quality.yml:323-337never inserts it into.octocov.yml. As a result,*_mock.go,*.pb.go, generated files,vendor/, andmigrations/are still included despite the advertised input/defaults.
💬 Non-blocking
-
🟡 Warning — The core package coverage matching is likely unreliable for inputs like
./modules/user/.....github/workflows/reusable-go-quality.yml:361-366grepsgo tool cover -funcoutput using the literal input path, but coverage output usually contains module/import-style file paths, not./...patterns. The fallback also averages percentages rather than calculating weighted coverage. -
🟡 Warning — In
.github/workflows/reusable-node-quality.yml:52-64and repeated jobs,working-directoryis applied to shell commands butactions/setup-nodecache has nocache-dependency-path. For subdirectory Node projects, setup-node may look for the lockfile at the repo root and fail or miss caching. Add a dependency path derived frominputs.working-directory.
✅ Highlights
- The PR adds relevant reusable workflows under
.github/workflows, matching the repository’s purpose. - The Go workflow’s split service/non-service jobs are a reasonable workaround for conditional service containers.
- Artifacts are uploaded with
if: always(), which is useful for failed test debugging.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #55 (.github)
Independent review of the two new reusable workflows (reusable-go-quality.yml, reusable-node-quality.yml). The intent is solid — gotestsum + JUnit/coverage artifacts, diff coverage via octocov, and a Node typecheck/lint/test/build pipeline are all reasonable additions for org-wide CI. However there are blocking correctness issues plus several deviations from this repo's established workflow conventions.
Verdict: CHANGES_REQUESTED.
1. Verification summary
⚠️ Repo's ownactionlintstatus check is failing on this PR's head SHA — see P0-1.- ❌ Core-package coverage threshold is advertised but never enforced — P1-1.
- ❌
coverage-excludeinput is computed but never applied — P1-2. - ❌
gotestsum tool mergeis not a real command — silent fallback corrupts the summary/artifact — P1-3. ⚠️ Action references are unpinned (@v4/@v5), contrary to the repo-wide SHA-pinning convention — P2-1.⚠️ Broad top-levelpermissions:instead of per-job least privilege — P2-2.
2. Findings
🔴 P0-1 — The repo's actionlint check fails on this PR (hard merge blocker)
The org runs workflow-sanity.yml (actionlint + shellcheck) on every PR touching .github/workflows/**, and it treats shellcheck findings as errors (exit 1). This PR's actionlint check is currently red:
reusable-go-quality.yml:158—SC2012:first_xml=$(ls junit-*.xml ... | head -1)— usefindinstead oflsfor non-alphanumeric filenames.reusable-go-quality.yml:170-173—SC2129: the fourecho ... >> "$GITHUB_OUTPUT"lines should be grouped as{ ...; } >> "$GITHUB_OUTPUT".reusable-go-quality.yml:250-252—SC2129: same pattern in the standalone job.reusable-go-quality.yml:371—SC2001:pkg_base=$(echo "$pkg" | sed 's|/\.\.\.$||')— use${pkg%/...}/ parameter expansion instead.
Because actionlint is a required-style gate here, the PR cannot pass CI until these are resolved. None are hard to fix, but they must be fixed.
🔴 P1-1 — Core-package coverage threshold is never enforced (false green)
reusable-go-quality.yml:375-386: when a core package is below core-coverage-threshold, the step only sets all_pass=false and appends a warning to $GITHUB_STEP_SUMMARY. It never exit 1s. A caller can configure a required core threshold, fall below it, and still get a green workflow. Either fail the step on all_pass=false, or document explicitly that this is advisory-only (the input name core-coverage-threshold strongly implies enforcement).
🔴 P1-2 — coverage-exclude is computed but dropped
reusable-go-quality.yml:320-327 builds $EXCLUDES from the input, but the .octocov.yml heredoc at lines 329-343 never references it. As a result the documented defaults (mock, pb.go, _gen.go, vendor, migrations) are silently ignored — generated/vendored files still count toward coverage, skewing the gate. The exclude list needs to be written into the octocov config (e.g. under coverage.paths/exclude, per octocov's schema), and verify the generated YAML is valid (note $EXCLUDES is assembled with literal \n, which won't expand inside a quoted heredoc).
🔴 P1-3 — gotestsum tool merge does not exist
reusable-go-quality.yml:156: gotestsum tool merge junit-*.xml > junit.xml. gotestsum tool only supports slowest and ci-matrix — there is no merge subcommand (verified against gotestsum's CLI). The call always fails, silently triggers the fallback at lines 157-160, and junit.xml ends up containing only the first package's results. Downstream effects:
- The PR summary counts (lines 165-167, parsed from
junit.xmlwithhead -1) reflect a single package, not the whole run — misleading "Passed/Failed" numbers. - The uploaded merged
junit.xmlartifact is incomplete (the per-packagejunit-*.xmlare still uploaded, so this is recoverable but the named merged file is wrong).
The pass/fail gate itself is safe (it uses the accumulated $fail, not the XML), so this is functional-but-wrong rather than a gate bypass. Replace with a real merge tool (e.g. junit-merge, or have gotestsum write a single junit by running once over ./... where DB-reset isn't needed) or parse counts by summing across junit-*.xml.
🟡 P2-1 — Actions are unpinned, against repo convention
Every existing workflow in this repo SHA-pins actions with a version comment (e.g. actions/checkout@de0fac2e... # v6.0.2, CodeQL/labeler/stale all pinned). This PR uses floating tags: actions/checkout@v4, actions/setup-go@v5, actions/setup-node@v4, pnpm/action-setup@v4, actions/upload-artifact@v4, actions/download-artifact@v4, plus third-party k1LoW/octocov-action@v1 and davelosert/vitest-coverage-report-action@v2. For reusable workflows consumed org-wide this is a supply-chain regression — please pin to commit SHAs to match the rest of the repo. Likewise go install gotest.tools/gotestsum@latest (lines 100, 231) is non-reproducible; pin a version.
🟡 P2-2 — Top-level permissions: is broader than needed
Both files set permissions: { contents: read, pull-requests: write } at the top, granting pull-requests: write to every job — including the Go/Node test, typecheck, lint, and build jobs that only need contents: read. The repo convention is top-level permissions: {} with per-job least privilege (see reusable-codeql.yml, reusable-stale.yml). Only the octocov diff-coverage job actually needs PR write. Please scope pull-requests: write to that job.
🟡 P2-3 — Core-package coverage matching is unreliable
reusable-go-quality.yml:367-372: it greps go tool cover -func output with the literal input (./modules/user/...), but cover output uses module/import-style file paths, so the primary match will usually miss and fall through to the broad fallback, which averages per-file percentages rather than computing statement-weighted coverage. Even once P1-1 is fixed to enforce, the number being compared may be inaccurate. Consider computing weighted coverage from the profile.
🟡 P2-4 — Node setup-node cache may miss for subdir projects
reusable-node-quality.yml: working-directory is applied to the run steps, but actions/setup-node has no cache-dependency-path. For a working-directory other than ., setup-node resolves the lockfile from the repo root and will either fail to cache or warn. Pass cache-dependency-path: ${{ inputs.working-directory }}/<lockfile>.
🟢 Nits / maintainability
reusable-node-quality.yml:581hardcodesdavelosert/vitest-coverage-report-action, which assumes vitest output; projects on jest/other runners withhas-tests: truewill break. Worth documenting the vitest assumption.pnpm test --coverage(line 574) relies on pnpm forwarding the flag to the underlying script; confirm behavior, or usepnpm test -- --coveragefor parity with the npm branch.
3. Recommendation
Address the P0 (fix the four shellcheck findings so actionlint goes green) and the three P1 correctness bugs (enforce or relabel core threshold, actually apply coverage-exclude, fix the non-existent gotestsum tool merge). The P2 convention items (SHA-pin actions, scope permissions per-job) should be fixed before these reusable workflows are adopted org-wide, since every consuming repo inherits them.
4. Additional notes
- The split
test-with-services/test-standalonejobs plus thealways() && (...success)gate ondiff-coverageare correct given GitHub Actions has no conditionalservices:block — good workaround. - Uploading artifacts with
if: always()for failure debugging is a nice touch.
P0 — shellcheck/actionlint fixes:
- SC2012: replace `ls | head` with `find` for JUnit XML fallback
- SC2129: group multiple `echo >> GITHUB_OUTPUT` into single block
- SC2001: replace `sed` with parameter expansion for pkg_base
P1-1: core package coverage now enforced with `exit 1`
P1-2: coverage-exclude patterns now written into .octocov.yml exclude section
P1-3: replace non-existent `gotestsum tool merge` with Python XML merger
P2-1: all actions pinned to commit SHA (actions/checkout, setup-go,
setup-node, pnpm/action-setup, upload-artifact, download-artifact,
k1LoW/octocov-action, davelosert/vitest-coverage-report-action)
P2-2: permissions moved from top-level to per-job; top-level set to `{}`
only diff-coverage and test (node, for coverage report) get pull-requests: write
🔧 Review Feedback Addressed —
|
| Issue | 修复方式 |
|---|---|
SC2012 — ls junit-*.xml | head -1 |
已删除,JUnit 合并改用 Python 脚本(见 P1-3),不再需要 ls fallback |
SC2129 — 多个 echo >> "$GITHUB_OUTPUT" |
合并为 { ... } >> "$GITHUB_OUTPUT" 分组写入(test-with-services 和 test-standalone 两处) |
SC2001 — sed 's|/\.\.\.\$||' |
改用 bash 参数展开 pkg_base="${pkg%/...}" |
P1-1:core package 覆盖率门槛 enforce
当 all_pass=false 时现在会 exit 1,并在 Step Summary 输出 ❌ 提示。
P1-2:coverage-exclude 写入 octocov 配置
重写了 .octocov.yml 生成逻辑:
- 不再使用 heredoc(避免变量展开问题)
- 改用
{ echo ... } > .octocov.yml逐行拼接 inputs.coverage-exclude的 glob 片段现在会生成coverage.exclude列表- 目录型 pattern(以
/结尾)→"**/dir**",文件型 →"**/*suffix"
P1-3:gotestsum tool merge → Python XML merger
gotestsum 没有 merge 子命令。改用内联 Python3 脚本(ubuntu-latest 自带):
- 合并所有
junit-*.xml为junit.xml - 自动 recompute
tests/failures/skipped汇总属性 - 无额外依赖安装
P2-1:所有 action 已 SHA pin
| Action | SHA | Tag |
|---|---|---|
actions/checkout |
34e11487... |
v4 |
actions/setup-go |
40f1582b... |
v5 |
actions/setup-node |
49933ea5... |
v4 |
pnpm/action-setup |
b906affc... |
v4 |
actions/upload-artifact |
ea165f8d... |
v4 |
actions/download-artifact |
d3f86a10... |
v4 |
k1LoW/octocov-action |
b3b6ee60... |
v1 |
davelosert/vitest-coverage-report-action |
02f3c2e6... |
v2 |
P2-2:permissions 最小化
- 顶层
permissions: {}(两个文件) - 各 job 按需声明:
test-with-services/test-standalone/test-lint/build→contents: readdiff-coverage(Go) /test(Node, 需要 coverage report) →contents: read+pull-requests: write
请 re-review,谢谢!🙏
Jerry-Xin
left a comment
There was a problem hiding this comment.
This PR is in scope for Mininglamp-OSS/.github, but two workflow correctness issues need fixing before merge.
🔴 Blocking
-
🔴 Critical — Core package coverage checks do not work with the documented
./modules/user/...input. In.github/workflows/reusable-go-quality.yml:405-410, the script grepsgo tool cover -func=coverage.outoutput using the raw input path. Go coverage output uses module import paths likeexample.com/repo/modules/user/file.go, sogrep "^./modules/user/..."and fallbackgrep "./modules/user"will not match. Any caller settingcore-packages: './modules/user/...'will likely get0coverage and fail incorrectly. Normalize./foo/...into a path fragment such as/foo/or compute coverage directly fromcoverage.outfilenames. -
🔴 Critical —
working-directorybreaks Node dependency caching for subdirectory projects. In.github/workflows/reusable-node-quality.yml:52-64and repeated in the other jobs,defaults.run.working-directoryonly affects shell steps, notactions/setup-node. Withcache: pnpmorcache: npm,setup-nodestill looks for the lockfile at the repository root unlesscache-dependency-pathis set. A valid caller usingworking-directory: frontendwill fail before install if the lockfile is underfrontend/. Addcache-dependency-path: ${{ inputs.working-directory }}/pnpm-lock.yamlor the npm equivalent, preferably with separate setup steps per package manager.
💬 Non-blocking
-
🟡 Warning —
.github/workflows/reusable-go-quality.yml:185-193stores total tests astest_passed, so summaries can show “passed” equal to total tests even when failures or skips exist. Compute passed astests - failures - skipped. -
🟡 Warning —
.github/workflows/reusable-go-quality.yml:93-94installsgotestsum@latest, which makes the org-wide reusable workflow sensitive to upstream releases. Pin a known version.
✅ Highlights
- Actions are pinned by SHA.
- The workflow keeps artifact upload under
if: always(). - The relevance and structure match the existing reusable workflow approach in this repository.
Review: .github PR #55 (re-review)Verdict: CHANGES_REQUESTED Previous Blocking Items — Resolution Status
New Findings
SecurityThe SHA pinning of |
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #55 (.github)
Independent re-review of the two new reusable workflows (reusable-go-quality.yml, reusable-node-quality.yml) at head 8a6c3837. The previous round's blockers were addressed well: actionlint is green again, all actions are SHA-pinned, top-level permissions: {} with per-job scoping is in place, the non-existent gotestsum tool merge was replaced with a real Python merge that correctly aggregates counts, the core-threshold step now exit 1s, and coverage-exclude is now written into .octocov.yml. Nice work on those.
Two correctness gaps remain that are worth one more cycle before this is adopted org-wide, since every consuming repo inherits them.
Verdict: CHANGES_REQUESTED.
1. Verification summary
- ✅
actionlint/ workflow-sanity checks pass on the head SHA. - ✅ Python JUnit merge aggregates
tests/failures/skippedacross all per-package suites correctly; heredoc indentation is valid (noIndentationError); thehead -1parse now reads the aggregated root, not the first package. Prior P1 fixed. - ✅ Actions SHA-pinned,
permissionsscoped per-job. Prior P2s fixed. - ❌
coverage-excludeis now wired into octocov, but the generated glob is wrong for 3 of the 5 advertised defaults — P1-1. ⚠️ Core-package coverage check now gates (exit 1) on a number derived from unreliable matching — P1-2 (opt-in).- 🟡
gotestsum@latest, Nodecache-dependency-path, vitest-only coverage action — P2/nits.
2. Findings
🔴 P1-1 — coverage-exclude default patterns don't exclude mock / vendor / migrations
reusable-go-quality.yml:354-359: each non-/-terminated fragment p is turned into the glob **/*<p>. octocov's coverage.exclude uses doublestar semantics where * does not cross /, so **/*<p> only matches when the final path segment ends with the literal suffix <p>.
Against the documented default coverage-exclude: 'mock,pb.go,_gen.go,vendor,migrations':
| Fragment | Generated pattern | Intended target | Result |
|---|---|---|---|
mock |
**/*mock |
internal/mocks/user_mock.go |
❌ last segment ends .go, not mock |
pb.go |
**/*pb.go |
api/foo.pb.go |
✅ true filename suffix |
_gen.go |
**/*_gen.go |
x/y_gen.go |
✅ true filename suffix |
vendor |
**/*vendor |
vendor/x/y.go |
❌ last segment y.go |
migrations |
**/*migrations |
db/migrations/001_init.go |
❌ last segment 001_init.go |
So only the two genuine filename suffixes work; the directory-style fragments (vendor, migrations) and the most impactful one — generated *_mock.go mocks — are silently not excluded. octocov matches both git-root-relative paths and Go module paths, but in both cases the trailing segment is the filename, so the conclusion is the same.
Practical impact: the PR description lists these excludes as a delivered feature, but generated mock files (typically 0% coverage) still count toward the gate, skewing overall coverage downward and making coverage.acceptable more likely to false-RED for adopting repos. (vendor is low-impact in practice since go test ./... already skips vendor/, but mock and migrations matter.)
Suggested fix — distinguish filename-suffix fragments from directory fragments, e.g.:
- directory names →
**/<name>/**(and/or<name>/**) - bare tokens meant to match filenames →
**/*<token>*.go(somock→**/*mock*.go) - keep explicit
.gosuffixes (pb.go,_gen.go) as**/*<suffix>
Whatever the mapping, please add a comment documenting the expected fragment forms and verify the generated .octocov.yml actually drops the intended files.
🔴 P1-2 — Core-package coverage check gates on an unreliable number (opt-in path)
reusable-go-quality.yml:405-425: the primary match grep "^${pkg}" uses the raw input pattern (e.g. ./modules/user/...), but go tool cover -func output lines start with module/import-style file paths, so ^./modules/... never matches and execution always falls through to the broader branch. The fallback grep "${pkg_base}" then averages per-function percentages (sum+=$NF; n++ … sum/n) rather than computing statement-weighted coverage. Now that this step does exit 1 (good — that was the prior fix), it is gating on an inaccurate figure, which can both false-pass and false-fail the core gate.
This is gated behind core-packages (default ''), so it only bites callers who opt in — but those are exactly the repos that asked for a stricter core gate, so the number needs to be trustworthy. Consider computing weighted coverage from the profile (e.g. parse coverage.out statement counts per package, or use octocov's own per-path reporting) instead of averaging -func rows. Also note grep "${pkg_base}" treats . as a regex wildcard; anchor/escape it.
3. Recommendation
Address P1-1 (the exclude defaults are the headline feature and 3/5 silently don't work) and P1-2 (don't enforce on an inaccurate core-coverage number). Both are small, localized changes. The P2 items below are non-blocking but worth folding in since this is consumed org-wide.
4. Non-blocking (P2 / nits)
- 🟡
reusable-go-quality.yml:94,255—go install gotest.tools/gotestsum@latestis non-reproducible; pin a version to match the repo's SHA-pinning convention for the rest of the toolchain. - 🟡
reusable-node-quality.yml—actions/setup-nodeenablescache:but has nocache-dependency-path. Forworking-directory != '.', setup-node resolves the lockfile from the repo root and will warn or miss the cache. Passcache-dependency-path: ${{ inputs.working-directory }}/<lockfile>. - 🟢
reusable-node-quality.ymlcoverage step hardcodesdavelosert/vitest-coverage-report-action; projects using jest/other runners withhas-tests: truewill not get a report. Worth documenting the vitest assumption. - 🟢
pnpm test --coveragerelies on pnpm forwarding the flag to the script;pnpm test -- --coveragewould be more robust and match the npm branch.
5. Additional notes
- The split
test-with-services/test-standalonejobs plus thealways() && (…success)gate ondiff-coverageremain a correct workaround for GitHub Actions' lack of conditionalservices:blocks. - Per-package DB reset (DROP/CREATE +
FLUSHALL) before each package is a sound isolation pattern for the service-backed runs. - Uploading artifacts with
if: always()is a good touch for failure debugging.
B2: Fix go list false-green by capturing output to temp file B3: Rewrite core packages coverage with Python parser B4: Add quality-gate job to both Go and Node workflows B5: Fix inputs injection risk via env variables (Node workflow) W1: Disable PR comment for fork PRs in octocov config W2: Fix coverage-exclude glob patterns (both dir and suffix) W9: Pin gotestsum to v1.12.0 W12: Add cache-dependency-path to all setup-node steps Nit-1: Fix Go summary Passed count calculation Nit-3: Add table header to core coverage summary Nit-4: Remove hardcoded OCTO_MASTER_KEY
🔧 Review 修复完成 (commit
|
| ID | 问题 | 修复方案 |
|---|---|---|
| B2 | go list 假绿 — 进程替换失败被静默吞掉 |
改为先写入临时文件,检查 go list 退出码和文件非空,再循环读取 |
| B3 | core packages coverage 用 bash 解析 go tool cover 输出不准确 |
完全重写为 Python 脚本,直接解析 coverage.out 的 stmts/count,用 go list 展开 pattern 到 import paths |
| B4 | 缺少 quality-gate job,branch protection 无稳定 check name | Go 和 Node workflow 各添加 quality-gate job(if: always()),聚合所有子 job 结果 |
| B5 | Node workflow 中 ${{ inputs.package-manager }} 直接注入 shell |
全部改为先设 env: PACKAGE_MANAGER,shell 里读 $PACKAGE_MANAGER,并加 allowlist 校验 |
Warning 修复
| ID | 问题 | 修复方案 |
|---|---|---|
| W1 | fork PR 的 octocov comment 会因权限不足报错 | 添加 fork PR 检测逻辑,fork 时自动 comment.enable: false |
| W2 | coverage-exclude glob 模式不够准确 | 每个 pattern 同时生成目录 glob **/${p}/** 和文件后缀 glob **/*${p} |
| W9 | gotestsum@latest 不确定性 |
固定为 gotestsum@v1.12.0 |
| W12 | Node setup-node 缺少 cache-dependency-path |
所有 4 个 job 的 setup-node 都加上 pnpm-lock.yaml 和 package-lock.json 路径 |
Nit 修复
| ID | 问题 | 修复方案 |
|---|---|---|
| Nit-1 | Go summary "Passed" 数字实为总测试数 | 改为 total_pass = total_tests - total_fail - total_skip |
| Nit-3 | core coverage 表格缺少 header | Python 脚本中写入完整 Markdown 表头 |
| Nit-4 | 硬编码 OCTO_MASTER_KEY |
已删除,应由调用方通过 secrets 传入 |
额外改动
- Go workflow 的
Generate octocov configstep 也改用 env 变量读取coverage-exclude和coverage-threshold,避免 inputs 直接注入 shell
Jerry-Xin
left a comment
There was a problem hiding this comment.
The PR is relevant to this .github repository, but the Go coverage workflow has a blocking correctness issue in the generated octocov configuration.
🔴 Blocking
- 🔴 Critical:
.github/workflows/reusable-go-quality.yml:397-.github/workflows/reusable-go-quality.yml:404generates unsupported octocov keys.diff.coverage.acceptable,comment.enable, andsummary.enableare not valid octocov config fields, so the advertised diff coverage gate is effectively not enforced. The fork-PR comment disable logic at.github/workflows/reusable-go-quality.yml:363-.github/workflows/reusable-go-quality.yml:372also does not actually disable comments because it writescomment.enableinstead of using octocov’s supported conditional field. This means the reusable workflow can pass without enforcing the PR’s claimed diff coverage behavior, and may still attempt PR comments when it intended not to.
💬 Non-blocking
-
🟡 Warning:
.github/workflows/reusable-go-quality.yml:44-.github/workflows/reusable-go-quality.yml:48says mocks are excluded, but.github/workflows/reusable-go-quality.yml:381-.github/workflows/reusable-go-quality.yml:383turnsmockinto**/*mock, which does not match normal Go mock files likefoo_mock.go. Consider defaulting to_mock.goor explicitly generating**/*_mock.go. -
🔵 Suggestion:
.github/workflows/reusable-node-quality.yml:83-.github/workflows/reusable-node-quality.yml:84always usesnpx tsc --noEmit, even for pnpm projects. Usingpnpm exec tsc --noEmitfor pnpm andnpm exec tsc -- --noEmitfor npm would better match the selected package manager.
✅ Highlights
- The workflows are correctly scoped to reusable
workflow_calluse and align with this repository’s purpose. - The Go workflow consistently uploads test artifacts with
if: always(), which is useful for failed test diagnosis. - The quality-gate jobs provide stable required-check names for downstream branch protection.
Review: .github PR #55 (R3 re-review)Verdict: CHANGES_REQUESTED Previous Findings — Resolution Status
New Findings
Security AssessmentThe PR is closer, but I cannot approve this round because the Node workflow still grants |
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #55 (.github)
Third-round review of the two reusable workflows (reusable-go-quality.yml, reusable-node-quality.yml) at head ad5d63b. Most of the prior blockers are genuinely resolved this round — nice work. One advertised feature is still partially broken, which is worth one more cycle since these workflows are inherited org-wide.
Verdict: CHANGES_REQUESTED.
1. Verification summary (prior findings)
- ✅
actionlint/ no-tabs / workflow-sanity checks are green on this head SHA. - ✅ Non-existent
gotestsum tool mergereplaced with a Python JUnit merge that aggregatestests/failures/skippedacross all per-package suites correctly. - ✅ Actions SHA-pinned;
gotestsumpinned tov1.12.0. - ✅ Top-level
permissions: {}with per-job least privilege. - ✅ User-controllable string inputs (
coverage-exclude,core-packages,package-manager) are now passed viaenv:and read as shell variables, not interpolated intorun:blocks. - ✅ Core-package coverage check (prior P1) is now correct — rewritten in Python to expand patterns via
go list -f '{{.ImportPath}}'and compute statement-weighted coverage by parsingcoverage.outdirectly (no moregrepon-funcoutput, no more percentage averaging). Verified the derived import path matchesgo listoutput exactly and the parse is crash-safe. - ✅ Node
setup-nodenow setscache-dependency-pathforpnpm-lock.yaml/package-lock.jsonin all jobs (resolves the prior subdirectory-cache blocker). - ✅
coverage-excludeforvendor/,migrations/,*.pb.go,*_gen.gonow work via the newly-added**/<p>/**directory glob +**/*<suffix>form. - ❌
coverage-excludeformockstill does not exclude*_mock.go— see P1 below.
2. Findings
🔴 P1 — mock default in coverage-exclude does not exclude *_mock.go (advertised-but-broken)
reusable-go-quality.yml (Generate octocov config step): for each fragment p, the script emits **/<p>/** and **/*<p>. octocov uses doublestar semantics where * does not cross /. For the default fragment mock this produces:
| Generated glob | Matches internal/mocks/user_mock.go? |
Why |
|---|---|---|
**/mock/** |
❌ | requires a path segment literally named mock; real mock dirs are mocks/ (plural), and the file itself is not a directory segment |
**/*mock |
❌ | requires the final segment to end in the literal mock; user_mock.go ends in .go |
So generated mock files (typically 0% coverage) still count toward the coverage report. The PR description explicitly lists *_mock.go as excluded, so this is a functional + documentation mismatch. The other four defaults (vendor, migrations, pb.go, _gen.go) are now correct — only mock is broken.
Note this does not fail-open a gate (under-excluding inflates the denominator, making thresholds equal-or-harder, never silently passing), so it is P1, not P0. But for org-wide infra it can cause a coverage gate to false-RED for repos with many generated mocks, which is exactly the friction these excludes are meant to remove.
Fix: distinguish filename-suffix fragments from bare tokens, e.g. map mock → **/*mock*.go (or accept that callers must pass complete glob patterns). Keep .go-suffixed fragments (pb.go, _gen.go) as **/*<suffix> and directory names as **/<name>/**. Please verify the generated .octocov.yml actually drops the intended files and add a short comment documenting the expected fragment forms.
3. Non-blocking (P2 / nits)
- 🟡 Core-coverage fail-open —
reusable-go-quality.ymlcore-packages step exits0with only a warning whengo listresolves nothing ortotal_stmts == 0. A typo'dcore-packagesvalue silently disables the gate rather than failing. Consider failing (or at least surfacing a louder signal) whencore-packagesis non-empty but resolves to zero statements. - 🟡 Node
testjob token scope — thetestjob holdspull-requests: writewhile runningpnpm install/pnpm test --coverage(arbitrary project scripts) before the coverage-comment action. The token is PR-write only and GitHub auto-downgrades it to read-only for fork PRs, so this is defense-in-depth, not a hole. Splitting the coverage-comment step into a job that doesn't execute project scripts would be cleaner. - 🟢
reusable-node-quality.ymlhardcodesdavelosert/vitest-coverage-report-action; projects on jest/other runners withhas-tests: truewon't get a report. Worth documenting the vitest assumption. - 🟢
pnpm test --coveragerelies on pnpm forwarding the flag to the script;pnpm test -- --coveragewould match the npm branch and be more robust.
4. Additional notes
- The split
test-with-services/test-standalonejobs + thealways() && (…success)gate ondiff-coverageremain a correct workaround for GitHub Actions' lack of conditionalservices:blocks. The sharedtest-resultsartifact name is safe because the two jobs are mutually exclusive viaif:. - Per-package DB reset (DROP/CREATE +
FLUSHALL) is a sound isolation pattern for the service-backed runs. quality-gateaggregation and artifact upload underif: always()are good touches.
This is close — the single remaining item is a one-line change to the mock default.
…te env hardening - coverage-exclude: distinguish .go suffix / bare token / directory fragments - bare tokens (mock) → **/<token>/** + **/*<token>*.go (correctly matches user_mock.go) - .go suffixes (pb.go, _gen.go) → **/*<suffix> (filename suffix, unchanged) - directory names (vendor, migrations) → **/<name>/** only - Node test job: remove pull-requests:write; split coverage reporting into separate report-coverage job that only downloads artifact and posts comment - Go quality-gate: move inputs.services + needs results out of shell interpolation into env vars (complete the shell injection hardening)
Jerry-Xin
left a comment
There was a problem hiding this comment.
The PR is in scope for this .github repository, but one workflow failure mode should be fixed before merge.
🔴 Blocking
- 🔴 Critical: Fork PRs with Node tests can fail the required quality gate even when typecheck/lint/test/build pass. In
.github/workflows/reusable-node-quality.yml,report-coverageruns on everypull_requestwhenhas-testsis true and requestspull-requests: writeat line 202. For forked PRs, the token is read-only, so the Vitest coverage comment/report action can fail with insufficient permissions. Becausequality-gatetreatsreport-coveragefailures as blocking at lines 305-308, external contributor PRs can be blocked by reporting rather than code quality. The Go workflow already handles this pattern by disabling comments for forks; the Node workflow needs the same fork guard, or the coverage report job should be skipped/allowed to succeed for forks.
💬 Non-blocking
- 🟡 Warning: In
.github/workflows/reusable-go-quality.yml, invalidcore-packagespatterns are silently ignored, and if no packages match the job exits successfully at lines 453-455. That makes a typo disable the intended core coverage gate. Consider failing when a non-emptycore-packagesinput matches nothing.
✅ Highlights
- Actions are pinned by SHA and permissions are scoped per job.
- The Go workflow preserves artifact upload on failure and provides a stable
quality-gatecheck. - The split Go service/non-service jobs are a practical workaround for conditional service containers.
lml2468
left a comment
There was a problem hiding this comment.
Summary since last review:
- Go workflow: the quality-gate shell now reads
inputs.services,needs.*.result, andgithub.event_namethrough environment variables instead of direct expression interpolation. The coverage-exclude glob handling was also refined for Go suffixes, bare tokens, and directory fragments. - Node workflow: test execution now runs with
contents: readonly, uploads coverage as an artifact, and a separatereport-coveragejob owns thepull-requests: writepermission for the PR coverage comment. The Node quality gate now includes that reporting job and reads needs results through environment variables.
Previous findings:
- Resolved: [P1]
reusable-go-quality.ymlquality-gate no longer directly interpolatesinputs.servicesorneeds.*expressions inside the shell logic. - Resolved: [P1]
reusable-node-quality.ymlno longer grantspull-requests: writeto the install/test job; the write permission is isolated to the reporter job after test artifacts are produced.
New findings:
- None blocking. I rechecked run-block expression usage, job permission scopes, pinned third-party actions, and the new artifact handoff. The remaining
${{ }}usages in shell steps are either fixed GitHub metadata, numeric/script-produced outputs, or environment assignments rather than untrusted shell syntax construction.
CI status:
No tabs in workflow files: passactionlint: passcode-review: pending at review time
Final verdict:
- No blocking findings. The prior blockers are resolved and I did not find new blocking security, correctness, or maintainability issues in the incremental changes.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #55 (.github)
Fourth-round review of the two reusable workflows (reusable-go-quality.yml, reusable-node-quality.yml) at head fd96a89. The two items targeted by this round are genuinely fixed — nice work — but two correctness gaps remain, and one of them (an invalid octocov config) was flagged last round and is still present. Since every consuming repo inherits these workflows, the advertised gates need to actually fire before merge.
Verdict: CHANGES_REQUESTED.
1. Verification summary
- ✅ Fixed —
mockexclude default now works.reusable-go-quality.yml"Generate octocov config" step maps the bare tokenmockto both**/mock/**and**/*mock*.go, so generated files likeinternal/mocks/user_mock.goare now excluded. The three-way fragment branch (*.gosuffix / bare token / dotted name) is correct and documented. - ✅ Fixed — Node privilege isolation. The
testjob is nowcontents: readonly, and coverage commenting moved to a dedicatedreport-coveragejob (pull-requests: write) that runs only checkout + download-artifact + the pinned vitest-coverage-report-action — no project install/test/build. Untrusted project scripts no longer execute with a PR-write token. - ✅
actionlint/ no-tabs / workflow-sanity checks are green on this head SHA; alluses:are SHA-pinned;gotestsumpinned tov1.12.0; shell inputs read viaenv:. - ❌ Still broken —
.octocov.ymluses invalid config keys, so the diff-coverage threshold and the fork comment-disable are silently no-ops — P1-1 (flagged in R3, not addressed). - ❌ New / unresolved — the core-package coverage gate fails open on its two most likely misconfigurations — P1-2.
2. Findings
🔴 P1-1 — Generated .octocov.yml uses unsupported keys → diff-coverage threshold is not enforced
reusable-go-quality.yml, "Generate octocov config" step (the heredoc that writes .octocov.yml, ~lines 400-419):
diff:
coverage:
acceptable: ${COVERAGE_THRESHOLD}% # ❌ diff.coverage.acceptable is not a valid octocov key
comment:
enable: $COMMENT_ENABLE # ❌ comment.enable does not exist
summary:
enable: true # ❌ summary.enable does not existVerified against octocov's config schema (k1LoW/octocov, config/config.go):
Coveragesupportsacceptable— socoverage.acceptable: 60%(line ~406) is valid and the overall-coverage gate works.CommentsupportshideFooterLink,deletePrevious,updatePrevious,message,if— noenable. Conditional commenting usescomment.if:.SummarysupportshideFooterLink,message,if— noenable.Diffsupportspath,datastores,if— nocoveragesubkey. Diff/PR-coverage thresholds are expressed insidecoverage.acceptablevia thediffexpression variable, e.g.acceptable: "current >= 60% && diff >= 0.5%".
octocov unmarshals its config without strict mode, so these unknown keys are silently ignored rather than erroring — the job stays green. Concrete impact:
- The diff-coverage threshold is not applied. The "Diff Coverage" job only enforces the simple overall
coverage.acceptable: 60%. The PR description advertises a configurable diff-coverage gate; as written, changed-line coverage is not gated at all. - The fork-PR comment suppression is dead config. The
IS_FORKlogic computesCOMMENT_ENABLE=falseand writescomment.enable, which octocov ignores — so the intended "don't comment on fork PRs" behavior never takes effect.
Fix: drop the diff.coverage.acceptable / comment.enable / summary.enable keys. To gate diff coverage, fold it into coverage.acceptable as an expression (current >= <overall>% && diff >= <diff>%); to suppress comments on forks, use comment.if: with a condition (e.g. is_pull_request && env.IS_FORK != 'true') rather than a boolean. Please confirm the generated .octocov.yml actually enforces the intended diff threshold after the change.
🔴 P1-2 — Core-package coverage gate fails open on misconfiguration / build break
reusable-go-quality.yml, "Core packages coverage check" Python step (~lines 427-513). This step is the only mechanism enforcing core-coverage-threshold (default 75%), and it exits 0 in two failure modes:
go listreturns non-zero (~lines 443-455): the result is only consumed underif result.returncode == 0:. Ifgo listfails — a build break in/under a core package, or a typo'dcore-packagespattern —core_import_pathsstays empty, the code hitsif not core_import_paths: ... sys.exit(0), and the gate passes without checking anything.total_stmts == 0(~lines 496-500): if a pattern resolves but nocoverage.outline matches the import paths, the step prints a warning andsys.exit(0).
In both cases the root cause is the same: "couldn't measure" is treated as "passed." On org-wide infra this means any repo silently loses its core-coverage gate the moment a core pattern is mistyped or a core package fails to build — exactly when the gate should bite. Fix: fail closed when core-packages is non-empty — exit 1 (with ::error::) on non-zero go list, and on total_stmts == 0.
3. Recommendation
Both blockers are small, localized changes:
- Replace the three invalid octocov keys so the diff-coverage threshold and fork comment-disable actually take effect (P1-1).
- Make the core-coverage step fail closed on
go listfailure and zero statements (P1-2).
The P2/nits below are non-blocking but worth folding in.
4. Non-blocking (P2 / nits)
- 🟡
reusable-node-quality.ymlhardcodesdavelosert/vitest-coverage-report-action; repos withhas-tests: trueon jest/other runners won't get a report. Worth documenting the vitest assumption (or making it conditional). - 🟢
pnpm test --coveragerelies on pnpm forwarding the flag to the underlying script;pnpm test -- --coveragematches the npm branch and is more robust. - 🟢 The
report-coveragejob checks out the PR ref but executes no project code, so it's safe; the checkout is only needed for working-directory context for the action. Fine as-is — noting for awareness.
5. Additional notes
- The split
test-with-services/test-standalonejobs and thealways() && (…success)gate ondiff-coverageremain a correct workaround for GitHub Actions' lack of conditionalservices:blocks; the sharedtest-resultsartifact name is safe because the two jobs are mutually exclusive viaif:. - Per-package DB reset (DROP/CREATE +
FLUSHALL) is a sound isolation pattern for the service-backed runs. quality-gateaggregation (both files) is fail-closed and correct; artifact upload underif: always()is a good touch.
P1-1: Replace invalid octocov config keys with correct schema - coverage.acceptable: expression 'current >= N && diff >= 0' gates both overall threshold and no-regression (replaces invalid diff.coverage.acceptable) - comment.if: 'is_pull_request && env.IS_FORK != true' suppresses fork comments (replaces invalid comment.enable boolean; export IS_FORK for octocov env access) - Remove invalid summary.enable (summary always shown by default) P1-2: Core-package coverage gate now fails closed - go list non-zero exit -> ::error:: + exit 1 (not silent skip) - no packages matched patterns -> ::error:: + exit 1 (not exit 0) - total_stmts == 0 -> ::error:: + exit 1 (not exit 0)
Jerry-Xin
left a comment
There was a problem hiding this comment.
This PR is in scope for Mininglamp-OSS/.github, but the new reusable workflows can fail valid fork PRs because PR-comment reporting is not safely disabled.
🔴 Blocking
-
🔴 Critical:
.github/workflows/reusable-go-quality.yml:363-411computesIS_FORKinside theGenerate octocov configshell step, but onlyexports it for that step. The nextRun octocovaction does not receive it, socomment.if: "is_pull_request && env.IS_FORK != 'true'"still evaluates as true whenenv.IS_FORKis unset. On fork PRs, octocov will try to comment with a read-only token and can fail the requireddiff-coverage/quality-gatepath. Persist it through$GITHUB_ENV, pass it asenv:to the octocov step, or skip/comment-disable explicitly for forks. -
🔴 Critical:
.github/workflows/reusable-node-quality.yml:195-218always runs the Vitest coverage PR reporter onpull_requestwhenhas-testsis true, withpull-requests: write. Fork PRs do not get write-capableGITHUB_TOKENs, so this reusable workflow can fail otherwise valid OSS contributions. Add a fork guard, setcomment-on: nonefor forks, or split reporting into a safeworkflow_runpattern.
💬 Non-blocking
- 🟡 Warning:
.github/workflows/reusable-go-quality.yml:413-500appliescoverage-excludeonly to octocov, not to the custom core package coverage check. Repos with generated or mock files insidecore-packagesmay fail the core threshold even though the workflow input says those files are excluded.
✅ Highlights
- The workflows follow the repository’s reusable workflow direction and keep permissions scoped per job.
- The Go workflow uploads test artifacts on failure and provides a stable
quality-gatejob, which is useful for branch protection.
lml2468
left a comment
There was a problem hiding this comment.
Re-review — PR #55 (449cf9f)
Incremental review of commit 449cf9f (only reusable-go-quality.yml changed since fd96a89).
Previous findings status
From yujiawei (fd96a89):
-
octocov invalid keys (
diff.coverage.acceptable,comment.enable,summary.enable) — ✅ Resolved. Config now uses valid octocov schema:coverage.acceptablewith expression syntax (current >= N && diff >= 0),comment.ifwith condition expression, andsummarysection removed (octocov defaults to showing summary). Verified against octocov source (config/config.go:260,config/ready.go:112, README line 450). -
Fork PR comment disable —
⚠️ Partially resolved. The config now uses validcomment.if: "is_pull_request && env.IS_FORK != 'true'"syntax (octocov supportsenvmap viaos.Environ()inconfig/config.go:481,500). However, see P1 below — the env var propagation is broken. -
Core-packages coverage gate — ✅ Resolved.
go listfailure nowexit 1, no packages matched nowexit 1, zero statements nowexit 1. All three fail-closed paths verified.
From Jerry-Xin (fd96a89):
-
Core-packages silent fail — ✅ Resolved. Same as #3 above.
-
Node fork PR quality gate failure — ❌ Still open.
reusable-node-quality.ymlwas not changed.report-coverage(line 195) still runs on allpull_requestevents withpull-requests: write. Fork PRs get a read-only token → vitest-coverage-report-action fails → quality-gate treats failure as blocking (line 306). The Go workflow has a fork guard; the Node workflow needs the same treatment (either skipreport-coveragefor forks, or treat its failure as non-blocking for forks).
New findings
🔴 P1 — Go IS_FORK env var not propagated to octocov action step
reusable-go-quality.yml:405: export IS_FORK is inside the "Generate octocov config" run: step. In GitHub Actions, each step runs in a separate shell process — export does NOT persist to subsequent steps. The "Run octocov" step (line 410-411) is a separate action step (k1LoW/octocov-action), so IS_FORK will not be in its environment.
octocov's comment.if evaluates env.IS_FORK by reading os.Environ() (verified in config/config.go:498-512). Since the variable is missing, env.IS_FORK will be empty string "", which is != 'true' → the condition evaluates to true → octocov will attempt to post a PR comment even on fork PRs → the comment will fail because the token is read-only.
Fix: Replace export IS_FORK with:
echo "IS_FORK=$IS_FORK" >> "$GITHUB_ENV"This makes IS_FORK available to all subsequent steps via $GITHUB_ENV.
CI status
No tabs in workflow files: ✅ passactionlint: ✅ passcode-review: pending
Verdict
CHANGES_REQUESTED — Two blocking items remain:
- Node fork PR quality gate issue (unchanged from prior round)
- Go
IS_FORKenv var propagation bug (new finding — the fork guard is syntactically correct but won't work at runtime)
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #55 (.github)
Fifth-round review of the two reusable workflows (reusable-go-quality.yml, reusable-node-quality.yml) at head 449cf9f. The two blockers raised last round are both genuinely fixed, verified against octocov and the vitest-action source rather than the PR description. No remaining blockers — the advertised gates now actually fire. A few non-blocking items below are worth folding in but should not hold up the merge.
Verdict: APPROVED.
1. Verification summary
- ✅ Fixed (prev P1-1) —
.octocov.ymlnow uses valid octocov keys. - ✅ Fixed (prev P1-2) — core-package coverage gate now fails closed.
- ✅ Confirmed non-blocking — Node
report-coverageon fork PRs does not break the quality gate. - 🟡 New, non-blocking — fork comment-suppression (
IS_FORK) is dead config due to a cross-step env bug, but it is cosmetic because octocov swallows comment failures.
2. Findings
✅ Prev P1-1 resolved — octocov config keys are valid and the gate enforces
The "Generate octocov config" step now writes a schema-valid .octocov.yml:
coverage:
paths:
- coverage.out
acceptable: "current >= 60 && diff >= 0"
comment:
if: "is_pull_request && env.IS_FORK != 'true'"The previously-invalid diff.coverage.acceptable, comment.enable, and summary.enable keys are gone. Verified against octocov source:
coverage.acceptable,coverage.paths,coverage.exclude, andcomment.ifare all supported keys.- The
currentvariable is on a 0–100 percentage scale (config/config.gocoverageAcceptable, and the table tests usecurrent >= 60%against50.0), so the barecurrent >= 60(no%) is correct —%is optional and stripped bytrimPercentRe. - The threshold is enforced:
c.Acceptable(...)returning an error propagates out ofRunE(cmd/root.go), failing the action, which failsdiff-coverage, which failsquality-gate. The gate is real now.
✅ Prev P1-2 resolved — core-coverage gate fails closed
The "Core packages coverage check" Python step now sys.exit(1) on all three "couldn't measure" paths: non-zero go list, zero matched patterns, and total_stmts == 0. A mistyped core-packages pattern or a build break in a core package now fails the gate instead of silently passing. Import-path matching (via go list -f {{.ImportPath}} against the dir of each coverage.out entry) is also the correct fix for the earlier literal-./modules/...-grep problem.
✅ Node fork report-coverage — not a gate blocker (was raised as a prior blocker)
report-coverage requests pull-requests: write, which forks don't get. I traced davelosert/vitest-coverage-report-action at the pinned SHA: handleError (src/index.ts) catches 403/404 and emits only core.warning(...) then returns; any other error is re-thrown into the top-level .catch((err) => core.error(err)), which logs but never calls setFailed. The action therefore always exits 0, so on a fork the coverage comment is skipped with a warning and report-coverage still succeeds — the gate is not blocked by reporting. No change required.
🟡 P2-1 (new, non-blocking) — fork comment-suppression is dead config
In the Go workflow's "Generate octocov config" step, IS_FORK is computed and export IS_FORK is called, but export only affects the current step's shell; it does not persist to the separate "Run octocov" step (GitHub Actions requires writing to $GITHUB_ENV for cross-step env). octocov's comment.if: env.IS_FORK != 'true' therefore reads an unset variable (empty string → condition is always true), so commenting is attempted on forks regardless. This is cosmetic only: octocov wraps commentReport and swallows the failure (Skip commenting report to pull request: ...) without failing the run, so the fork case degrades to a logged skip — exactly the intended end state, just via a different path. If you want the config to actually do what it says, write echo "IS_FORK=$IS_FORK" >> "$GITHUB_ENV" instead of export IS_FORK.
3. Non-blocking (P2 / nits, carried over)
- 🟡
coverage.acceptable: "current >= 60 && diff >= 0"— without a configured octocov datastore there is no previous report, soprevdefaults to 0 anddiff = current - 0 >= 0is always true. The effective gate is overall coverage only; thediff >= 0clause is a no-op until a datastore is wired. Worth a comment so future maintainers don't assume changed-line gating is active. - 🟡
reusable-node-quality.ymlhardcodesdavelosert/vitest-coverage-report-action; repos withhas-tests: trueon jest/other runners won't get a coverage report. Worth documenting the vitest assumption. - 🟢
pnpm test --coveragerelies on pnpm forwarding the flag to the underlying script;pnpm test -- --coveragematches the npm branch and is more robust.
4. Additional notes
- All
uses:are SHA-pinned;gotestsumpinned tov1.12.0; top-levelpermissions: {}with per-job least privilege;pull-requests: writecorrectly isolated to the octocov diff-coverage job and the Nodereport-coveragejob.actionlintand no-tabs checks are green on this head. - Both Python heredocs (
<<'PYEOF') are fed topython3 -; after YAML block-scalar stripping the code starts at column 0, so there is no indentation error despite the in-file visual indent — confirmed by parsing the YAML. - Split
test-with-services/test-standalone+ thealways() && (…success)gate ondiff-coverageremain a correct workaround for GHA's lack of conditionalservices:; the sharedtest-resultsartifact name is safe since the two jobs are mutually exclusive viaif:. Per-package DB reset (DROP/CREATE + FLUSHALL) is a sound isolation pattern. JUnit merge via Python and the correctedpassed = tests - failures - skippedcount are both fine.
Summary
Phase 1 of test quality CI improvements for the Mininglamp-OSS org.
New reusable workflows
reusable-go-quality.ymlgotestsuminstead of barego test, producing JUnit XML +coverage.out-race -shuffle=onflags for better test reliability$GITHUB_STEP_SUMMARYoctocov-actionfor diff coverage with PR comments*_mock.go,*.pb.go,*_gen.go,vendor/,migrations/time.Sleepin test files (warning, non-blocking)reusable-node-quality.ymltsc --noEmitpnpm lintornpm run lintpnpm test --coveragewith vitest coverage reportpnpm buildornpm run buildChecklist
reusable-go-quality.ymlcreated with all specified inputsreusable-node-quality.ymlcreated with all specified inputsreusable-check-sprint.yml)Related PRs
Individual repo CI upgrades will follow in separate PRs after this is merged.