↑ ↓ to move · Enter to open · Esc to closeTap a result

Architecture
skill · class build · sonnet

sonarqube-setup

Wires the project to a SonarQube server: the scanner in the build file, and a CI step or a local container.

skill · buildsonneteffort low3 allowed pathstravels to the generated project
§ the skill's definition

sonarqube-setup, section by section

The sections of the skill's own SKILL.md, excerpted. Hover or click one to see which project rules, templates and references/ files it cites. The sections are also chips: click one to jump to it.

nerviz/.claude/skills/sonarqube-setup/SKILL.md

## 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:

  1. Server — "Is there already a SonarQube server or SonarCloud organization for this
  2. 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:

ToolExemplarWhere
maventemplates/pom-sonar.xml.exampleproperties into the root POM's <properties>, the plugin into <build><pluginManagement><plugins>
gradletemplates/build-gradle-sonar.examplethe 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).

… cut here, the rest is in the file

### 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.

… cut here, the rest is in the file

Open the full file on GitHub →
§ behavior

What it reads, writes and refuses

The skill's own contract, from SKILL.md, in the original wording.

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.

Source: SKILL.md § Contract, at nerviz e4cae27.

§ guard and hook

What the guard does with it

build is the skill's class, and each skill of it carries its own territory. Who calls it decides which territory applies: under project-initializer the agent's, from the main thread no call at all. The text on the right is the hook's own.

write_allow of class sonarqube-setup

  • ✓pom.xml
  • ✓build.gradle
  • ✓.github/workflows/**
  • any path outside this list, when the skill opens the phase itself
  • a call from the main thread while /init-project is open

A refusal exits with code 2 and the message goes back to the model.

Chained by project-initializer, its writes carry the agent's agent_type and are judged by agent_classes.driver, **. This list applies when the skill runs on a prompt of its own.

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: **
§ orchestration

Where it sits on the rail

Each card is a step of /init-project. The interview runs in the main session; the tinted steps run inside project-initializer; git-publish comes back to the main session, after audit genesis. Dashed steps run only when they apply.

← back to the chains