From dev-assistant
Run SonarQube analysis on Etendo modules. Use this skill when the user wants to run Sonar, check code quality, fix Sonar issues, analyze a PR with SonarQube, or when a SonarQube check fails on a PR. Also trigger when the user mentions code smells, quality gate, static analysis, or duplicated code in the context of an Etendo module.
npx claudepluginhub etendosoftware/etendo_claude_marketplace --plugin dev-assistantThis skill uses the workspace's default tool permissions.
**Arguments:** `$ARGUMENTS` (optional: `check` to run analysis, `fix` to auto-fix issues, `pr` to analyze a PR, `setup` to install sonar-scanner)
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
Arguments: $ARGUMENTS (optional: check to run analysis, fix to auto-fix issues, pr to analyze a PR, setup to install sonar-scanner)
Detect the OS and guide accordingly:
macOS:
brew install sonar-scanner
Linux (Ubuntu/Debian/any):
# Download the latest sonar-scanner CLI
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-7.0.2.4839-linux-x64.zip
unzip sonar-scanner-cli-*-linux-x64.zip
sudo mv sonar-scanner-*-linux-x64 /opt/sonar-scanner
sudo ln -sf /opt/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner
rm sonar-scanner-cli-*-linux-x64.zip
If the download URL returns 404, fetch the latest version from the SonarSource binaries page and adjust the URL.
Verify installation:
sonar-scanner --version
The token authenticates against sonar.etendo.cloud. Check if it's already configured:
# Check env var
echo $SONAR_TOKEN
# Check macOS Keychain (macOS only)
security find-generic-password -a "$USER" -s "sonar-etendo" -w 2>/dev/null
If no token is found, inform the user:
You need a SonarQube token. Generate one at:
https://sonar.etendo.cloud→ Avatar → My Account → Security → Generate TokenThen store it:
# Option A — env var (add to ~/.zshrc or ~/.bashrc) export SONAR_TOKEN="squ_your_token_here" # Option B — macOS Keychain security add-generic-password -a "$USER" -s "sonar-etendo" -w "squ_your_token_here"
Token retrieval order:
$SONAR_TOKEN env varsecurity find-generic-password -a "$USER" -s "sonar-etendo" -w)NEVER store the token in files tracked by git, in CLAUDE.md, or in memory files.
Find the sonar-project.properties file in the current module:
# From current directory, search upward for sonar-project.properties
SONAR_PROPS=$(find . -maxdepth 2 -name "sonar-project.properties" -print -quit 2>/dev/null)
if [ -z "$SONAR_PROPS" ]; then
echo "No sonar-project.properties found. Cannot run analysis."
# Guide user to create one — see "Project Configuration" section
fi
PROJECT_KEY=$(grep "sonar.projectKey" "$SONAR_PROPS" | cut -d'=' -f2 | tr -d ' ')
echo "Project key: $PROJECT_KEY"
# Check if on a PR
PR_INFO=$(gh pr view --json number,headRefName 2>/dev/null)
if [ $? -eq 0 ]; then
PR_NUMBER=$(echo "$PR_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['number'])")
BRANCH_NAME=$(echo "$PR_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['headRefName'])")
echo "PR #$PR_NUMBER on branch $BRANCH_NAME"
else
BRANCH_NAME=$(git branch --show-current)
echo "Branch: $BRANCH_NAME (no open PR)"
fi
SONAR_TOKEN="${SONAR_TOKEN:-$(security find-generic-password -a "$USER" -s "sonar-etendo" -w 2>/dev/null)}"
if [ -z "$SONAR_TOKEN" ]; then
echo "ERROR: No SONAR_TOKEN found. See setup instructions."
exit 1
fi
Navigate to the directory containing sonar-project.properties, then run:
If PR exists:
sonar-scanner \
-Dsonar.host.url=https://sonar.etendo.cloud \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.pullrequest.key=$PR_NUMBER \
-Dsonar.pullrequest.branch=$BRANCH_NAME \
-Dsonar.pullrequest.base=main
If no PR (branch analysis):
sonar-scanner \
-Dsonar.host.url=https://sonar.etendo.cloud \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.branch.name=$BRANCH_NAME
If on main (default analysis):
sonar-scanner \
-Dsonar.host.url=https://sonar.etendo.cloud \
-Dsonar.token=$SONAR_TOKEN
The scanner takes ~20 seconds. Delegate to a subagent if preferred to keep context clean.
Before querying Sonar results, determine which files were changed so you can filter and prioritize issues on those files. The developer only cares about issues in code they touched — pre-existing issues in untouched files are not their responsibility.
# Get list of changed files (new + modified) relative to main
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMR main...HEAD 2>/dev/null || git diff --name-only --diff-filter=ACMR HEAD~5)
echo "Changed files:"
echo "$CHANGED_FILES"
Store this list — you'll use it in the next step to separate "your issues" from "pre-existing issues".
Wait 5 seconds for server processing, then query the API:
sleep 5
# Build the API URL based on context
API_URL="https://sonar.etendo.cloud/api/issues/search?componentKeys=${PROJECT_KEY}&resolved=false&ps=100"
if [ -n "$PR_NUMBER" ]; then
API_URL="${API_URL}&pullRequest=${PR_NUMBER}"
fi
# Fetch issues and separate by changed files vs pre-existing
curl -s -u "$SONAR_TOKEN:" "$API_URL" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
total = data.get('total', 0)
# Changed files from git (passed via env or stdin)
import os
changed_raw = os.environ.get('CHANGED_FILES', '')
changed = set(f.strip() for f in changed_raw.split('\n') if f.strip())
your_issues = []
other_issues = []
for i in data.get('issues', []):
comp = i.get('component','').split(':')[-1]
line = i.get('line','?')
sev = i.get('severity','?')
typ = i.get('type','?')
msg = i.get('message','')
rule = i.get('rule','')
entry = f'[{sev}] {typ} — {comp}:{line}\n {msg}\n Rule: {rule}'
# Check if the file matches any changed file
if any(comp.endswith(cf) or cf.endswith(comp) for cf in changed):
your_issues.append(entry)
else:
other_issues.append(entry)
print(f'=== Issues in YOUR changed files: {len(your_issues)} ===')
for e in your_issues:
print(e)
print()
if other_issues:
print(f'=== Pre-existing issues (other files): {len(other_issues)} ===')
print(f'(Not your responsibility — {len(other_issues)} issues in untouched files)')
print()
if not your_issues and not other_issues:
print('No issues found. Clean!')
"
Important: Set CHANGED_FILES env var before running the python script:
export CHANGED_FILES="$CHANGED_FILES"
Focus rule: When reporting results to the user, always show issues in changed files first and prominently. Pre-existing issues in untouched files should be mentioned as a count only, not listed in detail, unless the user explicitly asks.
QG_URL="https://sonar.etendo.cloud/api/qualitygates/project_status?projectKey=${PROJECT_KEY}"
if [ -n "$PR_NUMBER" ]; then
QG_URL="${QG_URL}&pullRequest=${PR_NUMBER}"
fi
QG_STATUS=$(curl -s -u "$SONAR_TOKEN:" "$QG_URL" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('projectStatus',{}).get('status','UNKNOWN'))")
echo "Quality Gate: $QG_STATUS"
When the user says fix or when issues are found:
| Issue Type | Common Fix |
|---|---|
| Duplicated string literal | Extract to private static final String CONSTANT_NAME = "value" |
| Unused import | Remove the import line |
| Empty catch block | Add logging or comment explaining why it's empty |
| Cognitive complexity too high | Extract sub-methods |
| Deprecated API usage | Replace with recommended alternative |
If a module lacks sonar-project.properties, create one:
sonar.projectKey=etendosoftware_<module-javapackage>_<sonar-id>
sonar.java.binaries=.
The projectKey must match the one registered on sonar.etendo.cloud. Check existing projects:
curl -s -u "$SONAR_TOKEN:" \
"https://sonar.etendo.cloud/api/projects/search?q=<module-name>" \
| python3 -c "import sys,json; [print(p['key']) for p in json.load(sys.stdin).get('components',[])]"
+ Ran SonarQube analysis
Project: etendosoftware_com.etendoerp.example_AY...
Context: PR #42 (branch: hotfix/#42-ETP-3400)
Quality Gate: OK | ERROR
Issues: 3 (1 CRITICAL, 2 MAJOR)
Dashboard: https://sonar.etendo.cloud/dashboard?id=<projectKey>&pullRequest=42
Next steps:
/etendo:sonar fix -> Auto-fix detected issues
/etendo:sonar -> Re-run after manual fixes