Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# SonarCloud quality gate + coverage upload.
#
# Triggers: push to main (full-history scan) and PRs (incremental scan
# against the base branch). Concurrency-gated per-ref so reruns cancel
# in-flight scans.
#
# Coverage is collected fresh in this workflow rather than reused from
# CI — Sonar needs both go-coverprofile and lcov in the same workspace
# pass, and the existing CI job runs `go test` without -coverprofile.
# Adding coverage there would slow every PR build; keeping it here
# isolates the cost to the Sonar job.

name: SonarCloud

on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: read
pull-requests: read

concurrency:
group: sonar-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Sonar uses git blame for new-code detection — shallow
# checkouts (default fetch-depth=1) attribute every line to
# the latest commit, breaking the quality-gate "new code"
# signal. fetch-depth: 0 grabs the full history.
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Set up pnpm
# Pinned to commit SHA (v4) per supply-chain hygiene — third-party
# actions can be rewritten under a moving tag. Bump by re-running
# `gh api repos/pnpm/action-setup/git/refs/tags/v4`.
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
with:
version: 10.33.0

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: ui/pnpm-lock.yaml

- name: Build UI bundle
# Required before any Go test run: //go:embed all:dist in
# internal/serve/assets.go errors out if internal/serve/dist/
# is empty. Same as ci.yml.
run: make ui

- name: Go test with coverage
run: |
go test -tags sqlite_fts5 \
-coverprofile=coverage.out \
-covermode=atomic \
./...

- name: UI install
# --ignore-scripts: refuse to execute lifecycle scripts from
# transitive deps (Sonar S6505). vitest + the React stack don't
# require any postinstall; esbuild ships per-platform binaries
# via optional deps. If a future dep needs a build step, add it
# to `pnpm.onlyBuiltDependencies` in ui/package.json instead.
run: pnpm -C ui install --frozen-lockfile --ignore-scripts

- name: UI test with coverage
run: pnpm -C ui exec vitest run --coverage

- name: Detect SONAR_TOKEN
id: token
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
if [ -n "$SONAR_TOKEN" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "::warning::SONAR_TOKEN secret is not set; skipping SonarCloud scan. Add the token in repo Settings → Secrets and variables → Actions."
fi

- name: SonarCloud Scan
if: steps.token.outputs.present == 'true'
# v6 — v5 emits a deprecation/security warning on every run.
# Pinned to commit SHA per supply-chain hygiene. Bump by
# re-running `gh api repos/SonarSource/sonarqube-scan-action/git/refs/tags/v6`.
uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ docs/

# Local code-intelligence cache (codeiq) — runtime DB + neo4j store, not source
.codeiq/

# Coverage reports (Go + vitest) — regenerated per-test, never committed
coverage.out
ui/coverage/
52 changes: 52 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SonarCloud project configuration. The scanner reads this file at the
# repo root automatically; the GitHub Action only needs SONAR_TOKEN.
#
# Project + organization keys must match what SonarCloud generated when
# the project was imported from GitHub — adjust if you renamed the org
# or the project (Sonar UI → Administration → General Settings → Project
# Key).

sonar.projectKey=RandomCodeSpace_ctm
sonar.organization=randomcodespace
sonar.projectName=ctm

# ── Sources ────────────────────────────────────────────────────────────
# Scan everything under the repo root. Generated artifacts, vendored
# code, the embedded UI dist, and agent worktree scratch never enter
# the analysis — they're either machine-written or out of scope.
sonar.sources=.
sonar.exclusions=\
**/dist/**,\
**/node_modules/**,\
**/vendor/**,\
**/_attic/**,\
**/.claude/**,\
**/.codeiq/**,\
internal/serve/dist/**,\
ui/coverage/**,\
ui/playwright-report/**,\
ui/test-results/**,\
coverage.out,\
docs/**

# ── Tests ──────────────────────────────────────────────────────────────
# Sonar separates "test code" from "production code" so coverage and
# duplication metrics target the right files. Playwright e2e specs
# stay out of the JS test set — they don't generate the unit-style
# coverage Sonar expects.
sonar.tests=.
sonar.test.inclusions=\
**/*_test.go,\
ui/src/**/*.test.ts,\
ui/src/**/*.test.tsx
sonar.test.exclusions=ui/e2e/**

# ── Coverage report paths ──────────────────────────────────────────────
# Go: `go test -coverprofile=coverage.out ./...` writes to repo root.
# JS/TS: vitest with provider:v8 writes lcov to ui/coverage/lcov.info
# (configured in ui/vitest.config.ts).
sonar.go.coverage.reportPaths=coverage.out
sonar.javascript.lcov.reportPaths=ui/coverage/lcov.info

# ── General ────────────────────────────────────────────────────────────
sonar.sourceEncoding=UTF-8
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.2.0",
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^9.0.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.12",
Expand Down
Loading
Loading