diff --git a/.gitignore b/.gitignore index 57e239f..2316f52 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ dependency-reduced-pom.xml # module. Not committed to keep the repo lean. The distribution build (dist # module) fetches its own copies through Maven and does not use this directory. daemon/plugins/*.jar + +# sonar-predictor scan output, written by `bin/sonar agent-scan`. Never commit. +.sonar-predictor/ diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bd90c07 --- /dev/null +++ b/NOTICE @@ -0,0 +1,99 @@ +sonar-predictor +Copyright 2025–2026 Amit Kumar and the sonar-predict contributors + +This product is licensed under the Apache License, Version 2.0 (see LICENSE). + +================================================================================ +THIRD-PARTY COMPONENTS +================================================================================ + +`sonar-predictor` invokes third-party analyzers and libraries at runtime. The +following components are NOT redistributed inside this repository (the +installable plugin) — the launcher downloads them on first invocation from +Maven Central. They are listed here for transparency about what your machine +runs after you install this plugin. + +------------------------------------------------------------------------------ +SonarSource analysis engine +------------------------------------------------------------------------------ + + org.sonarsource.sonarlint.core:sonarlint-analysis-engine + License: GNU Lesser General Public License v3.0 (LGPL-3.0) + Source : https://github.com/SonarSource/sonarlint-core + + The embedded analysis runtime — the same engine that powers SonarLint in + IDEs. The CLI and daemon link to it as a normal library dependency, which + is the use LGPL-3.0 is specifically designed to allow under any combining + license (including Apache 2.0). + +------------------------------------------------------------------------------ +SonarSource language analyzers +------------------------------------------------------------------------------ + + org.sonarsource.java :sonar-java-plugin + org.sonarsource.python :sonar-python-plugin + org.sonarsource.javascript:sonar-javascript-plugin + org.sonarsource.php :sonar-php-plugin + org.sonarsource.kotlin :sonar-kotlin-plugin + org.sonarsource.slang :sonar-go-plugin + org.sonarsource.slang :sonar-ruby-plugin + org.sonarsource.slang :sonar-scala-plugin + org.sonarsource.html :sonar-html-plugin + org.sonarsource.xml :sonar-xml-plugin + + License: SONAR Source-Available License v1.0 (SSALv1) + Text : https://www.sonarsource.com/license/ssal/ + + These are the per-language rule packs the analysis engine loads at runtime. + SonarSource relicensed them from LGPL v3 to SSALv1 in 2024. SSALv1 is + "source-available": it permits free internal use and free local execution + (what this tool does), and restricts using the analyzers to operate a + competing "Service Offering". Read the full license text at the link above + before using `sonar-predictor` to build a hosted service offering. + + No source code from these analyzers is included in this repository. + `sonar-predictor` invokes them only through the public SonarSource APIs + exposed by sonarlint-analysis-engine. + +------------------------------------------------------------------------------ +Build-time / direct-dependency Apache 2.0 components +------------------------------------------------------------------------------ + + info.picocli:picocli (CLI framework) + com.fasterxml.jackson.core:jackson-* (JSON serialization) + org.apache.maven.plugins:maven-* and the embedded + build-helper / central-publishing-maven-plugin (build tooling) + + All Apache License 2.0. + +------------------------------------------------------------------------------ +Runtime JRE auto-download (optional) +------------------------------------------------------------------------------ + + When no Java 17+ runtime is found on the user's machine, the plugin's + bootstrap launcher fetches a JRE from the URL configured in + `plugin/skills/sonar-predictor/config.env`. The public default uses the + Adoptium Temurin API (https://api.adoptium.net), which serves Eclipse + Temurin OpenJDK builds under the GNU General Public License v2 with the + Classpath Exception (GPL-2.0 WITH Classpath-exception-2.0). + + An air-gapped or corporate setup can replace this URL with a private JRE + mirror; the bootstrap does not require Adoptium specifically. + +================================================================================ +NOTES ON THE BUNDLED MAVEN CENTRAL ARTIFACT +================================================================================ + +The launcher currently downloads a single archive, +`sonar-predictor-dist-.zip`, from Maven Central. That archive (built +and published by this project's release workflow) contains the SonarSource +analyzer JARs alongside this project's own CLI and daemon jars, repackaged +unmodified. + +A future release will change the launcher to fetch each analyzer JAR +individually from Maven Central using its published coordinates, so that +SonarSource's own Maven Central distribution is the sole redistribution +channel and `sonar-predict-dist-*.zip` no longer carries third-party JARs. +This eliminates any redistribution question about the SSALv1-licensed +analyzers. The plugin's `config.env` already isolates the Maven repository +URL, so this transition is transparent to corporate Maven proxy setups. diff --git a/README.md b/README.md index ddac2bf..22ceb01 100644 --- a/README.md +++ b/README.md @@ -45,17 +45,16 @@ It is **scan-only** — it reads your source and reports findings, and never mod ## Quick start -Grab the self-contained bundle from the [latest release](https://github.com/RandomCodeSpace/sonar-predict/releases/latest) — it carries the CLI, the daemon and all 12 analyzers: +`sonar-predictor` installs as a Claude Code / GitHub Copilot CLI plugin from this repo's built-in marketplace. The plugin is tiny (kilobytes); the analyzer bundle is fetched lazily from Maven Central on first scan and cached locally for every subsequent run. -```sh -curl -L -o sonar-predictor.zip \ - https://github.com/RandomCodeSpace/sonar-predict/releases/download/v0.1.0/sonar-predict-skill-0.1.0.zip -unzip sonar-predictor.zip -d sonar-predictor - -./sonar-predictor/bin/sonar check src/Main.java +``` +/plugin marketplace add RandomCodeSpace/sonar-predict +/plugin install sonar-predictor@sonar-predict ``` -The launcher auto-discovers a Java 17+ runtime and starts the daemon on first use — no configuration. +The same two commands work on Claude Code and Copilot CLI — they share the `.claude-plugin` format. Two named scanner subagents come with the plugin: `sonar-scanner-claude` (model: haiku) and `sonar-scanner-copilot` (model: gpt-5-mini); selection is by agent name. + +Prefer the raw CLI? Grab the skill bundle directly from the [latest release](https://github.com/RandomCodeSpace/sonar-predict/releases/latest) and run `./bin/sonar` from the unpacked directory. The first invocation auto-discovers Java 17+ (or downloads one if none is found); every subsequent call is offline. ## Usage @@ -77,18 +76,26 @@ sonar --format json check --diff sonar --coverage target/site/jacoco/jacoco.xml --coverage-min 80 analyze . ``` -## Use it as an AI-agent skill +## Corporate / air-gapped setup + +The plugin's launcher reads a single configuration file at `plugin/skills/sonar-predictor/config.env`. Edit it (or set the same-named environment variable, which takes precedence) to point at a corporate Maven proxy, a private JRE mirror, or a vendored bundle version — no code changes. -`sonar-predictor` is packaged as a [Claude Code](https://claude.com/claude-code) skill — an -agent discovers it and runs it as a quality gate automatically. Install the bundle into your -skills directory: +| Key | What it controls | +|-----|------------------| +| `SONAR_MAVEN_REPO_URL` | Where the analyzer bundle is downloaded from. Default: Maven Central. | +| `SONAR_BUNDLE_VERSION` | Bundle version pin. Bump in lockstep with each plugin release. | +| `SONAR_MIN_JAVA_VERSION` | Minimum Java major version required. Default: `17`. | +| `SONAR_JRE_URL_TEMPLATE` | JRE source for auto-download (when none is on the system). Tokens `{os}` `{arch}` `{version}` are substituted at runtime. Default: Adoptium Temurin API. | +| `SONAR_JRE_VERSION` | JRE version to fetch. Default: `17`. | +| `SONAR_DISABLE_JRE_AUTODOWNLOAD` | Set to `1` to refuse the JRE auto-download (corp-policy escape hatch). | + +For a fully pre-staged install — no network at all on the developer machine — extract a skill bundle by hand into a known location and point the launcher at it: ```sh -unzip sonar-predict-skill-0.1.0.zip -d ~/.claude/skills/ +export SONAR_PREDICTOR_HOME=/opt/sonar-predictor ``` -The bundled `SKILL.md` tells the agent when to run it and how to read its output; the CLI's -own `--help` is the single source of truth for commands. +The forking workflow: clone this repo, edit `plugin/skills/sonar-predictor/config.env`, push to your fork, `/plugin marketplace add /sonar-predict`. One file. ## How it works @@ -134,6 +141,15 @@ smells, vulnerabilities and security hotspots — and imports coverage. It is a first-pass, **not** a replacement for release-time gates: the SonarQube server's deep cross-file taint analysis, Fortify, and dependency/CVE scanning remain their own tools. -## License +## License & third-party components + +This project (the CLI, daemon, protocol, dist module, plugin scaffolding) is licensed under the [Apache License 2.0](LICENSE). + +The SonarSource analyzers and engine that `sonar-predictor` invokes at runtime are **third-party components with their own licenses**, listed in [NOTICE](NOTICE). Headline points: + +- **`sonarlint-analysis-engine`** (the embedded analysis runtime, by SonarSource) — **LGPL v3**. +- **SonarSource language analyzers** (`sonar-java`, `sonar-python`, `sonar-javascript`, `sonar-php`, `sonar-kotlin`, `sonar-go`, `sonar-ruby`, `sonar-scala`, `sonar-html`, `sonar-xml`) — **SONAR Source-Available License v1.0 (SSALv1)** since SonarSource's 2024 license change. Source-available, free for internal use, with restrictions on operating a competing "Service Offering". + +The plugin **does not redistribute** these JARs — its launcher fetches them from Maven Central on first invocation (the same channel SonarSource themselves publish to). Editing `SONAR_MAVEN_REPO_URL` to a corporate mirror that proxies Maven Central is equivalent. None of this project's source code is derived from SonarSource code. -[Apache License 2.0](LICENSE). +For a deeper inventory and the legal text of each license, see [NOTICE](NOTICE). If your environment's policy needs the analyzer JARs to come from somewhere other than Maven Central, the corporate / air-gapped setup above is your knob. diff --git a/dist/README.md b/dist/README.md index c8d8f58..66ba386 100644 --- a/dist/README.md +++ b/dist/README.md @@ -1,71 +1,61 @@ # sonar-predictor :: dist -The distribution module. It builds **sonar-predictor** as a self-contained -Claude Code agent skill — a directory an AI agent can use with zero setup. +The distribution module. It assembles the **skill bundle** that `sonar-predictor`'s plugin launcher downloads at first run. -## Build - -From the repository root: +## What this module builds ```sh mvn package ``` -This produces, under `dist/target/`: +produces, under `dist/target/`: - `skill/sonar-predictor/` — the exploded skill bundle: - - `SKILL.md` — the agent-facing skill definition (the only file loaded into context) + - `SKILL.md` — the agent-facing skill definition - `bin/sonar`, `bin/sonar.bat` — launchers that auto-discover a Java 17+ runtime - - `lib/` — the CLI and daemon fat jars + - `lib/` — the CLI and daemon shaded fat jars - `plugins/` — the 10 SonarSource analyzer plugins -- `sonar-predict-skill-.zip` — the same tree zipped, for non-skill use - (manual install, or transfer to an air-gapped host) +- `sonar-predict-skill-.zip` — the same tree zipped, attached to the GitHub Release and **published to Maven Central as `io.github.randomcodespace.sonarpredict:sonar-predictor-dist::zip`**. The plugin's bootstrap launcher pulls this artifact (from Maven Central, or a corporate proxy of it) on first invocation. -The analyzer plugins are fetched through Maven, so any mirror, Nexus, or -corporate proxy configured in your `settings.xml` is honored — there are no -hardcoded download URLs. +The analyzer plugins are resolved through Maven, so any mirror, Nexus, or corporate proxy configured in your `settings.xml` is honored — there are no hardcoded download URLs in the build. -## Install as a Claude Code skill +## How users install `sonar-predictor` -```sh -cp -r dist/target/skill/sonar-predictor ~/.claude/skills/ +End users do not interact with this module directly. They install the plugin from the in-repo `/plugin/` directory through their AI tool's marketplace: + +``` +/plugin marketplace add RandomCodeSpace/sonar-predict +/plugin install sonar-predictor@sonar-predict ``` -The skill is then available to agents in any project. +The plugin's launcher (`/plugin/skills/sonar-predictor/bin/sonar`) downloads the bundle this module builds from Maven Central, verifies its SHA-1, caches it under `~/.cache/sonar-predictor//`, and execs the cached `bin/sonar` on every subsequent call. -## Prerequisites +For raw / non-plugin use (manual or air-gapped install), the same skill bundle zip is attached to each GitHub Release. -The skill runs on a system Java; nothing is downloaded at runtime. +## Prerequisites at runtime **Required:** -- **Java 17+** (JDK or JRE) — the CLI and daemon are JVM processes. `bin/sonar` auto-discovers it (`JAVA_HOME` → `PATH` → common install locations); one must exist somewhere on the machine. +- **Java 17+** (JDK or JRE) — the CLI and daemon are JVM processes. `bin/sonar` auto-discovers it (`JAVA_HOME` → `PATH` → common install locations); the plugin's bootstrap also auto-downloads one from `SONAR_JRE_URL_TEMPLATE` (defaulting to Adoptium Temurin) if none is found. - **Linux or macOS** — the daemon uses Unix domain sockets. Windows is not yet supported (the TCP-socket fallback is a TODO). - A writable temp directory and ~1 GB free RAM (the daemon embeds the analysis engine; idle ~150–350 MB, up to ~1 GB during JS/TS analysis). - ~165 MB disk for the bundle. **For specific features:** -- **`git`** on `PATH` — required for `sonar check --diff` (the primary agent workflow). `sonar check ` and `sonar analyze ` work without it. -- **Node.js 18.17+** on `PATH` — required for JavaScript/TypeScript/CSS analysis. Without it those 3 languages are skipped (the other 9 still analyze). +- **`git`** on `PATH` — required for `sonar check --diff` (the primary agent workflow). +- **Node.js 18.17+** on `PATH` — required for JavaScript/TypeScript/CSS analysis. Without it those 3 languages are skipped. -**Not required:** no network at runtime (fully offline after install), no per-language SDK or compiler, no Maven (build-time only). - -If no Java 17+ is found, the `bin/sonar` launcher exits with a clear message. +**Not required after first invocation:** no network, no per-language SDK or compiler, no Maven. ## Releasing -A release is cut by pushing a version tag. The -`.github/workflows/publish.yml` GitHub Actions pipeline then: +A release is cut by pushing a version tag. The `.github/workflows/publish.yml` GitHub Actions pipeline then: -1. derives the release version from the tag (`v0.1.0` → `0.1.0`) and strips - `-SNAPSHOT` — Maven Central rejects snapshot versions, -2. builds, tests, GPG-signs and deploys the library modules - `protocol`, `daemon` and `cli` to Maven Central via the Sonatype - Central Portal (this `dist` module sets `maven.deploy.skip=true`, so it - is built but never staged to Central), -3. creates a GitHub Release carrying two bundles: - - **`sonar-predict--src.zip`** — the whole repository as a - `git archive` of `HEAD`, - - **`sonar-predict-skill-.zip`** — the assembled skill bundle. +1. derives the release version from the tag (`v0.1.0` → `0.1.0`) and strips `-SNAPSHOT`, +2. builds, tests, GPG-signs and deploys the library modules `protocol`, `daemon` and `cli` to Maven Central via the Sonatype Central Portal, +3. publishes this `dist` module's skill bundle zip to Maven Central as `sonar-predictor-dist::zip` (this is the artifact the plugin's bootstrap downloads), +4. creates a GitHub Release carrying two bundles: + - `sonar-predict--src.zip` — the whole repository as a `git archive` of `HEAD`, + - `sonar-predict-skill-.zip` — the assembled skill bundle. To cut release `X.Y.Z`: @@ -74,13 +64,11 @@ git tag vX.Y.Z git push origin vX.Y.Z ``` -The workflow can also be run manually from the **Actions** tab -(`workflow_dispatch`), supplying the version explicitly. +When the release is cut, bump the plugin launcher's pinned bundle version in `plugin/skills/sonar-predictor/config.env` (`SONAR_BUNDLE_VERSION`) in the same commit, so a freshly installed plugin downloads the bundle that matches it. -### Required repository secrets +The workflow can also be run manually from the **Actions** tab (`workflow_dispatch`), supplying the version explicitly. -The pipeline needs these secrets on the `RandomCodeSpace/sonar-predict` -repository (Settings → Secrets and variables → Actions): +### Required repository secrets | Secret | Purpose | |--------|---------| @@ -89,6 +77,4 @@ repository (Settings → Secrets and variables → Actions): | `MAVEN_GPG_PRIVATE_KEY` | ASCII-armored GPG private key used to sign artifacts | | `MAVEN_GPG_PASSPHRASE` | Passphrase for that GPG key (omit if the key has none) | -`GITHUB_TOKEN` is provided automatically and needs no setup. The -`io.github.randomcodespace` namespace must also be registered and verified -on the Sonatype Central Portal. +`GITHUB_TOKEN` is provided automatically. The `io.github.randomcodespace` namespace must be registered and verified on the Sonatype Central Portal. diff --git a/plugin/agents/sonar-scanner-claude.md b/plugin/agents/sonar-scanner-claude.md index 2c81e56..15dc2ee 100644 --- a/plugin/agents/sonar-scanner-claude.md +++ b/plugin/agents/sonar-scanner-claude.md @@ -15,40 +15,47 @@ and safe. You are given a scope: a directory, a repository, a file list, or "the git changeset". Scan exactly that — no more, no less. -## Running the scanner — keep its output OUT of your context +## Running the scanner — `bin/sonar agent-scan` -Invoke the `sonar-predictor` skill (via the `Skill` tool). It exposes the -analyzer CLI and tells you how to invoke it — read its `SKILL.md` and the -tool's own `--help`. Do not assume command syntax. +Invoke the `sonar-predictor` skill (via the `Skill` tool). The skill's +`bin/sonar agent-scan [scope]` subcommand writes the full JSON output to +`.sonar-predictor/scan.json` at the project root and adds that path to +`.gitignore` on first use, so the analyzer's large output never enters your +context. It prints a compact summary on stdout — issue count, severity +breakdown, file path. -**Critical:** a full scan emits a large JSON document — bigger than a context -window. Never let that output land in your context, and never `cat` or read -the raw JSON. Always: +Default scope is the current git changeset (no arg). Pass explicit scope +after `agent-scan`: -1. Redirect JSON output to a temp file (e.g. `... --format json ... > "$J" 2>&1`). -2. Extract a small summary from that file with `jq`. -3. Report only those small extracts, then delete the temp file. +- `agent-scan` — git changeset (default) +- `agent-scan check src/Main.java` — specific files +- `agent-scan analyze src/` — whole directory + +Read `bin/sonar --help` for the underlying command vocabulary if you need +something unusual; don't guess flag names. Exit codes: `0` clean, `1` issues found (a normal result, not a failure), -`2` tool error (report it verbatim). +`2` tool error. + +## What to report -A `jq` recipe — issues are nested under `.files[].issues[]`: +The stdout summary from `agent-scan` is your top-line: issue count, severity +counts, and the `.sonar-predictor/scan.json` path. Report that verbatim plus +a one-line verdict. + +If your caller wants drill-down (specific files, specific rules, criticals +only), query the JSON file with `jq` — never read it raw. Issues are nested +under `.files[].issues[]`: ```sh -jq -r '.issueCount' "$J" -jq -rc '[.files[].issues[].severity]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' "$J" -jq -rc '[.files[].issues[].type]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' "$J" +jq -rc '[.files[]?.issues[]?.type] | group_by(.) | map("\(.[0])=\(length)") | join(" ")' .sonar-predictor/scan.json jq -r '["BLOCKER","CRITICAL","MAJOR","MINOR","INFO"] as $r | [.files[] as $f | $f.issues[] | {s:.severity,k:.ruleKey,f:($f.file|split("/")|last),l:.startLine,m:.message}] | sort_by(.s as $x | $r|index($x)) | .[0:8][] - | " \(.s) \(.k) \(.f):\(.l) \(.m[0:80])"' "$J" -jq -rc '.warnings' "$J" + | " \(.s) \(.k) \(.f):\(.l) \(.m[0:80])"' .sonar-predictor/scan.json +jq -rc '.warnings' .sonar-predictor/scan.json ``` -## What to report - -A concise summary — never the raw JSON, never a file-by-file dump: the total -issue count, the severity and type breakdown, the ~8 highest-severity findings -as `ruleKey file:line message`, and any analyzer warnings (a skipped language -or a crashed sensor means files went unanalyzed). State the verdict plainly. +Never dump raw JSON, never do a file-by-file walk. Concise, actionable summary +only. diff --git a/plugin/agents/sonar-scanner-copilot.md b/plugin/agents/sonar-scanner-copilot.md index a81c9c0..3c55a61 100644 --- a/plugin/agents/sonar-scanner-copilot.md +++ b/plugin/agents/sonar-scanner-copilot.md @@ -15,40 +15,47 @@ and safe. You are given a scope: a directory, a repository, a file list, or "the git changeset". Scan exactly that — no more, no less. -## Running the scanner — keep its output OUT of your context +## Running the scanner — `bin/sonar agent-scan` -Invoke the `sonar-predictor` skill (via the `skill` tool). It exposes the -analyzer CLI and tells you how to invoke it — read its `SKILL.md` and the -tool's own `--help`. Do not assume command syntax. +Invoke the `sonar-predictor` skill (via the `skill` tool). The skill's +`bin/sonar agent-scan [scope]` subcommand writes the full JSON output to +`.sonar-predictor/scan.json` at the project root and adds that path to +`.gitignore` on first use, so the analyzer's large output never enters your +context. It prints a compact summary on stdout — issue count, severity +breakdown, file path. -**Critical:** a full scan emits a large JSON document — bigger than a context -window. Never let that output land in your context, and never `view` or read -the raw JSON. Always: +Default scope is the current git changeset (no arg). Pass explicit scope +after `agent-scan`: -1. Redirect JSON output to a temp file (e.g. `... --format json ... > "$J" 2>&1`). -2. Extract a small summary from that file with `jq`. -3. Report only those small extracts, then delete the temp file. +- `agent-scan` — git changeset (default) +- `agent-scan check src/Main.java` — specific files +- `agent-scan analyze src/` — whole directory + +Read `bin/sonar --help` for the underlying command vocabulary if you need +something unusual; don't guess flag names. Exit codes: `0` clean, `1` issues found (a normal result, not a failure), -`2` tool error (report it verbatim). +`2` tool error. + +## What to report -A `jq` recipe — issues are nested under `.files[].issues[]`: +The stdout summary from `agent-scan` is your top-line: issue count, severity +counts, and the `.sonar-predictor/scan.json` path. Report that verbatim plus +a one-line verdict. + +If your caller wants drill-down (specific files, specific rules, criticals +only), query the JSON file with `jq` — never `view` it raw. Issues are nested +under `.files[].issues[]`: ```sh -jq -r '.issueCount' "$J" -jq -rc '[.files[].issues[].severity]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' "$J" -jq -rc '[.files[].issues[].type]|group_by(.)|map("\(.[0])=\(length)")|join(" ")' "$J" +jq -rc '[.files[]?.issues[]?.type] | group_by(.) | map("\(.[0])=\(length)") | join(" ")' .sonar-predictor/scan.json jq -r '["BLOCKER","CRITICAL","MAJOR","MINOR","INFO"] as $r | [.files[] as $f | $f.issues[] | {s:.severity,k:.ruleKey,f:($f.file|split("/")|last),l:.startLine,m:.message}] | sort_by(.s as $x | $r|index($x)) | .[0:8][] - | " \(.s) \(.k) \(.f):\(.l) \(.m[0:80])"' "$J" -jq -rc '.warnings' "$J" + | " \(.s) \(.k) \(.f):\(.l) \(.m[0:80])"' .sonar-predictor/scan.json +jq -rc '.warnings' .sonar-predictor/scan.json ``` -## What to report - -A concise summary — never the raw JSON, never a file-by-file dump: the total -issue count, the severity and type breakdown, the ~8 highest-severity findings -as `ruleKey file:line message`, and any analyzer warnings (a skipped language -or a crashed sensor means files went unanalyzed). State the verdict plainly. +Never dump raw JSON, never do a file-by-file walk. Concise, actionable summary +only. diff --git a/plugin/skills/sonar-predictor/SKILL.md b/plugin/skills/sonar-predictor/SKILL.md index ecfd2ea..abeef0d 100644 --- a/plugin/skills/sonar-predictor/SKILL.md +++ b/plugin/skills/sonar-predictor/SKILL.md @@ -11,10 +11,12 @@ An offline SonarSource pre-push quality gate — runs the genuine analyzers loca Run `./bin/sonar` from this skill's base directory (the folder with this `SKILL.md`), or by its absolute path — it is not on `PATH`. The first invocation downloads the analyzer bundle (~150 MB) from Maven Central into a user cache; every subsequent call runs from that cache with no network. **Read the tool's own help before invoking it:** `./bin/sonar --help` lists the commands and the global options, and `./bin/sonar --help` gives a command's own options and exact argument order. The skill scans a git changeset or explicit files and directories and reports in a chosen format — the help states the precise flags and where each one goes. That generated help is the single source of truth; this `SKILL.md` deliberately does not restate command syntax, which would drift. Do not guess flag names or their placement — read the help. +**Agent invocation pattern — `./bin/sonar agent-scan [scope]`.** A wrapper subcommand that bakes the out-of-context discipline into the tool: it runs the scan with `--format json` redirected to `.sonar-predictor/scan.json` at the project root, adds `.sonar-predictor/` to `.gitignore` on first use (when inside a git repo), and prints a compact summary to stdout — issue count, severity breakdown, file path. The calling agent reports that summary and points its caller at the file; deeper drill-down happens with `jq` on the file, on demand. With no scope argument, `agent-scan` defaults to `check --diff` (the git changeset); pass an explicit scope (`agent-scan analyze src/`, `agent-scan check src/Main.java`, etc.) to scan something else. All flags forward to the underlying CLI. + Exit codes: `0` clean, `1` issues found, `2` tool error. Acting on findings: fix `BUG`/`VULNERABILITY`/`SECURITY_HOTSPOT` and `CRITICAL`/`MAJOR` first. This is a fast first-pass gate, not the release gate — fix the real issues and move on. -**Air-gapped / pre-staged installs.** Set `SONAR_PREDICTOR_HOME=/path/to/extracted/sonar-predictor` to point the launcher at a pre-downloaded bundle and skip the first-run download. +**Configuration / corporate environments.** The launcher reads `config.env` next to this `SKILL.md` — Maven proxy URL, JRE download URL, bundle and Java version pins. Edit it (or override with same-named env vars) to point at a corporate Nexus / Artifactory mirror and a private JRE source. The defaults use Maven Central and Adoptium Temurin's public API. `SONAR_PREDICTOR_HOME=/path/to/extracted/sonar-predictor` skips the bundle download entirely for fully air-gapped / pre-staged installs. **Plugin-bundled agent variants.** Two named scanner subagents ship with this plugin: invoke `sonar-scanner-claude` on Claude Code (model: haiku) or `sonar-scanner-copilot` on GitHub Copilot CLI (model: gpt-5-mini). Selection is by agent name — pick the one matching your platform. diff --git a/plugin/skills/sonar-predictor/bin/sonar b/plugin/skills/sonar-predictor/bin/sonar index 077e7b7..150a49e 100755 --- a/plugin/skills/sonar-predictor/bin/sonar +++ b/plugin/skills/sonar-predictor/bin/sonar @@ -1,37 +1,74 @@ #!/usr/bin/env bash # Bootstrap wrapper for the sonar-predictor skill bundle. # -# On first invocation, downloads the analyzer bundle (~150 MB) from Maven -# Central into the user cache, verifies its SHA-1, and unpacks it. Every -# subsequent call exec's the cached launcher directly — no network. +# On first invocation: +# 1. loads ../config.env (corporate Maven proxy, JRE mirror, version pin), +# 2. downloads sonar-predictor-dist-.zip from SONAR_MAVEN_REPO_URL, +# verifies its SHA-1 sidecar, and unpacks it into the user cache, +# 3. ensures a Java >= SONAR_MIN_JAVA_VERSION is available — downloading +# one from SONAR_JRE_URL_TEMPLATE into the user cache if not found +# (the bundle's own launcher already searches JAVA_HOME / PATH / common +# install dirs; this only kicks in when that comes up empty), +# 4. exec's the cached launcher. +# Every subsequent call exec's the cached launcher directly with no network. # -# Override the bundle location for air-gapped or pre-staged installs: +# Override the bundle location for air-gapped / pre-staged installs: # SONAR_PREDICTOR_HOME=/path/to/extracted/sonar-predictor +# +# All other knobs (Maven proxy, JRE source, version pin, min Java) live in +# ../config.env; each one is also an env-var override. set -euo pipefail -# Pinned to the bundle version published to Maven Central. Bumped in lockstep -# with the plugin's release: each tagged plugin release publishes a matching -# sonar-predictor-dist-.zip and updates this pin. -# -# Note: v0.1.2's dist artifact on Central is the (now-removed) plugin-bundle -# shape, not the skill-bundle this wrapper expects. v0.1.1 is the last good -# skill bundle; v0.1.3 onwards will be skill-shaped again. -VERSION="0.1.1" +# -------- 1. load config ----------------------------------------------------- + +HERE="$(cd "$(dirname "$0")" && pwd)" +CONFIG="$HERE/../config.env" + +if [ -f "$CONFIG" ]; then + # Parse KEY=VALUE lines; env vars already in scope take precedence. + while IFS='=' read -r key value; do + case "$key" in + ''|'#'*) continue ;; + esac + key="${key%%[[:space:]]*}" + if [ -z "${!key:-}" ]; then + printf -v "$key" '%s' "$value" + export "$key" + fi + done < "$CONFIG" +fi +# Defaults if config.env is missing or empty. +: "${SONAR_MAVEN_REPO_URL:=https://repo1.maven.org/maven2}" +: "${SONAR_BUNDLE_VERSION:=0.1.1}" +: "${SONAR_MIN_JAVA_VERSION:=17}" +: "${SONAR_JRE_URL_TEMPLATE:=https://api.adoptium.net/v3/binary/latest/{version}/ga/{os}/{arch}/jre/hotspot/normal/eclipse}" +: "${SONAR_JRE_VERSION:=17}" +: "${SONAR_DISABLE_JRE_AUTODOWNLOAD:=}" + +VERSION="$SONAR_BUNDLE_VERSION" GROUP_PATH="io/github/randomcodespace/sonarpredict" ARTIFACT="sonar-predictor-dist" -BASE_URL="https://repo1.maven.org/maven2/${GROUP_PATH}/${ARTIFACT}/${VERSION}" +BUNDLE_URL_BASE="${SONAR_MAVEN_REPO_URL%/}/${GROUP_PATH}/${ARTIFACT}/${VERSION}" +CACHE_ROOT="${XDG_CACHE_HOME:-$HOME/.cache}/sonar-predictor" if [ -n "${SONAR_PREDICTOR_HOME:-}" ]; then SKILL_DIR="$SONAR_PREDICTOR_HOME" else - CACHE_ROOT="${XDG_CACHE_HOME:-$HOME/.cache}/sonar-predictor" SKILL_DIR="$CACHE_ROOT/$VERSION" fi - REAL_SONAR="$SKILL_DIR/bin/sonar" +# -------- helpers ------------------------------------------------------------ + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "sonar-predictor: required command '$1' not found on PATH" >&2 + exit 2 + fi +} + sha1_of() { if command -v sha1sum >/dev/null 2>&1; then sha1sum "$1" | awk '{print $1}' @@ -43,41 +80,138 @@ sha1_of() { fi } -if [ ! -x "$REAL_SONAR" ]; then - for cmd in curl unzip; do - if ! command -v "$cmd" >/dev/null 2>&1; then - echo "sonar-predictor: required command '$cmd' not found on PATH" >&2 - exit 2 +java_major_of() { + local v raw maj + v="$("$1" -version 2>&1 | head -1)" || return 1 + raw="$(printf '%s' "$v" | sed -E 's/^[^"]*"([^"]+)".*/\1/')" + [ -n "$raw" ] || return 1 + maj="$(printf '%s' "$raw" | cut -d. -f1)" + if [ "$maj" = "1" ]; then + maj="$(printf '%s' "$raw" | cut -d. -f2)" + fi + printf '%s' "$maj" +} + +found_java_meets_min() { + # The bundle's own launcher searches JAVA_HOME, PATH and common dirs. We + # only need to know whether *some* working Java >= the minimum exists; if + # so, we hand off to it. Otherwise we install one and set JAVA_HOME so the + # bundle's launcher picks it up. + local cand major + for cand in \ + "${JAVA_HOME:-}/bin/java" \ + "$(command -v java 2>/dev/null || true)" \ + /usr/lib/jvm/*/bin/java \ + /usr/java/*/bin/java \ + /Library/Java/JavaVirtualMachines/*/Contents/Home/bin/java \ + "${HOME:-/nonexistent}"/.sdkman/candidates/java/*/bin/java \ + /opt/java/*/bin/java \ + /opt/*/bin/java + do + [ -x "$cand" ] || continue + major="$(java_major_of "$cand" 2>/dev/null || true)" + [ -n "$major" ] || continue + if [ "$major" -ge "$SONAR_MIN_JAVA_VERSION" ] 2>/dev/null; then + return 0 fi done + return 1 +} + +detect_os_arch() { + case "$(uname -s)" in + Linux*) OS=linux ;; + Darwin*) OS=mac ;; + *) echo "sonar-predictor: unsupported OS '$(uname -s)' for JRE auto-download" >&2; exit 2 ;; + esac + case "$(uname -m)" in + x86_64|amd64) ARCH=x64 ;; + aarch64|arm64) ARCH=aarch64 ;; + *) echo "sonar-predictor: unsupported arch '$(uname -m)' for JRE auto-download" >&2; exit 2 ;; + esac +} + +install_jre() { + local jre_dir="$CACHE_ROOT/jre/$SONAR_JRE_VERSION" + if [ -x "$jre_dir/bin/java" ]; then + export JAVA_HOME="$jre_dir" + return 0 + fi - echo "sonar-predictor: first run — downloading $VERSION bundle from Maven Central..." >&2 + case "${SONAR_DISABLE_JRE_AUTODOWNLOAD:-}" in + 1|true|yes|TRUE|YES) + echo "sonar-predictor: no Java >= $SONAR_MIN_JAVA_VERSION found, and SONAR_DISABLE_JRE_AUTODOWNLOAD is set" >&2 + exit 2 + ;; + esac + + require_cmd curl + require_cmd tar + detect_os_arch + + local url="$SONAR_JRE_URL_TEMPLATE" + url="${url//\{os\}/$OS}" + url="${url//\{arch\}/$ARCH}" + url="${url//\{version\}/$SONAR_JRE_VERSION}" + + echo "sonar-predictor: no Java $SONAR_MIN_JAVA_VERSION+ found — downloading JRE $SONAR_JRE_VERSION ($OS/$ARCH) from $url" >&2 + mkdir -p "$(dirname "$jre_dir")" + + local tmp; tmp="$(mktemp -d)" + curl -fsSL --retry 3 -o "$tmp/jre.tgz" "$url" + ( cd "$tmp" && tar -xzf jre.tgz ) + + # Find the JRE root (the dir whose bin/java is executable). Handles both + # Linux layout (.../jdk-17.../bin/java) and macOS layout + # (.../jdk-17.../Contents/Home/bin/java). + local java_path home + java_path="$(find "$tmp" -mindepth 2 -path '*/bin/java' -type f -executable 2>/dev/null | head -1 || true)" + if [ -z "$java_path" ]; then + echo "sonar-predictor: downloaded JRE has no bin/java; archive layout unrecognised" >&2 + rm -rf "$tmp" + exit 2 + fi + home="${java_path%/bin/java}" + mv "$home" "$jre_dir" + rm -rf "$tmp" + + if [ ! -x "$jre_dir/bin/java" ]; then + echo "sonar-predictor: JRE install completed but $jre_dir/bin/java is missing" >&2 + exit 2 + fi + export JAVA_HOME="$jre_dir" +} + +# -------- 2. ensure bundle is cached ---------------------------------------- + +if [ ! -x "$REAL_SONAR" ]; then + require_cmd curl + require_cmd unzip + + echo "sonar-predictor: first run — downloading $VERSION bundle from $SONAR_MAVEN_REPO_URL..." >&2 mkdir -p "$(dirname "$SKILL_DIR")" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT - ZIP="$TMP/bundle.zip" - SHA="$TMP/bundle.zip.sha1" - - curl -fsSL --retry 3 -o "$ZIP" "$BASE_URL/$ARTIFACT-$VERSION.zip" - curl -fsSL --retry 3 -o "$SHA" "$BASE_URL/$ARTIFACT-$VERSION.zip.sha1" + curl -fsSL --retry 3 -o "$TMP/bundle.zip" "$BUNDLE_URL_BASE/$ARTIFACT-$VERSION.zip" + curl -fsSL --retry 3 -o "$TMP/bundle.zip.sha1" "$BUNDLE_URL_BASE/$ARTIFACT-$VERSION.zip.sha1" - EXPECTED="$(awk '{print $1}' "$SHA")" - ACTUAL="$(sha1_of "$ZIP")" + EXPECTED="$(awk '{print $1}' "$TMP/bundle.zip.sha1")" + ACTUAL="$(sha1_of "$TMP/bundle.zip")" if [ "$EXPECTED" != "$ACTUAL" ]; then echo "sonar-predictor: SHA-1 verification failed (expected $EXPECTED, got $ACTUAL)" >&2 exit 2 fi - unzip -q "$ZIP" -d "$TMP/extracted" + unzip -q "$TMP/bundle.zip" -d "$TMP/extracted" if [ ! -d "$TMP/extracted/sonar-predictor" ]; then - echo "sonar-predictor: unexpected bundle layout (no sonar-predictor/ dir at zip root)" >&2 + echo "sonar-predictor: unexpected bundle layout (no sonar-predictor/ at zip root)" >&2 exit 2 fi - # If another invocation populated the cache while we were downloading, - # respect its work — atomically rename, ignoring failure on race. mv "$TMP/extracted/sonar-predictor" "$SKILL_DIR" 2>/dev/null || true + trap - EXIT + rm -rf "$TMP" if [ ! -x "$REAL_SONAR" ]; then echo "sonar-predictor: bootstrap completed but $REAL_SONAR is not executable" >&2 @@ -85,4 +219,63 @@ if [ ! -x "$REAL_SONAR" ]; then fi fi +# -------- 3. ensure a usable Java is available ------------------------------ + +if ! found_java_meets_min; then + install_jre +fi + +# -------- 4. hand off ------------------------------------------------------- + +if [ -n "${JAVA_HOME:-}" ]; then + export JAVA_HOME + export PATH="$JAVA_HOME/bin:$PATH" +fi + +# The `agent-scan` subcommand bakes the out-of-context discipline into the +# tool: run the scan with JSON output redirected to .sonar-predictor/scan.json +# at the project root, add that path to .gitignore on first use, and print a +# compact summary on stdout. The calling agent reports the summary and the +# file path; deeper drill-down happens with jq on the file, on demand. +agent_scan() { + local scan_file=".sonar-predictor/scan.json" + mkdir -p "$(dirname "$scan_file")" + + # Add the scan dir to .gitignore on first use, only inside a git repo. + if git rev-parse --git-dir >/dev/null 2>&1; then + if [ ! -f .gitignore ] || ! grep -qF '.sonar-predictor/' .gitignore; then + printf '\n# sonar-predictor scan output (do not commit)\n.sonar-predictor/\n' >> .gitignore + fi + fi + + # No args -> default to `check --diff` (the git changeset). + if [ $# -eq 0 ]; then + set -- check --diff + fi + + local rc=0 + set +e + "$REAL_SONAR" --format json "$@" > "$scan_file" 2>&1 + rc=$? + set -e + + if command -v jq >/dev/null 2>&1; then + local total sev + total="$(jq -r '(.issueCount // ([.files[]?.issues[]?] | length))' "$scan_file" 2>/dev/null)" + sev="$(jq -rc '[.files[]?.issues[]?.severity] | group_by(.) | map("\(.[0])=\(length)") | join(" ")' "$scan_file" 2>/dev/null)" + echo "sonar-predictor: ${total:-?} issues written to $scan_file" + [ -n "$sev" ] && echo " severity: $sev" + echo " query: jq '...' $scan_file" + else + echo "sonar-predictor: scan complete -> $scan_file (install jq for an inline summary)" + fi + return "$rc" +} + +if [ "${1:-}" = "agent-scan" ]; then + shift + agent_scan "$@" + exit "$?" +fi + exec "$REAL_SONAR" "$@" diff --git a/plugin/skills/sonar-predictor/bin/sonar.bat b/plugin/skills/sonar-predictor/bin/sonar.bat index c60592d..98b4430 100644 --- a/plugin/skills/sonar-predictor/bin/sonar.bat +++ b/plugin/skills/sonar-predictor/bin/sonar.bat @@ -1,20 +1,34 @@ @echo off rem Bootstrap wrapper for the sonar-predictor skill bundle (Windows). rem -rem On first invocation, downloads the analyzer bundle (~150 MB) from Maven -rem Central into the user cache, verifies its SHA-1, and unpacks it. Every -rem subsequent call dispatches to the cached launcher directly — no network. +rem Loads ../config.env (KEY=VALUE lines, # for comments) and uses the +rem configured Maven proxy + bundle version to download the analyzer bundle +rem on first invocation. Env vars of the same name take precedence over the +rem file. rem -rem Override the bundle location for air-gapped or pre-staged installs: +rem Java auto-install is NOT yet supported on Windows — install Java 17+ +rem manually (or set JAVA_HOME). The bundle's own launcher will discover it. +rem +rem Override the bundle location for air-gapped / pre-staged installs: rem set SONAR_PREDICTOR_HOME=C:\path\to\extracted\sonar-predictor setlocal enabledelayedexpansion -rem v0.1.2's dist artifact on Central is the (now-removed) plugin-bundle shape, -rem not the skill-bundle this wrapper expects. v0.1.1 is the last good skill -rem bundle; v0.1.3 onwards will be skill-shaped again. -set "VERSION=0.1.1" -set "BASE_URL=https://repo1.maven.org/maven2/io/github/randomcodespace/sonarpredict/sonar-predictor-dist/%VERSION%" +rem ---- 1. load config.env (env vars already set win) ---- +set "CONFIG=%~dp0..\config.env" +if exist "%CONFIG%" ( + for /f "usebackq eol=# tokens=1,* delims==" %%a in ("%CONFIG%") do ( + if not "%%a"=="" if not defined %%a set "%%a=%%b" + ) +) + +rem ---- defaults ---- +if not defined SONAR_MAVEN_REPO_URL set "SONAR_MAVEN_REPO_URL=https://repo1.maven.org/maven2" +if not defined SONAR_BUNDLE_VERSION set "SONAR_BUNDLE_VERSION=0.1.1" + +set "VERSION=%SONAR_BUNDLE_VERSION%" +set "ARTIFACT=sonar-predictor-dist" +set "BUNDLE_URL_BASE=%SONAR_MAVEN_REPO_URL%/io/github/randomcodespace/sonarpredict/sonar-predictor-dist/%VERSION%" if defined SONAR_PREDICTOR_HOME ( set "SKILL_DIR=%SONAR_PREDICTOR_HOME%" @@ -30,7 +44,7 @@ set "REAL_SONAR=%SKILL_DIR%\bin\sonar.bat" if exist "%REAL_SONAR%" goto :exec -echo sonar-predictor: first run -- downloading %VERSION% bundle from Maven Central... 1>&2 +echo sonar-predictor: first run -- downloading %VERSION% bundle from %SONAR_MAVEN_REPO_URL%... 1>&2 set "TMP=%TEMP%\sonar-predictor-bootstrap-%RANDOM%-%RANDOM%" mkdir "%TMP%" || (echo sonar-predictor: failed to create temp dir 1>&2 & exit /b 2) @@ -39,11 +53,11 @@ set "ZIP=%TMP%\bundle.zip" set "SHA=%TMP%\bundle.zip.sha1" powershell -NoProfile -ExecutionPolicy Bypass -Command ^ - "$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '%BASE_URL%/sonar-predictor-dist-%VERSION%.zip' -OutFile '%ZIP%' } catch { exit 2 }" + "$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '%BUNDLE_URL_BASE%/%ARTIFACT%-%VERSION%.zip' -OutFile '%ZIP%' } catch { exit 2 }" if errorlevel 1 (echo sonar-predictor: bundle download failed 1>&2 & rmdir /S /Q "%TMP%" & exit /b 2) powershell -NoProfile -ExecutionPolicy Bypass -Command ^ - "$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '%BASE_URL%/sonar-predictor-dist-%VERSION%.zip.sha1' -OutFile '%SHA%' } catch { exit 2 }" + "$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '%BUNDLE_URL_BASE%/%ARTIFACT%-%VERSION%.zip.sha1' -OutFile '%SHA%' } catch { exit 2 }" if errorlevel 1 (echo sonar-predictor: SHA-1 download failed 1>&2 & rmdir /S /Q "%TMP%" & exit /b 2) for /f "tokens=1" %%i in (%SHA%) do set "EXPECTED=%%i" @@ -75,5 +89,36 @@ if not exist "%REAL_SONAR%" ( ) :exec +if /i "%~1"=="agent-scan" ( + shift /1 + call :agent_scan %* + exit /b %ERRORLEVEL% +) call "%REAL_SONAR%" %* exit /b %ERRORLEVEL% + +rem agent-scan: write JSON to .sonar-predictor\scan.json, gitignore that +rem dir on first use, print a short pointer on stdout. +:agent_scan +set "SCAN_FILE=.sonar-predictor\scan.json" +if not exist ".sonar-predictor" mkdir ".sonar-predictor" + +git rev-parse --git-dir >nul 2>&1 +if not errorlevel 1 ( + if not exist .gitignore ( + >>.gitignore echo .sonar-predictor/ + ) else ( + findstr /C:".sonar-predictor/" .gitignore >nul 2>&1 || >>.gitignore echo .sonar-predictor/ + ) +) + +if "%~1"=="" ( + call "%REAL_SONAR%" --format json check --diff > "%SCAN_FILE%" 2>&1 +) else ( + call "%REAL_SONAR%" --format json %* > "%SCAN_FILE%" 2>&1 +) +set RC=%ERRORLEVEL% + +echo sonar-predictor: scan complete -^> %SCAN_FILE% +echo query: jq ^"...^" %SCAN_FILE% +exit /b %RC% diff --git a/plugin/skills/sonar-predictor/config.env b/plugin/skills/sonar-predictor/config.env new file mode 100644 index 0000000..edc2e2d --- /dev/null +++ b/plugin/skills/sonar-predictor/config.env @@ -0,0 +1,46 @@ +# sonar-predictor — bootstrap configuration +# +# Fork-and-go: this is the only file you need to edit to point the launcher +# at a corporate Maven proxy, a private JRE mirror, or a vendored bundle +# version. The launcher reads this file before doing anything. +# +# Format: KEY=VALUE per line, # for comments, no quotes, no shell expansion. +# Both the bash launcher (bin/sonar) and the Windows launcher (bin/sonar.bat) +# parse this file the same way. +# +# An environment variable of the same name takes precedence over the value +# set here — set one to override without editing this file. + +# --- Analyzer bundle source ---------------------------------------------------- + +# Maven proxy / repository where sonar-predictor-dist-.zip lives. +# Public default: Maven Central. Corporate Nexus / Artifactory example: +# SONAR_MAVEN_REPO_URL=https://nexus.corp.example.com/repository/maven-public +SONAR_MAVEN_REPO_URL=https://repo1.maven.org/maven2 + +# Bundle version. Bumped in lockstep with each plugin release. A fork can pin +# to a vendored / mirrored version without touching the launcher. +SONAR_BUNDLE_VERSION=0.1.1 + +# --- Java runtime -------------------------------------------------------------- + +# Minimum Java major version the analyzer requires. +SONAR_MIN_JAVA_VERSION=17 + +# If no Java >= SONAR_MIN_JAVA_VERSION is found on PATH, in JAVA_HOME, or in +# the common install locations the bundle's launcher already searches, the +# bootstrap downloads a JRE from this URL. Tokens are substituted at download: +# {os} linux | mac (Windows JRE auto-install is not yet supported) +# {arch} x64 | aarch64 +# {version} the value of SONAR_JRE_VERSION +# +# Public default: Adoptium Temurin API (302-redirects to a GitHub release). +# Corporate mirror example (predictable archive layout): +# SONAR_JRE_URL_TEMPLATE=https://artifacts.corp.example.com/temurin/{version}/jre-{os}-{arch}.tar.gz +SONAR_JRE_URL_TEMPLATE=https://api.adoptium.net/v3/binary/latest/{version}/ga/{os}/{arch}/jre/hotspot/normal/eclipse +SONAR_JRE_VERSION=17 + +# Set to 1 (or true, yes) to skip the JRE auto-download even if no Java is +# found — for environments with a policy against the bootstrap fetching +# binaries. The launcher will then print an error and exit instead. +SONAR_DISABLE_JRE_AUTODOWNLOAD=