diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 0000000..2fecfdd
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,24 @@
+{
+ "name": "sonar-predict",
+ "owner": {
+ "name": "Amit Kumar",
+ "url": "https://github.com/RandomCodeSpace"
+ },
+ "plugins": [
+ {
+ "name": "sonar-predictor",
+ "source": "./plugin",
+ "description": "Offline SonarSource quality gate — runs the genuine SonarSource analyzers locally (no network, no server) to catch bugs, code smells, vulnerabilities and security hotspots. The plugin itself is lightweight (kilobytes); the heavy analyzer bundle is fetched from Maven Central on first invocation and cached locally for every subsequent run.",
+ "homepage": "https://github.com/RandomCodeSpace/sonar-predict",
+ "license": "Apache-2.0",
+ "keywords": [
+ "code-quality",
+ "static-analysis",
+ "sonarsource",
+ "linter",
+ "quality-gate",
+ "offline"
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index dcdc66b..6b2ea3e 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,10 +9,12 @@ name: Publish to Maven Central + GitHub Release
# 2. builds, tests, GPG-signs and deploys protocol/daemon/cli to Maven
# Central via the Sonatype Central Portal (the dist module sets
# maven.deploy.skip=true so it is built but never staged),
-# 3. creates a GitHub Release carrying three bundles: a whole-repo source
-# zip (git archive of HEAD), the assembled skill bundle zip, and the
-# assembled plugin bundle zip (the .claude-plugin format installable
-# on Claude Code and GitHub Copilot CLI).
+# 3. creates a GitHub Release carrying two bundles: a whole-repo source
+# zip (git archive of HEAD) and the assembled skill bundle zip. The
+# plugin itself is installed via the in-repo /plugin/ directory through
+# Claude Code's / Copilot CLI's marketplace (see /.claude-plugin/
+# marketplace.json); its skill launcher downloads the analyzer bundle
+# on first run from the Maven Central artifact this workflow publishes.
#
# Required repo secrets:
# OSS_NEXUS_USER - Sonatype Central Portal token username
@@ -126,20 +128,6 @@ jobs:
echo "skill_zip=${SKILL_ZIP}" >> "$GITHUB_OUTPUT"
echo "Skill bundle: ${SKILL_ZIP}"
- - name: Locate the plugin bundle zip
- id: plugin
- run: |
- set -euo pipefail
- VERSION="${{ steps.version.outputs.version }}"
- PLUGIN_ZIP="dist/target/sonar-predict-plugin-${VERSION}.zip"
- if [ ! -f "${PLUGIN_ZIP}" ]; then
- echo "::error::plugin bundle not found at ${PLUGIN_ZIP}"
- ls -la dist/target || true
- exit 1
- fi
- echo "plugin_zip=${PLUGIN_ZIP}" >> "$GITHUB_OUTPUT"
- echo "Plugin bundle: ${PLUGIN_ZIP}"
-
- name: Create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
@@ -148,7 +136,6 @@ jobs:
VERSION="${{ steps.version.outputs.version }}"
SRC_ZIP="sonar-predict-${VERSION}-src.zip"
SKILL_ZIP="${{ steps.bundle.outputs.skill_zip }}"
- PLUGIN_ZIP="${{ steps.plugin.outputs.plugin_zip }}"
if [ -n "${GITHUB_REF_NAME:-}" ] && [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
TAG="${GITHUB_REF_NAME}"
else
@@ -158,5 +145,4 @@ jobs:
--title "sonar-predict ${VERSION}" \
--generate-notes \
"${SRC_ZIP}" \
- "${SKILL_ZIP}" \
- "${PLUGIN_ZIP}"
+ "${SKILL_ZIP}"
diff --git a/dist/pom.xml b/dist/pom.xml
index 11c8550..c5bc100 100644
--- a/dist/pom.xml
+++ b/dist/pom.xml
@@ -171,37 +171,6 @@
-
-
- build-plugin-dir
- package
- single
-
- false
- plugin
-
- src/assembly/plugin.xml
-
-
-
-
-
- build-plugin-zip
- package
- single
-
- false
- sonar-predict-plugin-${project.version}
-
- src/assembly/plugin-zip.xml
-
-
-
diff --git a/dist/src/assembly/plugin-zip.xml b/dist/src/assembly/plugin-zip.xml
deleted file mode 100644
index e672ff8..0000000
--- a/dist/src/assembly/plugin-zip.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
- plugin-zip
-
-
-
- zip
-
-
- sonar-predictor
- true
-
-
-
- ${project.basedir}/src/main/plugin/.claude-plugin
- .claude-plugin
-
- plugin.json
-
-
-
- ${project.basedir}/../.claude/agents
- agents
-
- sonar-scanner-claude.md
-
-
-
- ${project.basedir}/src/main/plugin/agents
- agents
-
- sonar-scanner-copilot.md
-
-
-
- ${project.basedir}/src/main/skill
- skills/sonar-predictor
-
- SKILL.md
-
-
-
- ${project.basedir}/src/main/scripts
- skills/sonar-predictor/bin
-
- sonar
-
- 0755
-
-
- ${project.basedir}/src/main/scripts
- skills/sonar-predictor/bin
-
- sonar.bat
-
- 0644
-
-
- ${project.build.directory}/plugins
- skills/sonar-predictor/plugins
-
- *.jar
-
-
-
-
-
-
- skills/sonar-predictor/lib
- false
- false
- ${artifact.artifactId}.jar
-
- io.github.randomcodespace.sonarpredict:sonar-predictor-cli
- io.github.randomcodespace.sonarpredict:sonar-predictor-daemon
-
-
-
-
diff --git a/dist/src/assembly/plugin.xml b/dist/src/assembly/plugin.xml
deleted file mode 100644
index 7be9977..0000000
--- a/dist/src/assembly/plugin.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-
-
- plugin-dir
-
-
-
- dir
-
-
- sonar-predictor
- true
-
-
-
-
- ${project.basedir}/src/main/plugin/.claude-plugin
- .claude-plugin
-
- plugin.json
-
-
-
-
-
- ${project.basedir}/../.claude/agents
- agents
-
- sonar-scanner-claude.md
-
-
-
- ${project.basedir}/src/main/plugin/agents
- agents
-
- sonar-scanner-copilot.md
-
-
-
-
-
- ${project.basedir}/src/main/skill
- skills/sonar-predictor
-
- SKILL.md
-
-
-
- ${project.basedir}/src/main/scripts
- skills/sonar-predictor/bin
-
- sonar
-
- 0755
-
-
- ${project.basedir}/src/main/scripts
- skills/sonar-predictor/bin
-
- sonar.bat
-
- 0644
-
-
- ${project.build.directory}/plugins
- skills/sonar-predictor/plugins
-
- *.jar
-
-
-
-
-
-
-
- skills/sonar-predictor/lib
- false
- false
- ${artifact.artifactId}.jar
-
- io.github.randomcodespace.sonarpredict:sonar-predictor-cli
- io.github.randomcodespace.sonarpredict:sonar-predictor-daemon
-
-
-
-
diff --git a/dist/src/main/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json
similarity index 66%
rename from dist/src/main/plugin/.claude-plugin/plugin.json
rename to plugin/.claude-plugin/plugin.json
index ddde8fb..74286f7 100644
--- a/dist/src/main/plugin/.claude-plugin/plugin.json
+++ b/plugin/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "sonar-predictor",
- "version": "0.1.1",
- "description": "Offline SonarSource quality gate — runs the genuine SonarSource analyzers locally (no network, no server) to catch bugs, code smells, vulnerabilities and security hotspots. Bundles the sonar-predictor scanner skill and two named agent variants — invoke `sonar-scanner-claude` on Claude Code (model: haiku) and `sonar-scanner-copilot` on GitHub Copilot CLI (model: gpt-5-mini). Selection is by agent name; neither is implicitly default.",
+ "version": "0.1.3",
+ "description": "Offline SonarSource quality gate — runs the genuine SonarSource analyzers locally (no network, no server) to catch bugs, code smells, vulnerabilities and security hotspots. Ships two named scanner agent variants (sonar-scanner-claude on Claude Code, sonar-scanner-copilot on GitHub Copilot CLI) and a thin sonar-predictor skill whose launcher fetches the analyzer bundle (~150 MB) from Maven Central on first invocation and caches it for every subsequent run.",
"author": {
"name": "Amit Kumar",
"url": "https://github.com/RandomCodeSpace"
diff --git a/.claude/agents/sonar-scanner-claude.md b/plugin/agents/sonar-scanner-claude.md
similarity index 100%
rename from .claude/agents/sonar-scanner-claude.md
rename to plugin/agents/sonar-scanner-claude.md
diff --git a/dist/src/main/plugin/agents/sonar-scanner-copilot.md b/plugin/agents/sonar-scanner-copilot.md
similarity index 100%
rename from dist/src/main/plugin/agents/sonar-scanner-copilot.md
rename to plugin/agents/sonar-scanner-copilot.md
diff --git a/plugin/skills/sonar-predictor/SKILL.md b/plugin/skills/sonar-predictor/SKILL.md
new file mode 100644
index 0000000..ecfd2ea
--- /dev/null
+++ b/plugin/skills/sonar-predictor/SKILL.md
@@ -0,0 +1,20 @@
+---
+name: sonar-predictor
+description: Use after writing or modifying source code, before committing or pushing — runs genuine SonarSource analyzers offline as a fast local quality gate to catch bugs, code smells, vulnerabilities and security hotspots. Also use when the user asks to check code quality, run sonar, or analyze a file or diff.
+---
+
+# sonar-predictor
+
+An offline SonarSource pre-push quality gate — runs the genuine analyzers locally, no network, no server.
+
+**Scan-only.** This skill only *scans and reports* — it reads source and emits findings, and never modifies any file. Applying fixes is the calling agent's job, not this tool's; running it is a safe, read-only operation.
+
+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.
+
+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.
+
+**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
new file mode 100755
index 0000000..077e7b7
--- /dev/null
+++ b/plugin/skills/sonar-predictor/bin/sonar
@@ -0,0 +1,88 @@
+#!/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.
+#
+# Override the bundle location for air-gapped or pre-staged installs:
+# SONAR_PREDICTOR_HOME=/path/to/extracted/sonar-predictor
+
+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"
+
+GROUP_PATH="io/github/randomcodespace/sonarpredict"
+ARTIFACT="sonar-predictor-dist"
+BASE_URL="https://repo1.maven.org/maven2/${GROUP_PATH}/${ARTIFACT}/${VERSION}"
+
+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"
+
+sha1_of() {
+ if command -v sha1sum >/dev/null 2>&1; then
+ sha1sum "$1" | awk '{print $1}'
+ elif command -v shasum >/dev/null 2>&1; then
+ shasum -a 1 "$1" | awk '{print $1}'
+ else
+ echo "sonar-predictor: no sha1sum or shasum found — cannot verify bundle" >&2
+ exit 2
+ 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
+ fi
+ done
+
+ echo "sonar-predictor: first run — downloading $VERSION bundle from Maven Central..." >&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"
+
+ EXPECTED="$(awk '{print $1}' "$SHA")"
+ ACTUAL="$(sha1_of "$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"
+ if [ ! -d "$TMP/extracted/sonar-predictor" ]; then
+ echo "sonar-predictor: unexpected bundle layout (no sonar-predictor/ dir 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
+
+ if [ ! -x "$REAL_SONAR" ]; then
+ echo "sonar-predictor: bootstrap completed but $REAL_SONAR is not executable" >&2
+ exit 2
+ fi
+fi
+
+exec "$REAL_SONAR" "$@"
diff --git a/plugin/skills/sonar-predictor/bin/sonar.bat b/plugin/skills/sonar-predictor/bin/sonar.bat
new file mode 100644
index 0000000..c60592d
--- /dev/null
+++ b/plugin/skills/sonar-predictor/bin/sonar.bat
@@ -0,0 +1,79 @@
+@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
+rem Override the bundle location for air-gapped or 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%"
+
+if defined SONAR_PREDICTOR_HOME (
+ set "SKILL_DIR=%SONAR_PREDICTOR_HOME%"
+) else (
+ if defined LOCALAPPDATA (
+ set "SKILL_DIR=%LOCALAPPDATA%\sonar-predictor\%VERSION%"
+ ) else (
+ set "SKILL_DIR=%USERPROFILE%\.cache\sonar-predictor\%VERSION%"
+ )
+)
+
+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
+
+set "TMP=%TEMP%\sonar-predictor-bootstrap-%RANDOM%-%RANDOM%"
+mkdir "%TMP%" || (echo sonar-predictor: failed to create temp dir 1>&2 & exit /b 2)
+
+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 }"
+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 }"
+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"
+for /f "tokens=1" %%i in ('powershell -NoProfile -Command "(Get-FileHash -Algorithm SHA1 '%ZIP%').Hash.ToLower()"') do set "ACTUAL=%%i"
+if /i not "!EXPECTED!"=="!ACTUAL!" (
+ echo sonar-predictor: SHA-1 verification failed ^(expected !EXPECTED!, got !ACTUAL!^) 1>&2
+ rmdir /S /Q "%TMP%"
+ exit /b 2
+)
+
+powershell -NoProfile -ExecutionPolicy Bypass -Command ^
+ "$ProgressPreference='SilentlyContinue'; try { Expand-Archive -Force -Path '%ZIP%' -DestinationPath '%TMP%\extracted' } catch { exit 2 }"
+if errorlevel 1 (echo sonar-predictor: extraction failed 1>&2 & rmdir /S /Q "%TMP%" & exit /b 2)
+
+if not exist "%TMP%\extracted\sonar-predictor" (
+ echo sonar-predictor: unexpected bundle layout ^(no sonar-predictor\ at zip root^) 1>&2
+ rmdir /S /Q "%TMP%"
+ exit /b 2
+)
+
+for %%I in ("%SKILL_DIR%") do set "PARENT=%%~dpI"
+if not exist "%PARENT%" mkdir "%PARENT%"
+move /Y "%TMP%\extracted\sonar-predictor" "%SKILL_DIR%" >nul
+rmdir /S /Q "%TMP%"
+
+if not exist "%REAL_SONAR%" (
+ echo sonar-predictor: bootstrap completed but %REAL_SONAR% is missing 1>&2
+ exit /b 2
+)
+
+:exec
+call "%REAL_SONAR%" %*
+exit /b %ERRORLEVEL%