Apply this flow in small, verifiable iterations. Prioritize real impact and low risk.
Required inputs
Define these inputs before the first cycle:
SONAR_HOST_URL(example:http://127.0.0.1:9000)SONAR_TOKENwith analysis permissionsSONAR_PROJECT_KEYand optionalSONAR_PROJECT_NAMESONAR_NEW_CODE_REFERENCE_BRANCH(default:main)- Monorepo paths:
apps/backend,apps/frontend,packages/* - Test commands per app (backend/frontend) with coverage
- Report paths
lcov.infoper app - Sonar file (
sonar-project.properties) or equivalent parameters in CI - Exclusion pattern for coverage and analysis
- Quality Gate thresholds (coverage, bugs, vulns, smells, duplication)
- Build/pipeline command that must not break
Prioritization strategy
Follow this work order:
- New Code first
- New Bugs and Vulnerabilities
- Security Hotspots in status
TO_REVIEW - Coverage in critical modules and those touched by the PR
- New Code Smells (prioritize high severity)
- Duplication and technical debt in active areas
- Historical debt (only if it does not affect the sprint goal)
Impact heuristic
For each issue or file, calculate priority in a simple way:
priorityScore = (isNewCode*100) + (isBugOrVuln*80) + (isHotspotToReview*70) + severityWeight + (criticalPath*20) + (coverageGap*10) - (estimatedEffort*5)
Where severityWeight can be: blocker 40, critical 30, major 20, minor 10.
Anti coverage inflation rule
Do not accept tests that only raise the percentage without validating behavior:
- Every new test must cover the happy case, the negative case, and the edge case
- Snapshot-only as the sole assertion is prohibited
- Avoid trivial tests of code without logic
- If the test does not fail in the face of a real bug, it does not count as valuable coverage
Skill pipeline (step by step)
Coverage discrepancy runbook (reusable case)
Use this runbook when SonarQube shows much lower coverage than local.
Typical symptom
- Local (Jest/Vitest): high coverage (for example >90%)
- SonarQube: noticeably lower coverage (for example 40-60%)
Frequent root causes
- Paths
SF:with backslashes (\\) inlcov.infogenerated on Windows. - SonarQube running on Linux/containers without being able to map paths with
\\. - Shared packages with low coverage (example
packages/utils) dragging down the global.
Quick diagnosis
- Review the first entries
SF:of thelcov.infoof each app/package. - Check in the
sonar-scannerlogs whether there are unmapped or ignored coverage files. - Compare coverage per module (backend/frontend/packages) to detect outliers.
Suggested commands:
Get-Content "apps\\backend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10
Get-Content "apps\\frontend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10
If paths like SF:src\\app.tsappear, normalize before the scan.
Recommended remediation
- Normalize paths to forward slash (
/) in alllcov.infobeforesonar-scanner. - Re-run coverage tests and scan.
- Increase coverage of the package/module with the lowest percentage so as not to skew the global.
Portable example (scripts/fix-lcov-paths.js):
const fs = require('node:fs');
const reports = [
'apps/backend/coverage/lcov.info',
'apps/frontend/coverage/lcov.info',
'packages/utils/coverage/lcov.info'
];
for (const reportPath of reports) {
if (!fs.existsSync(reportPath)) continue;
const content = fs.readFileSync(reportPath, 'utf8');
const normalized = content.replace(/\\\\/g, '/');
fs.writeFileSync(reportPath, normalized);
}
CI Guardrail (fail if backslashes remain):
if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then
echo "ERROR: Backslashes encontrados en lcov.info"
exit 1
fi
Step 1: Detect current gap
- Run tests with coverage in relevant backend/frontend/packages
- Run SonarQube analysis
- Raise metrics and new issues to prioritize
Generic commands:
bun run --cwd apps/backend test -- --coverage
bun run --cwd apps/frontend test -- --coverage
sonar-scanner `
-Dsonar.host.url=$env:SONAR_HOST_URL `
-Dsonar.token=$env:SONAR_TOKEN `
-Dsonar.projectKey=$env:SONAR_PROJECT_KEY `
-Dsonar.qualitygate.wait=true
curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=coverage,new_coverage,bugs,new_bugs,vulnerabilities,new_vulnerabilities,code_smells,new_code_smells,duplicated_lines_density,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"
curl -4 -s "$env:SONAR_HOST_URL/api/issues/search?componentKeys=$env:SONAR_PROJECT_KEY&resolved=false&inNewCodePeriod=true&types=BUG,VULNERABILITY,CODE_SMELL&ps=500" -u "$env:SONAR_TOKEN:"
curl -4 -s "$env:SONAR_HOST_URL/api/hotspots/search?projectKey=$env:SONAR_PROJECT_KEY&status=TO_REVIEW&ps=500" -u "$env:SONAR_TOKEN:"
Step 2: Generate or update Sonar config
Create or update sonar-project.properties at root with the monorepo's real paths.
Step 3: Ensure real coverage reading
- Verify existence of
lcov.info - Configure
sonar.javascript.lcov.reportPathswith all paths - Validate in the scanner logs that there are no missing coverage warnings
- Verify that lines
SF:use/and not\\ - If there is a mixed Windows/Linux environment, run path normalization before the scan
- Confirm that relevant shared packages also report coverage (not only apps)
Step 4: Run tests consistently
- Use the same commands and flags locally and in CI
- Publish coverage artifacts
- Do not mix runners/flags between environments
Step 5: Iterate in small batches
- Batch A: New Bugs/Vulns/Hotspots
- Batch B: Coverage on New Code
- Batch C: New Code Smells
- Batch D: Duplication/debt in touched areas
Guided test generation (backend and frontend)
1) Identify files without coverage
- Main source: SonarQube (files with uncovered lines in New Code)
- Secondary source:
lcov.info+ modified files in the PR
git diff --name-only origin/main...HEAD | Where-Object { $_ -match '\.(ts|tsx)$' }
2) Choose the correct test type
- Unit: pure logic and functions with few dependencies
- Integration (backend): controllers + services + mocked repositories
- Component (frontend): interactions, states, and accessibility
- E2E: only critical flows, not as a replacement for unit/component
3) Mocking strategy
- DB: fake repo or isolated test database
- External HTTP: deterministic mocks (without hitting real services)
- Time/random: control clock and random values
- Avoid excessive mocks that hide real defects
4) Minimum test quality criteria
- Clear Arrange/Act/Assert structure
- Semantic assertions of expected behavior
- Happy path + negative + edge cases mandatory
- Stable test without flakiness
- Behavior-oriented name
5) Naming and structure
- Backend:
*.spec.ts - Frontend:
*.test.tsx - Recommended pattern:
should <resultado> when <condicion>returns <error> when <input invalido>
Remediation of Code Smells, Bugs, and Security
Apply surgical fixes, without massive style changes.
Checklist by category:
- Complexity: extract functions, use early return, reduce nesting
- Duplication: consolidate utilities and avoid copy-paste
- Nullability: explicit guards and safe defaults
- Error handling: avoid
catchempty, propagate with context - Leaks/resources: close timers, subscriptions, connections
- Regex/DoS: avoid catastrophic patterns and limit input
- Insecure dependencies: update vulnerable libraries
- Sanitization: validate and sanitize input, avoid XSS/injection
- Security Hotspots: review and document resolution for each item
Exclusion rules and trade-offs
Acceptable exclusions (with justification):
**/index.tswithout logic- DTOs or simple types without branches
- generated code (
**/generated/**) - minimal bootstrap without business logic
Unacceptable exclusions:
- services/use cases/controllers with logic
- hooks and validators with branches
- authentication/authorization code
- exclude to pass the percentage without improving quality
Rule: each exclusion must record the technical reason.
Definition of Done and metrics
Declare cycle completed only with evidence:
- Quality Gate in state
PASSED new_coverage >= 80new_bugs = 0new_vulnerabilities = 0new_code_smells = 0new_security_hotspots_reviewed = 100%- Duplication and debt within gate threshold
- Build and pipelines green
Minimum evidence to attach:
- output of
sonar-scannerwithsonar.qualitygate.wait=true - output of metrics/issues/hotspots APIs
- coverage by folder (backend/frontend/packages)
- summary of resolved issues (before/after)
Configuration snippets
sonar-project.properties
sonar.projectKey=my-org_my-monorepo
sonar.projectName=my-monorepo
sonar.sourceEncoding=UTF-8
sonar.sources=apps/backend/src,apps/frontend/src,packages
sonar.tests=apps/backend,apps/frontend,packages
sonar.test.inclusions=**/*.spec.ts,**/*.test.ts,**/*.test.tsx,**/*.spec.tsx
sonar.javascript.lcov.reportPaths=apps/backend/coverage/lcov.info,apps/frontend/coverage/lcov.info
sonar.newCode.referenceBranch=main
sonar.exclusions=**/dist/**,**/build/**,**/node_modules/**,**/*.d.ts
sonar.coverage.exclusions=**/index.ts,**/*.dto.ts,**/generated/**,**/*.stories.tsx
Jest backend (example)
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.dto.ts',
'!src/**/index.ts',
'!src/**/generated/**'
]
};
export default config;
Vitest frontend (example)
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'html'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/**/*.stories.tsx', 'src/**/index.ts']
}
}
});
GitHub Actions (example)
name: quality-gate
on:
pull_request:
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Backend tests with coverage
run: bun run --cwd apps/backend test -- --coverage
- name: Frontend tests with coverage
run: bun run --cwd apps/frontend test -- --coverage
- name: Normalize lcov paths (Windows/Linux safe)
run: node scripts/fix-lcov-paths.js
- name: Validate lcov path format
run: |
if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then
echo "ERROR: Backslashes encontrados en backend lcov.info"
exit 1
fi
- name: Build
run: bun run build
- name: SonarQube Scan
env:
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner \
-Dsonar.host.url=$SONAR_HOST_URL \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.qualitygate.wait=true
GitLab CI (example)
stages:
- test
- quality
variables:
GIT_DEPTH: "0"
test_and_build:
stage: test
image: oven/bun:1
script:
- bun install --frozen-lockfile
- bun run --cwd apps/backend test -- --coverage
- bun run --cwd apps/frontend test -- --coverage
- node scripts/fix-lcov-paths.js
- bun run build
artifacts:
when: always
paths:
- apps/backend/coverage/
- apps/frontend/coverage/
sonarqube:
stage: quality
image: sonarsource/sonar-scanner-cli:latest
dependencies:
- test_and_build
script:
- sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
allow_failure: false
Skill README
Use this quick summary during execution:
- run tests with coverage
- run sonar-scanner with gate wait
- prioritize New Code -> Bugs/Vulns -> Hotspots -> Coverage -> Smells
- iterate in small batches and attach evidence
Base commands:
bun run --cwd apps/backend test -- --coveragebun run --cwd apps/frontend test -- --coveragesonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
PR Checklist
- No new Bugs or Vulnerabilities
- No new Code Smells
- New Coverage >= 80%
- New Hotspots reviewed
- No unjustified exclusions
- Tests with happy, negative, and edge cases
- Build and CI green
- Sonar evidence attached
-
lcov.infowithout backslashes in linesSF: - Shared packages without a strong coverage gap
Command templates ready to paste
$env:SONAR_HOST_URL="http://127.0.0.1:9000"
$env:SONAR_TOKEN="<token>"
$env:SONAR_PROJECT_KEY="<project-key>"
bun run --cwd apps/backend test -- --coverage
bun run --cwd apps/frontend test -- --coverage
bun run build
sonar-scanner `
-Dsonar.host.url=$env:SONAR_HOST_URL `
-Dsonar.token=$env:SONAR_TOKEN `
-Dsonar.projectKey=$env:SONAR_PROJECT_KEY `
-Dsonar.qualitygate.wait=true
curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=new_coverage,new_bugs,new_vulnerabilities,new_code_smells,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"
Defaults to adjust if there is uncertainty
- New Code branch:
main - New Coverage threshold:
80 - maximum duplication new code:
3% - coverage exclusions: only files without logic
- batch strategy: 1 PR per critical category