Skip to content

Commit 21d5284

Browse files
aksOpsclaude
andauthored
ci: SonarCloud quality gate + coverage (#7)
* ci: SonarCloud quality gate + coverage on PR and main Adds a SonarCloud scan that ingests Go + JS/TS coverage on every PR and on push to main. Coverage is collected fresh in this workflow rather than reused from CI — the existing ci.yml runs go test without -coverprofile and adding it there would slow every PR build. Setup required (manual, one-time): 1. Sign in to sonarcloud.io with GitHub. 2. Import RandomCodeSpace/ctm; project key auto-generated as RandomCodeSpace_ctm (matches sonar-project.properties). 3. Generate a user token; add as repo secret SONAR_TOKEN. Until SONAR_TOKEN is set, the workflow runs the test+coverage steps and emits a workflow warning instead of erroring on the scan step. Coverage layout: - Go: go test -coverprofile=coverage.out (atomic mode for race-safe) - UI: vitest run --coverage with provider:v8 → ui/coverage/lcov.info - Both: excluded from git via .gitignore Excluded from analysis: dist/, vendor/, node_modules/, _attic/, .claude/, .codeiq/, internal/serve/dist/ (generated UI bundle), ui/playwright-report/, ui/test-results/, docs/. Quality gate uses SonarCloud's default ('clean as you code', 80% new-code coverage) — adjustable in the SonarCloud UI later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(sonar): pin third-party actions, --ignore-scripts, bump scan v6 Closes the three findings the SonarCloud quality gate raised on PR #7: - pnpm/action-setup pinned to commit SHA (v4 → b906aff). Tag-based refs for third-party actions can be silently rewritten — Sonar S7637. - SonarSource/sonarqube-scan-action bumped v5 → v6 and pinned to SHA fd88b7d. v5 emits a deprecation/security warning on every run. - pnpm install now passes --ignore-scripts (Sonar S6505). Modern vitest + React stack doesn't require lifecycle scripts; verified locally with a clean install + `vitest run --coverage`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c8e1224 commit 21d5284

6 files changed

Lines changed: 449 additions & 0 deletions

File tree

.github/workflows/sonar.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SonarCloud quality gate + coverage upload.
2+
#
3+
# Triggers: push to main (full-history scan) and PRs (incremental scan
4+
# against the base branch). Concurrency-gated per-ref so reruns cancel
5+
# in-flight scans.
6+
#
7+
# Coverage is collected fresh in this workflow rather than reused from
8+
# CI — Sonar needs both go-coverprofile and lcov in the same workspace
9+
# pass, and the existing CI job runs `go test` without -coverprofile.
10+
# Adding coverage there would slow every PR build; keeping it here
11+
# isolates the cost to the Sonar job.
12+
13+
name: SonarCloud
14+
15+
on:
16+
push:
17+
branches: [main]
18+
pull_request:
19+
types: [opened, synchronize, reopened]
20+
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
25+
concurrency:
26+
group: sonar-${{ github.workflow }}-${{ github.ref }}
27+
cancel-in-progress: true
28+
29+
jobs:
30+
scan:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Checkout
34+
uses: actions/checkout@v4
35+
with:
36+
# Sonar uses git blame for new-code detection — shallow
37+
# checkouts (default fetch-depth=1) attribute every line to
38+
# the latest commit, breaking the quality-gate "new code"
39+
# signal. fetch-depth: 0 grabs the full history.
40+
fetch-depth: 0
41+
42+
- name: Set up Go
43+
uses: actions/setup-go@v5
44+
with:
45+
go-version-file: go.mod
46+
47+
- name: Set up pnpm
48+
# Pinned to commit SHA (v4) per supply-chain hygiene — third-party
49+
# actions can be rewritten under a moving tag. Bump by re-running
50+
# `gh api repos/pnpm/action-setup/git/refs/tags/v4`.
51+
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
52+
with:
53+
version: 10.33.0
54+
55+
- name: Set up Node
56+
uses: actions/setup-node@v4
57+
with:
58+
node-version: 22
59+
cache: pnpm
60+
cache-dependency-path: ui/pnpm-lock.yaml
61+
62+
- name: Build UI bundle
63+
# Required before any Go test run: //go:embed all:dist in
64+
# internal/serve/assets.go errors out if internal/serve/dist/
65+
# is empty. Same as ci.yml.
66+
run: make ui
67+
68+
- name: Go test with coverage
69+
run: |
70+
go test -tags sqlite_fts5 \
71+
-coverprofile=coverage.out \
72+
-covermode=atomic \
73+
./...
74+
75+
- name: UI install
76+
# --ignore-scripts: refuse to execute lifecycle scripts from
77+
# transitive deps (Sonar S6505). vitest + the React stack don't
78+
# require any postinstall; esbuild ships per-platform binaries
79+
# via optional deps. If a future dep needs a build step, add it
80+
# to `pnpm.onlyBuiltDependencies` in ui/package.json instead.
81+
run: pnpm -C ui install --frozen-lockfile --ignore-scripts
82+
83+
- name: UI test with coverage
84+
run: pnpm -C ui exec vitest run --coverage
85+
86+
- name: Detect SONAR_TOKEN
87+
id: token
88+
env:
89+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
90+
run: |
91+
if [ -n "$SONAR_TOKEN" ]; then
92+
echo "present=true" >> "$GITHUB_OUTPUT"
93+
else
94+
echo "present=false" >> "$GITHUB_OUTPUT"
95+
echo "::warning::SONAR_TOKEN secret is not set; skipping SonarCloud scan. Add the token in repo Settings → Secrets and variables → Actions."
96+
fi
97+
98+
- name: SonarCloud Scan
99+
if: steps.token.outputs.present == 'true'
100+
# v6 — v5 emits a deprecation/security warning on every run.
101+
# Pinned to commit SHA per supply-chain hygiene. Bump by
102+
# re-running `gh api repos/SonarSource/sonarqube-scan-action/git/refs/tags/v6`.
103+
uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6
104+
env:
105+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ docs/
2626

2727
# Local code-intelligence cache (codeiq) — runtime DB + neo4j store, not source
2828
.codeiq/
29+
30+
# Coverage reports (Go + vitest) — regenerated per-test, never committed
31+
coverage.out
32+
ui/coverage/

sonar-project.properties

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# SonarCloud project configuration. The scanner reads this file at the
2+
# repo root automatically; the GitHub Action only needs SONAR_TOKEN.
3+
#
4+
# Project + organization keys must match what SonarCloud generated when
5+
# the project was imported from GitHub — adjust if you renamed the org
6+
# or the project (Sonar UI → Administration → General Settings → Project
7+
# Key).
8+
9+
sonar.projectKey=RandomCodeSpace_ctm
10+
sonar.organization=randomcodespace
11+
sonar.projectName=ctm
12+
13+
# ── Sources ────────────────────────────────────────────────────────────
14+
# Scan everything under the repo root. Generated artifacts, vendored
15+
# code, the embedded UI dist, and agent worktree scratch never enter
16+
# the analysis — they're either machine-written or out of scope.
17+
sonar.sources=.
18+
sonar.exclusions=\
19+
**/dist/**,\
20+
**/node_modules/**,\
21+
**/vendor/**,\
22+
**/_attic/**,\
23+
**/.claude/**,\
24+
**/.codeiq/**,\
25+
internal/serve/dist/**,\
26+
ui/coverage/**,\
27+
ui/playwright-report/**,\
28+
ui/test-results/**,\
29+
coverage.out,\
30+
docs/**
31+
32+
# ── Tests ──────────────────────────────────────────────────────────────
33+
# Sonar separates "test code" from "production code" so coverage and
34+
# duplication metrics target the right files. Playwright e2e specs
35+
# stay out of the JS test set — they don't generate the unit-style
36+
# coverage Sonar expects.
37+
sonar.tests=.
38+
sonar.test.inclusions=\
39+
**/*_test.go,\
40+
ui/src/**/*.test.ts,\
41+
ui/src/**/*.test.tsx
42+
sonar.test.exclusions=ui/e2e/**
43+
44+
# ── Coverage report paths ──────────────────────────────────────────────
45+
# Go: `go test -coverprofile=coverage.out ./...` writes to repo root.
46+
# JS/TS: vitest with provider:v8 writes lcov to ui/coverage/lcov.info
47+
# (configured in ui/vitest.config.ts).
48+
sonar.go.coverage.reportPaths=coverage.out
49+
sonar.javascript.lcov.reportPaths=ui/coverage/lcov.info
50+
51+
# ── General ────────────────────────────────────────────────────────────
52+
sonar.sourceEncoding=UTF-8

ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@types/react": "^19.2.14",
3535
"@types/react-dom": "^19.2.3",
3636
"@vitejs/plugin-react": "^5.2.0",
37+
"@vitest/coverage-v8": "^3.2.4",
3738
"eslint": "^9.0.0",
3839
"eslint-plugin-react-hooks": "^5.2.0",
3940
"eslint-plugin-react-refresh": "^0.4.12",

0 commit comments

Comments
 (0)