sonarqube-setup
Liga o projeto a um servidor SonarQube: o scanner no arquivo de build e um passo de CI ou um container local.
sonarqube-setup, seção a seção
As seções do próprio SKILL.md da skill, em trechos. Passe o mouse ou clique numa delas para ver quais regras do projeto, templates e arquivos de references/ ela cita. As seções também são chips: clique num para ir até ela.
## Why this is a skill (Form 1) and not Form 2
Form 1: project-bootstrap and arch-adopt chain it by name, and a skill with disable-model-invocation: true cannot be called through the Skill tool — the same reason git-publish and docker-architect stay model-invocable. Axis 2 of the interview — two callers, one of which runs inside a project where project-bootstrap does not exist — is what made it a skill of its own instead of a step inside the bootstrap. The closest rejected form was that step: arch-adopt would then have to write pom.xml and docker-compose.yml itself, widening its territory and giving the compose file a second owner. Record: @.claude/decisions/0091-sonarqube-setup-skill.md.
Runs on sonnet with effort: low: a fixed procedure behind one question, every version resolved by curl (@.claude/decisions/0080-procedural-skills-on-sonnet.md).
## Procedure
Project directory. When the caller's one-line context names project: <absolute path>, that directory is the project root, not the session's: read and write every path of this procedure under it, and run every shell command as (cd "<path>" && …). When this skill chains docker-architect, pass project: <path> on in its context.
### 1 · Detect the build tool and whether Sonar is already there
pom.xml at the root → maven. build.gradle at the root → gradle. Neither → stop: there is no project to configure. Both → stop and say so; this repo never generates both.
Grep the root build file for sonar-maven-plugin / org.sonarqube. Found → entry rule above: report and stop.
### 2 · Ask about the server — one `AskUserQuestion`
Two questions in the same call:
- Server — "Is there already a SonarQube server or SonarCloud organization for this
- Authentication — "How does the analysis authenticate?"
When a caller already passed an answer in its one-line context, don't ask it again.
### 3 · Write the build configuration
Resolve the scanner version on the repository itself — never from memory, same rule project-bootstrap/references/build-maven.md § 4.6 applies to every plugin:
# maven
curl -sS 'https://repo1.maven.org/maven2/org/sonarsource/scanner/maven/sonar-maven-plugin/maven-metadata.xml' | grep -o '<release>[^<]*</release>'
# gradle — the plugin lives on the Gradle Plugin Portal, not on Maven Central
curl -sS 'https://plugins.gradle.org/m2/org/sonarqube/org.sonarqube.gradle.plugin/maven-metadata.xml' | grep -o '<release>[^<]*</release>'
No network → don't write a version: say which one could not be resolved and let the user supply it.
Then merge — never replace what the root build file already has:
| Tool | Exemplar | Where |
|---|---|---|
| maven | templates/pom-sonar.xml.example | properties into the root POM's <properties>, the plugin into <build><pluginManagement><plugins> |
| gradle | templates/build-gradle-sonar.example | the plugin into the root plugins {}, the sonar {} block at the root level — not inside subprojects |
sonar.projectKey is <groupId>:<artifactId> from the root build file. sonar.host.url is the URL from step 2, or http://localhost:9000 for the local container. sonar.organization only for SonarCloud. The coverage report path needs no property: the scanner's default report paths are exactly where the JaCoCo setup project-bootstrap writes puts them (target/site/jacoco/jacoco.xml, build/reports/jacoco/test/jacocoTestReport.xml).
… cortado aqui, o resto está no arquivo
### 4 · Local server — only when step 2 answered *None*
Invoke docker-architect through the Skill tool with a one-line context: "service sonarqube, chained from sonarqube-setup — no engine question". It owns every service block in docker-compose.yml, and its template for this one is templates/sonarqube-service.yml.example there. Don't write the block here, not even when it looks like a copy: that is the ownership split, not a style choice.
No docker-compose.yml at the root → docker-architect stops on its own entry rule. Report that the build points at http://localhost:9000 and nothing runs there yet.
### 5 · CI — only when the server is external
A local container is unreachable from a CI runner, so this step runs only for an existing server or SonarCloud. Merge templates/ci-sonar-step.yml.example into .github/workflows/build.yml (the file project-bootstrap step 6.5 writes): job-level env, fetch-depth: 0 on the checkout, and the analysis step after verify. The step is skipped while the SONAR_TOKEN secret is absent — a fork's pull request has none, and a red build there would say nothing about the code.
No .github/workflows/build.yml → skip, and say it in the report.
## Report
✅ SonarQube configured — <existing server <url> | SonarCloud <org> | local container>
Build ....... <pom.xml | build.gradle> — scanner <version> (resolved from <repository>)
Project key . <groupId:artifactId>
Coverage .... sonar.coverage.exclusions = <patterns> (from the JaCoCo excludes in <file>:<line>) | not set — no JaCoCo excludes in <file>
Server ...... <url> · auth: <SONAR_TOKEN | anonymous>
Compose ..... <sonarqube added by docker-architect, tag <tag> | not touched — external server>
CI .......... <step added, runs when secret SONAR_TOKEN is set | skipped — local server | skipped — no workflow>
Access check (before any scan):
curl -s <url>/api/authentication/validate → {"valid":true} when anonymous is allowed
(or SONAR_TOKEN is exported — the scanner reads it)
First run: <./mvnw -B verify sonar:sonar | ./gradlew build sonar>
Rerun: <./mvnw -B sonar:sonar | ./gradlew sonar> — same build output, no tests again
The access check moves an authentication failure from the end of the scan to its start: validate answers in milliseconds, the scan fails only after verify has run every test. The rerun line is for a change on the server side — a setting, a permission, a quality profile — where the code and target/ (or build/) are unchanged.
… cortado aqui, o resto está no arquivo
O que lê, escreve e recusa
O contrato da própria skill, do SKILL.md, no texto original.
Class
build — the territory is skill_classes.build's override for this skill in @.claude/schemas/extensions.json: the root build file and .github/workflows/**, nothing else. ArchHook.java guard enforces it. docker-compose.yml is deliberately outside it.
Reads
the root pom.xml or build.gradle (coordinates, existing plugins, the JaCoCo excludes that sonar.coverage.exclusions is derived from), and .github/workflows/build.yml when it exists.
Writes
the scanner plugin and the sonar.* properties into the root build file — the only change to that file outside a feature round besides project-bootstrap's generation and the two installers' own setup — and the analysis step into the workflow when the server is external.
Never writes
a token, a password, or any credential value, in any file.
Delegates
the sonarqube compose service to docker-architect, the single owner of every service block.
Chained by
project-bootstrap (step 8.4, after Verify) and arch-adopt (step 7), and invocable by hand. Travels into the generated project (export.skills.include): arch-adopt runs inside the project, where project-bootstrap does not exist, and needs this skill there.
Fonte: SKILL.md § Contract, no nerviz e4cae27.
O que o guard faz com ela
build é a classe da skill, e cada skill dela tem território próprio. Quem chama decide qual território vale: sob o project-initializer, o do agent; da thread principal, nenhuma chamada passa. O texto à direita é o do próprio hook.
write_allow da classe sonarqube-setup
- ✓
pom.xml - ✓
build.gradle - ✓
.github/workflows/** qualquer caminho fora desta lista, quando a própria skill abre a faseuma chamada da thread principal com o /init-project aberto
A recusa sai com código 2 e a mensagem volta para o modelo.
Encadeada pelo project-initializer, as escritas dela levam o agent_type do agent e são julgadas por agent_classes.driver, **. Esta lista vale quando a skill roda num prompt próprio.
PreToolUse Skill(sonarqube-setup) # agent_type: project-initializer (class driver) guard call exit 0 # a classed agent's call leaves the phase as it was PreToolUse Write(../bank-app/pom.xml) guard write exit 0 # judged by agent_classes.driver, write_allow: **
# phase open: init-project (class orchestrator) PreToolUse Skill(sonarqube-setup) guard call exit 2 ❌ `sonarqube-setup` is class `build` and `init-project` (class `orchestrator`) is open — a design run does not materialize files. Record what is missing in the partial and in the consolidated spec, finish the run, then invoke `/sonarqube-setup` from a prompt of its own.
# in the generated project, on a prompt of its own > /sonarqube-setup … UserPromptSubmit audit prompt exit 0 guard prompt exit 0 # phase opened: sonarqube-setup (class build) PreToolUse Write(src/main/resources/application.yml) guard write exit 2 ❌ `sonarqube-setup` is class `build` — src/main/resources/application.yml is outside its territory. write_allow: pom.xml, build.gradle, .github/workflows/** Put the content in the spec as a code block, or finish the run and invoke the skill that owns this path. If the path is legitimately this skill's, widen skill_classes.build in .claude/schemas/extensions.json — retrying will not help.
Onde ela fica no trilho
Cada card é um passo do /init-project. A entrevista roda na sessão principal; os passos com fundo colorido rodam dentro do project-initializer; o git-publish volta para a sessão principal, depois do audit genesis. Passos tracejados só rodam quando se aplicam.