Hotspot analysis
Skill baekchangjoon/hotspot-analysis/skills/hotspot-analysis
Use to produce a deterministic, reproducible, ranked prioritization of a Java codebase's REST API endpoints (and methods) for test generation — especially as the input that decides which RestAssured API tests to write first. Drives this repo's Java CLI over a local git working tree, combining recency-weighted git churn, SonarQube-style cognitive complexity, and JaCoCo coverage gap into a Composite Hotspot Score, and emits a machine-readable ranking (CSV/YAML/Markdown/HTML) plus a CI gating exit code. Method-level Java, hunk-accurate, no LLM guesswork. Based on Adam Tornhill's "Your Code as a Crime Scene".From its SKILL.md
npx -y skills add baekchangjoon/hotspot-analysis --skill hotspot-analysisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
20.4 KB, ~5.2k tokens by cl100k_base, as published. Nobody here has run it
Hotspot analysis → test-generation prioritization (Java)
This skill is a thin wrapper around a Java CLI. The jar does the analysis deterministically; the skill's job is to build it, configure it, run it, and hand the ranked, machine-readable output to whatever generates tests (typically RestAssured API tests, or unit tests for the top methods).
What makes it worth a separate step (vs. asking a model to "guess the risky endpoints"):
- Deterministic & reproducible — pure JVM computation; same inputs → same scores and same ordering. No randomness, no model variance.
- Method-level, hunk-accurate Java — churn is attributed to the exact methods whose line ranges a commit's diff hunks touched, not the whole file.
- Recency-weighted — recent churn counts more via exponential decay (configurable half-life).
- JaCoCo-integrated — real line-coverage gaps raise priority of untested code; coverage can be a scoring input or an observational column.
- CI-gating —
--strictreturns a non-zero exit code on empty results.
The primary deliverable is a priority queue of REST API endpoints: for each endpoint you get its HTTP method + route, the aggregated risk over its whole call graph, the call graph itself, and a coverage signal — i.e. exactly what an agent needs to decide which endpoint to test first and which under-tested path to target.
When to use
- "Generate RestAssured tests for this Spring app, most important endpoints first."
- "Which API endpoints / methods are riskiest and least tested? In what order?"
- Producing a deterministic prioritization that a test-generation step consumes.
Not for: non-Java repos; deciding test content (it ranks what to test, the test generator decides how).
Target must be a directory containing a real .git/ folder. Phase 1 runs
local-git end-to-end; github target needs a local clone (see Troubleshooting).
Prerequisites
| Need | Requirement |
|---|---|
| Get the jar | Nothing to build — scripts/get-jar.sh downloads the released fat jar (cached). Building from source instead needs any JDK 17+. |
| Run the jar | No JDK required — scripts/ensure-java.sh finds an installed Java 21+ or auto-downloads a Temurin 21 JRE (~46MB, sha256-verified, cached in ~/.cache/hotspot-analysis/jre; refresh by deleting that dir) |
| Analysis target | A directory with a .git/ folder |
| API analysis (recommended) | apiAnalysis.enabled: true; ideally classpathDirectories for symbol resolution |
| Coverage signal (recommended) | A JaCoCo XML report from the same build |
Workflow
A convenience wrapper, scripts/run-analysis.sh,
resolves the jar (downloading the released fat jar if needed) and runs analyze.
The steps below show the explicit form.
-
Get a runtime and the jar. No build and no pre-installed JDK required —
ensure-java.shresolves an installed Java 21+ (or auto-downloads a Temurin JRE), andget-jar.shdownloads the released fat jar to a cache (or reuses a local build, or builds from source as a fallback):JAVA="$(skills/hotspot-analysis/scripts/ensure-java.sh)" # prints a java 21+ path JAR="$(skills/hotspot-analysis/scripts/get-jar.sh)" # prints the jar path # manual alternative (no clone needed): # curl -fsSL https://github.com/baekchangjoon/hotspot-analysis/releases/latest/download/hotspot.jar -o hotspot.jar # JAR=hotspot.jar # from-source alternative (needs the repo + a JDK): ./gradlew bootJarPrefer no downloads at all? Use the Docker image, mounting the target repo at
/work:docker run --rm -v "$PWD":/work ghcr.io/baekchangjoon/hotspot-analysis:latest analyze --config /work/hotspot.yml -
Generate a config.
"$JAVA" -jar "$JAR" init -o hotspot.yml -
Configure for endpoint prioritization. Point
analysis.target.pathat the target repo, enable API analysis, and (if available) supply a JaCoCo report. If the user can't hand-write the YAML, run the interview below ("Configure interactively") — ask, fill defaults, write the file. See Config reference for every key. The key block:analysis: apiAnalysis: enabled: true sharedComponentMode: BOTH # CUMULATIVE | SEPARATE | BOTH classpathDirectories: # optional but improves call-graph resolution - build/libs jacocoReportPath: build/reports/jacoco/test/jacocoTestReport.xml output: formats: [yaml, md, html] apiLayout: BOTH # COMBINED | STANDALONE | BOTH topN: 30 -
Analyze (add
--strictin CI)."$JAVA" -jar "$JAR" analyze --config hotspot.yml --strict # or, in one shot (resolves the runtime AND the jar for you): # skills/hotspot-analysis/scripts/run-analysis.sh hotspot.yml --strictOutputs land in
output.path. With API analysis on andapiLayout: BOTH:hotspot-report/ ├── api_report.yml ← STANDALONE: apiHotspots + sharedComponents (agent input) ├── hotspots.yml ← COMBINED: file + method + api + shared in one doc ├── hotspots.md / .html ← human-readable ├── file_hotspots.csv └── method_hotspots.csv -
Consume the ranking for RestAssured. Read
api_report.ymland iterateapiHotspotsincompositeRankorder. Field-by-field schema:references/api-report-schema.md. Each row carries:Field Use for test generation httpMethod,routeThe request: given()...when().<method>(route)fqcn,method,parametersController signature → request body/param shape callGraphReachable methods → which downstream logic the endpoint exercises coverageMultiplier/lineCoverageHow under-tested the endpoint's logic is compositeRankThe order to write tests in sharedComponents[]Methods many endpoints depend on — high-leverage to cover once Generate tests highest-rank first, targeting the least-covered paths in each endpoint's call graph. Do not fabricate the ranking — run the CLI and read the actual file.
Configure interactively (interview)
A freshly-installed user usually can't write hotspot.yml cold. Don't make
them. Generate a starting file (init), then fill it by Q&A: auto-detect what
you can, ask only what's ambiguous, confirm, and write the file.
Procedure:
-
Detect first, then ask. Inspect the target repo to pre-fill defaults so most questions become a yes/no confirmation:
- Spring app? —
grep -rl "@RestController\|@RequestMapping" <repo>/src→ if hits, defaultapiAnalysis.enabled: true. - Multi-module? — more than one
src/main/javaroot → include both globs. - JaCoCo report present? — look for
**/jacoco/**/*.xml(e.g.build/reports/jacoco/test/jacocoTestReport.xml) → defaultjacocoReportPath. - Built classes/jars? —
build/libs,build/classes→ defaultapiAnalysis.classpathDirectories. - Recent activity? —
git -C <repo> log -1 --format=%cd→ if older than a year, propose absolutewindow.since/untilinstead ofdays.
- Spring app? —
-
Ask, one decision at a time (skip any you confidently detected — just state the default and let the user correct it):
# Question Maps to Default 1 Which repo to analyze? (path to the .git/working tree)analysis.target.path— (required) 2 Prioritize REST API endpoints for test generation? apiAnalysis.enabledtrueif Spring detected3 Count churn over the last N days, or an absolute date range? window.daysorwindow.since/untildays: 3654 File-level, method-level, or both? scope.granularity[file, method]5 Have a JaCoCo coverage report? Where? analysis.jacocoReportPathdetected path, else omit 6 Dirs with built classes/dep jars (improves call graph)? apiAnalysis.classpathDirectoriesdetected, else []7 Shared-method handling? apiAnalysis.sharedComponentModeBOTH8 Output formats / where / how many rows? output.formats/path/topN[csv,yaml,md,html],./hotspot-report,309 Fail the run if the result is empty (CI)? pass --strictat run timeno -
Write
hotspot.ymlfrom the answers, show it back to the user for a final OK, then run step 4. If a tool likeAskUserQuestionis available, prefer it for crisp multiple-choice prompts; otherwise ask in plain text.
Keep it short: a typical session is "confirm repo path → confirm Spring/API on → accept window default → confirm the detected JaCoCo path → go".
How the score is computed
Four input factors → two scores. Full per-granularity derivations (with source
references and worked examples) live in docs/scoring/:
file ·
method ·
REST API endpoint ·
shared component.
| Factor / score | Definition |
|---|---|
| Revisions | Commits in the window that touched the artifact (method: diff-hunk overlap) |
| Recency Decay | Σ exp(-ln(2)·Δt / halfLife) over those commits — recent weighs more |
| Cognitive Complexity | SonarQube-style AST walk (file = sum of its methods) |
| Coverage Multiplier | 1/(lineCoverage + 0.1) from JaCoCo; 1.0 if no report |
| Simple Score | Revisions × LOC (Tornhill's original) |
| Composite Score | Cognitive Complexity × Recency Decay × Coverage Multiplier |
For an API endpoint, each factor is aggregated over the controller method
plus its whole call graph; coverage is the average over those methods.
Sorted by Composite DESC, ties broken deterministically (route,httpMethod).
Config reference
analysis:
target:
type: local-git # local-git | github (Phase 1 CLI: local-git end-to-end)
path: /path/to/target/repo # must contain a .git/ folder
window:
days: 365 # Mode A: relative window from now
# since: "2024-01-01" # Mode B: absolute ISO range (use INSTEAD of days)
# until: "2026-01-01"
scope:
granularity: [file, method]
include:
- "src/main/java/**/*.java" # single-module repos
- "**/src/main/java/**/*.java" # multi-module repos (list both if unsure)
exclude:
- "**/generated/**"
- "**/test/**"
- "**/build/**"
scoring:
decayHalfLifeDays: 90 # half-life for recency decay (days)
excludeCoverage: false # true → Composite = CC × Decay; coverage shown raw, not scored
apiAnalysis:
enabled: true # off by default; required for api/shared granularities
sharedComponentMode: BOTH # CUMULATIVE | SEPARATE | BOTH
classpathDirectories: [] # dirs with dependency jars/classes for symbol resolution
jacocoReportPath: build/reports/jacoco/test/jacocoTestReport.xml # optional
output:
formats: [csv, yaml, md, html] # case-insensitive; ≥1 required
apiLayout: BOTH # COMBINED (into hotspots.*) | STANDALONE (api_report.*) | BOTH
coverageBreakdown: false # true → also write coverage_breakdown.yml: the audit
# trail behind every coverage number (per-file counts;
# per-endpoint per-method covered/executable lines)
path: ./hotspot-report
topN: 30 # 0 = all rows
Env vars substitute as ${VAR_NAME} in any string value; YAML comment lines
(#) are left untouched.
sharedComponentMode
CUMULATIVE— shared methods counted inside every endpoint's aggregate; no separate list.SEPARATE— shared methods excluded from endpoint aggregates and reported once on their own.BOTH(default) — endpoint aggregates include them and a separate shared list is emitted.
analyze options
| Option | Effect |
|---|---|
--config, -c <file> | Path to the YAML config (required) |
--output-dir, -o <dir> | Directory to write the reports into (overrides output.path) |
--quiet, -q | Suppress the stdout summary |
--strict, -s | Exit code 3 on empty result (zero commits or zero files) — for CI gating |
Exit codes: 0 ok · 1 config/pipeline failure · 2 usage error · 3 --strict empty result.
Decision rules (IF → THEN)
- IF the user wants the order to write API tests in THEN read
api_report.ymland iterateapiHotspotsby ascendingcompositeRank. - IF
apiHotspotsis empty but the app clearly has endpoints THEN check, in order:apiAnalysis.enabled: true, controllers carry@RestController/@Controller+ a mapping annotation, andapiAnalysis.classpathDirectoriesincludes the dependency jars/classes so cross-type calls resolve. Do not report "no endpoints". - IF the run warns that files are "not present in the JaCoCo report" (their
coverageMultiplierstays1.0) THEN the report doesn't cover those files — supply a report from the same build/module; do not conclude "nothing is tested". Broken/zero-coverage/partial reports are all auto-detected and treated as unknown coverage (multiplier1.0+ warning), never as a silent 10x penalty. Regenerate with e.g../gradlew test jacocoTestReportfrom the same checkout. - IF the summary shows
Files: 0THEN fixscope.include(single-module needssrc/main/java/**/*.java, multi-module needs**/src/main/java/**/*.java; list both). - IF the summary shows
Commits: 0THEN widenwindow.daysor switch to absolutewindow.since/window.untiloverlapping real activity. - IF
target.typeisgithubTHEN clone the repo locally and re-run withtarget.type: local-git(Phase 1 wires onlylocal-gitend-to-end). - IF running in CI THEN pass
--strictso an empty result fails loudly. - IF you only need observational coverage, not coverage-driven scoring
THEN set
scoring.excludeCoverage: true(Composite becomesCC × Decay).
Anti-patterns & pitfalls
- Don't fabricate or estimate the ranking. Run the jar and read the actual
api_report.yml; the whole point is determinism, not a model guess. - Don't treat an empty
apiHotspotsas "no endpoints." It almost always meansapiAnalysisis off or the call graph couldn't resolve (missingclasspathDirectories). - Don't feed a JaCoCo report from a different module/build. Path mismatch reads as 0% coverage → every multiplier maxes at 10 and the ranking is bogus.
- Don't run the jar on JDK < 21 — it's compiled for 21 (
UnsupportedClassVersionError);ensure-java.shguards this by only accepting 21+. - Don't analyze generated or build output — keep
**/generated/**,**/build/**,**/target/**,**/test/**inscope.exclude. - Don't present the Composite Score as a verdict. It's prioritization evidence; surface the factors (churn, recency, complexity, coverage) so the choice is explainable.
- Don't reorder by Simple Score when the goal is risk.
compositeRank, notsimpleRank, is the test-priority signal.
Testing
The project's own suite exercises every layer (parser, scoring, output, E2E):
./gradlew test # comprehensive; run before trusting a build
Skill-level smoke check — analyze this very repo and assert a non-empty result:
bash -n skills/hotspot-analysis/scripts/ensure-java.sh skills/hotspot-analysis/scripts/get-jar.sh skills/hotspot-analysis/scripts/run-analysis.sh
JAVA="$(skills/hotspot-analysis/scripts/ensure-java.sh)" # resolves/downloads a java 21+
JAR="$(skills/hotspot-analysis/scripts/get-jar.sh)" # resolves/downloads the jar
"$JAVA" -jar "$JAR" init -o /tmp/h.yml -f
# set analysis.target.path in /tmp/h.yml to this repo's absolute path, then:
"$JAVA" -jar "$JAR" analyze --config /tmp/h.yml --strict
echo "exit=$?" # 0 = produced output; 3 = empty (misconfigured)
A green ./gradlew test plus a 0 exit on the smoke run means the skill's
toolchain is sound end-to-end.
Changelog
- 0.1.6 — scoring-trust and report fixes from a fresh-eyes evaluation
round: unknown coverage is never a 10x penalty (files absent from a
partial JaCoCo report, line-less sourcefile entries, and uninstrumented
methods all get multiplier 1.0 + a warning); switch expressions count
toward cognitive complexity; duplicate sourcefile entries OR-merge;
stale-report and shallow-clone warnings; unparseable files are skipped
instead of aborting; HTML column sorting works on every table (pinned by
a JS-executing smoke test) and the X-Ray drill-down is documented;
clearer config errors (field + value + allowed values),
~/expansion,daysvssince/untilnow mutually exclusive,initrefuses directory targets,analyzeskips nothing silently. - 0.1.5 — zero-config:
analyzenow runs without a config file (auto-detects git root, single/multi-module layout, JaCoCo report, Spring API), takes an optional[path],--print-configdumps the synthesized config, the first run prints the top-3 hotspots + the report path, and linked git worktrees are supported. A one-line installer (curl ... install.sh | bash) provides thehotspotcommand. And the JDK-21 friction is gone:scripts/ensure-java.sh(and thehotspotwrapper installed by install.sh) finds an installed Java 21+ or auto-downloads a sha256-verified Temurin 21 JRE;brew install baekchangjoon/tap/hotspotinstalls with the JDK as a brew dependency; each release ships self-containedhotspot-<tag>-<os>-<arch>.tar.gzarchives (bundled JRE, verified on 4 native CI runners before attach); an all-zero JaCoCo report (no execution data) now warns and disables coverage instead of silently inflating every multiplier to 10x;analyze -o/--output-diroverrides the report directory. - 0.1.4 — endpoint coverage is now line-weighted (Σcovered/Σexecutable over
the call graph) instead of a mean of per-method ratios, so a large untested
method can no longer hide behind a small covered one; new opt-in
output.coverageBreakdownwritescoverage_breakdown.yml, the calculation trace behind every coverage number; releases enforce 4-way version consistency (tag = gradle = CLI = plugin/marketplace manifests). - 0.1.3 — one-click
releasebutton + skills-validation CI gate + tag protection; the button reliably fans out to jar/image viaworkflow_call(a GITHUB_TOKEN-created release doesn't re-trigger event workflows). - 0.1.2 — releases are now event-driven: every published release (incl. one
created by
gh skill publish) auto-attacheshotspot.jarand builds the Docker image, so a new release never breaks the download.licenseadded to frontmatter. - 0.1.1 — distribute the jar via GitHub Releases (version-stable
hotspot.jarasset) + ghcr Docker image; a missingjacocoReportPathnow warns and disables coverage instead of silently penalizing every artifact. - 0.1.0 — initial skill: file / method / REST API endpoint / shared-component
prioritization driving the Phase 1 CLI; RestAssured consumption guide;
apiAnalysis+ JaCoCo +--strictexposed; per-granularity scoring docs.
References
- API report field schema:
references/api-report-schema.md. - Scoring derivations:
docs/scoring/. - Jar resolver / wrapper:
scripts/get-jar.sh·scripts/run-analysis.sh. - Prebuilt jar: GitHub Releases.
- Project README and
docs/(architecture, advanced techniques, theory). - Adam Tornhill, Your Code as a Crime Scene.
What ships with it: 4 files
12.4 KB alongside SKILL.md, 3 of them executable
references/
- api-report-schema.md3.0 KB
scripts/
- ensure-java.shruns6.6 KB
- get-jar.shruns2.1 KB
- run-analysis.shruns704 B
Gives 0 of the 12 instructions most docs writing skills give in ~5.2k tokens
Counted across 1,637 of the 3,044 authors here whose files we hold, read 2026-08-07
- Announce the skill at startin 54 of 1637, across 26 files
- Convert legacy doc files before editingin 45 of 1637, across 7 files
- Predict questions readers might askin 42 of 1637, across 4 files
- Generate clarifying questions for initial contextin 42 of 1637, across 3 files
- Create document scaffold with placeholder textin 42 of 1637, across 3 files
- Brainstorm content options for each sectionin 42 of 1637, across 3 files
- Test the document with a fresh context-less instancein 42 of 1637, across 3 files
- Include exact file paths in every taskin 42 of 1637, across 15 files
- Ask interview questions one at a timein 42 of 1637, across 27 files
- Apply surgical edits during refinementin 41 of 1637, across 2 files
- Offer structured workflow or freeformin 40 of 1637, across 1 file
- Ask for document meta-contextin 40 of 1637, across 2 files
Said here and by no other author read
- run the hotspot analysis CLI
- enable API analysis in config
- supply a JaCoCo coverage report
- read the generated API report
- generate tests highest-rank first
- target least-covered paths
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.