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 —
--strict returns 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.sh resolves an installed Java 21+ (or auto-downloads a
Temurin JRE), and get-jar.sh downloads 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 bootJar
Prefer 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.path at
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 --strict in 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 --strict
Outputs land in output.path. With API analysis on and apiLayout: 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.yml and iterate
apiHotspots in compositeRank order. Field-by-field schema:
references/api-report-schema.md. Each row
carries:
| Field |
Use for test generation |
httpMethod, route |
The request: given()...when().<method>(route) |
fqcn, method, parameters |
Controller signature → request body/param shape |
callGraph |
Reachable methods → which downstream logic the endpoint exercises |
coverageMultiplier / lineCoverage |
How under-tested the endpoint's logic is |
compositeRank |
The 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, default apiAnalysis.enabled: true.
- Multi-module? — more than one
src/main/java root → include both globs.
- JaCoCo report present? — look for
**/jacoco/**/*.xml (e.g.
build/reports/jacoco/test/jacocoTestReport.xml) → default jacocoReportPath.
- Built classes/jars? —
build/libs, build/classes → default
apiAnalysis.classpathDirectories.
- Recent activity? —
git -C <repo> log -1 --format=%cd → if older than a
year, propose absolute window.since/until instead of days.
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.enabled |
true if Spring detected |
| 3 |
Count churn over the last N days, or an absolute date range? |
window.days or window.since/until |
days: 365 |
| 4 |
File-level, method-level, or both? |
scope.granularity |
[file, method] |
| 5 |
Have a JaCoCo coverage report? Where? |
analysis.jacocoReportPath |
detected path, else omit |
| 6 |
Dirs with built classes/dep jars (improves call graph)? |
apiAnalysis.classpathDirectories |
detected, else [] |
| 7 |
Shared-method handling? |
apiAnalysis.sharedComponentMode |
BOTH |
| 8 |
Output formats / where / how many rows? |
output.formats/path/topN |
[csv,yaml,md,html], ./hotspot-report, 30 |
| 9 |
Fail the run if the result is empty (CI)? |
pass --strict at run time |
no |
Write hotspot.yml from the answers, show it back to the user for a
final OK, then run step 4. If a tool like AskUserQuestion is 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.yml and iterate apiHotspots by ascending compositeRank.
- IF
apiHotspots is empty but the app clearly has endpoints THEN check,
in order: apiAnalysis.enabled: true, controllers carry
@RestController/@Controller + a mapping annotation, and
apiAnalysis.classpathDirectories includes 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
coverageMultiplier stays 1.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 (multiplier 1.0 + warning),
never as a silent 10x penalty. Regenerate with e.g.
./gradlew test jacocoTestReport from the same checkout.
- IF the summary shows
Files: 0 THEN fix scope.include (single-module
needs src/main/java/**/*.java, multi-module needs **/src/main/java/**/*.java;
list both).
- IF the summary shows
Commits: 0 THEN widen window.days or switch to
absolute window.since/window.until overlapping real activity.
- IF
target.type is github THEN clone the repo locally and re-run with
target.type: local-git (Phase 1 wires only local-git end-to-end).
- IF running in CI THEN pass
--strict so an empty result fails loudly.
- IF you only need observational coverage, not coverage-driven scoring
THEN set
scoring.excludeCoverage: true (Composite becomes CC × 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
apiHotspots as "no endpoints." It almost always
means apiAnalysis is off or the call graph couldn't resolve (missing
classpathDirectories).
- 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.sh guards this by only accepting 21+.
- Don't analyze generated or build output — keep
**/generated/**,
**/build/**, **/target/**, **/test/** in scope.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, not
simpleRank, 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,
days vs since/until now mutually exclusive, init refuses
directory targets, analyze skips nothing silently.
- 0.1.5 — zero-config:
analyze now runs without a config file
(auto-detects git root, single/multi-module layout, JaCoCo report, Spring
API), takes an optional [path], --print-config dumps 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 the hotspot command. And the
JDK-21 friction is gone: scripts/ensure-java.sh (and the
hotspot wrapper installed by install.sh) finds an installed Java 21+ or
auto-downloads a sha256-verified Temurin 21 JRE; brew install baekchangjoon/tap/hotspot installs with the JDK as a brew dependency; each
release ships self-contained hotspot-<tag>-<os>-<arch>.tar.gz archives
(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-dir
overrides 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.coverageBreakdown writes coverage_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
release button + skills-validation CI gate + tag
protection; the button reliably fans out to jar/image via workflow_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-attaches hotspot.jar and builds the
Docker image, so a new release never breaks the download. license added to
frontmatter.
- 0.1.1 — distribute the jar via GitHub Releases (version-stable
hotspot.jar asset) + ghcr Docker image; a missing jacocoReportPath now
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 + --strict exposed; 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.
1---2name: hotspot-analysis3description: 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".4license: MIT5---67# Hotspot analysis → test-generation prioritization (Java)89This skill is a **thin wrapper around a Java CLI**. The jar does the analysis10deterministically; the skill's job is to build it, configure it, run it, and11hand the **ranked, machine-readable output** to whatever generates tests12(typically **RestAssured** API tests, or unit tests for the top methods).1314What makes it worth a separate step (vs. asking a model to "guess the risky15endpoints"):1617- **Deterministic & reproducible** — pure JVM computation; same inputs → same18 scores and same ordering. No randomness, no model variance.19- **Method-level, hunk-accurate Java** — churn is attributed to the exact20 methods whose line ranges a commit's diff hunks touched, not the whole file.21- **Recency-weighted** — recent churn counts more via exponential decay22 (configurable half-life).23- **JaCoCo-integrated** — real line-coverage gaps raise priority of untested24 code; coverage can be a scoring input or an observational column.25- **CI-gating** — `--strict` returns a non-zero exit code on empty results.2627The primary deliverable is a **priority queue of REST API endpoints**: for each28endpoint you get its HTTP method + route, the aggregated risk over its whole29call graph, the call graph itself, and a coverage signal — i.e. exactly what an30agent needs to decide *which endpoint to test first and which under-tested path31to target*.3233## When to use3435- "Generate RestAssured tests for this Spring app, most important endpoints first."36- "Which API endpoints / methods are riskiest and least tested? In what order?"37- Producing a deterministic prioritization that a test-generation step consumes.3839Not for: non-Java repos; deciding test *content* (it ranks *what* to test, the40test generator decides *how*).4142Target must be a directory containing a real `.git/` folder. Phase 1 runs43`local-git` end-to-end; `github` target needs a local clone (see Troubleshooting).4445## Prerequisites4647| Need | Requirement |48|---|---|49| Get the jar | Nothing to build — `scripts/get-jar.sh` downloads the released fat jar (cached). Building from source instead needs any JDK 17+. |50| **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) |51| Analysis target | A directory with a `.git/` folder |52| API analysis (recommended) | `apiAnalysis.enabled: true`; ideally `classpathDirectories` for symbol resolution |53| Coverage signal (recommended) | A JaCoCo XML report from the **same** build |5455## Workflow5657A convenience wrapper, [`scripts/run-analysis.sh`](scripts/run-analysis.sh),58resolves the jar (downloading the released fat jar if needed) and runs `analyze`.59The steps below show the explicit form.60611. **Get a runtime and the jar.** No build and no pre-installed JDK required —62 `ensure-java.sh` resolves an installed Java 21+ (or auto-downloads a63 Temurin JRE), and `get-jar.sh` downloads the released fat jar to a cache64 (or reuses a local build, or builds from source as a fallback):6566 ```bash67 JAVA="$(skills/hotspot-analysis/scripts/ensure-java.sh)" # prints a java 21+ path68 JAR="$(skills/hotspot-analysis/scripts/get-jar.sh)" # prints the jar path69 # manual alternative (no clone needed):70 # curl -fsSL https://github.com/baekchangjoon/hotspot-analysis/releases/latest/download/hotspot.jar -o hotspot.jar71 # JAR=hotspot.jar72 # from-source alternative (needs the repo + a JDK): ./gradlew bootJar73 ```7475 Prefer no downloads at all? Use the Docker image, mounting the target repo at `/work`:76 `docker run --rm -v "$PWD":/work ghcr.io/baekchangjoon/hotspot-analysis:latest analyze --config /work/hotspot.yml`77782. **Generate a config.**7980 ```bash81 "$JAVA" -jar "$JAR" init -o hotspot.yml82 ```83843. **Configure for endpoint prioritization.** Point `analysis.target.path` at85 the target repo, enable API analysis, and (if available) supply a JaCoCo86 report. If the user can't hand-write the YAML, **run the interview below**87 (["Configure interactively"](#configure-interactively-interview)) — ask, fill88 defaults, write the file. See **Config reference** for every key. The key block:8990 ```yaml91 analysis:92 apiAnalysis:93 enabled: true94 sharedComponentMode: BOTH # CUMULATIVE | SEPARATE | BOTH95 classpathDirectories: # optional but improves call-graph resolution96 - build/libs97 jacocoReportPath: build/reports/jacoco/test/jacocoTestReport.xml98 output:99 formats: [yaml, md, html]100 apiLayout: BOTH # COMBINED | STANDALONE | BOTH101 topN: 30102 ```1031044. **Analyze** (add `--strict` in CI).105106 ```bash107 "$JAVA" -jar "$JAR" analyze --config hotspot.yml --strict108 # or, in one shot (resolves the runtime AND the jar for you):109 # skills/hotspot-analysis/scripts/run-analysis.sh hotspot.yml --strict110 ```111112 Outputs land in `output.path`. With API analysis on and `apiLayout: BOTH`:113114 ```115 hotspot-report/116 ├── api_report.yml ← STANDALONE: apiHotspots + sharedComponents (agent input)117 ├── hotspots.yml ← COMBINED: file + method + api + shared in one doc118 ├── hotspots.md / .html ← human-readable119 ├── file_hotspots.csv120 └── method_hotspots.csv121 ```1221235. **Consume the ranking for RestAssured.** Read `api_report.yml` and iterate124 `apiHotspots` in `compositeRank` order. Field-by-field schema:125 [`references/api-report-schema.md`](references/api-report-schema.md). Each row126 carries:127128 | Field | Use for test generation |129 |---|---|130 | `httpMethod`, `route` | The request: `given()...when().<method>(route)` |131 | `fqcn`, `method`, `parameters` | Controller signature → request body/param shape |132 | `callGraph` | Reachable methods → which downstream logic the endpoint exercises |133 | `coverageMultiplier` / `lineCoverage` | How under-tested the endpoint's logic is |134 | `compositeRank` | **The order to write tests in** |135 | `sharedComponents[]` | Methods many endpoints depend on — high-leverage to cover once |136137 Generate tests highest-rank first, targeting the least-covered paths in each138 endpoint's call graph. **Do not fabricate the ranking — run the CLI and read139 the actual file.**140141## Configure interactively (interview)142143A freshly-installed user usually can't write `hotspot.yml` cold. **Don't make144them.** Generate a starting file (`init`), then fill it by Q&A: auto-detect what145you can, ask only what's ambiguous, confirm, and write the file.146147Procedure:1481491. **Detect first, then ask.** Inspect the target repo to pre-fill defaults so150 most questions become a yes/no confirmation:151 - Spring app? — `grep -rl "@RestController\|@RequestMapping" <repo>/src` → if152 hits, default `apiAnalysis.enabled: true`.153 - Multi-module? — more than one `src/main/java` root → include both globs.154 - JaCoCo report present? — look for `**/jacoco/**/*.xml` (e.g.155 `build/reports/jacoco/test/jacocoTestReport.xml`) → default `jacocoReportPath`.156 - Built classes/jars? — `build/libs`, `build/classes` → default157 `apiAnalysis.classpathDirectories`.158 - Recent activity? — `git -C <repo> log -1 --format=%cd` → if older than a159 year, propose absolute `window.since`/`until` instead of `days`.1601612. **Ask, one decision at a time** (skip any you confidently detected — just162 state the default and let the user correct it):163164 | # | Question | Maps to | Default |165 |---|---|---|---|166 | 1 | Which repo to analyze? (path to the `.git/` working tree) | `analysis.target.path` | — (required) |167 | 2 | Prioritize REST API endpoints for test generation? | `apiAnalysis.enabled` | `true` if Spring detected |168 | 3 | Count churn over the last N days, or an absolute date range? | `window.days` or `window.since`/`until` | `days: 365` |169 | 4 | File-level, method-level, or both? | `scope.granularity` | `[file, method]` |170 | 5 | Have a JaCoCo coverage report? Where? | `analysis.jacocoReportPath` | detected path, else omit |171 | 6 | Dirs with built classes/dep jars (improves call graph)? | `apiAnalysis.classpathDirectories` | detected, else `[]` |172 | 7 | Shared-method handling? | `apiAnalysis.sharedComponentMode` | `BOTH` |173 | 8 | Output formats / where / how many rows? | `output.formats`/`path`/`topN` | `[csv,yaml,md,html]`, `./hotspot-report`, `30` |174 | 9 | Fail the run if the result is empty (CI)? | pass `--strict` at run time | no |1751763. **Write `hotspot.yml`** from the answers, **show it back** to the user for a177 final OK, then run step 4. If a tool like `AskUserQuestion` is available,178 prefer it for crisp multiple-choice prompts; otherwise ask in plain text.179180Keep it short: a typical session is "confirm repo path → confirm Spring/API on →181accept window default → confirm the detected JaCoCo path → go".182183## How the score is computed184185Four input factors → two scores. Full per-granularity derivations (with source186references and worked examples) live in **[`docs/scoring/`](../../docs/scoring/README.en.md)**:187[file](../../docs/scoring/file.en.md) ·188[method](../../docs/scoring/method.en.md) ·189[REST API endpoint](../../docs/scoring/rest-api-endpoint.en.md) ·190[shared component](../../docs/scoring/shared-component.en.md).191192| Factor / score | Definition |193|---|---|194| Revisions | Commits in the window that touched the artifact (method: diff-hunk overlap) |195| Recency Decay | `Σ exp(-ln(2)·Δt / halfLife)` over those commits — recent weighs more |196| Cognitive Complexity | SonarQube-style AST walk (file = sum of its methods) |197| Coverage Multiplier | `1/(lineCoverage + 0.1)` from JaCoCo; `1.0` if no report |198| Simple Score | `Revisions × LOC` (Tornhill's original) |199| Composite Score | `Cognitive Complexity × Recency Decay × Coverage Multiplier` |200201For an **API endpoint**, each factor is aggregated over the controller method202**plus its whole call graph**; coverage is the average over those methods.203Sorted by Composite DESC, ties broken deterministically (`route`,`httpMethod`).204205## Config reference206207```yaml208analysis:209 target:210 type: local-git # local-git | github (Phase 1 CLI: local-git end-to-end)211 path: /path/to/target/repo # must contain a .git/ folder212 window:213 days: 365 # Mode A: relative window from now214 # since: "2024-01-01" # Mode B: absolute ISO range (use INSTEAD of days)215 # until: "2026-01-01"216 scope:217 granularity: [file, method]218 include:219 - "src/main/java/**/*.java" # single-module repos220 - "**/src/main/java/**/*.java" # multi-module repos (list both if unsure)221 exclude:222 - "**/generated/**"223 - "**/test/**"224 - "**/build/**"225 scoring:226 decayHalfLifeDays: 90 # half-life for recency decay (days)227 excludeCoverage: false # true → Composite = CC × Decay; coverage shown raw, not scored228 apiAnalysis:229 enabled: true # off by default; required for api/shared granularities230 sharedComponentMode: BOTH # CUMULATIVE | SEPARATE | BOTH231 classpathDirectories: [] # dirs with dependency jars/classes for symbol resolution232 jacocoReportPath: build/reports/jacoco/test/jacocoTestReport.xml # optional233output:234 formats: [csv, yaml, md, html] # case-insensitive; ≥1 required235 apiLayout: BOTH # COMBINED (into hotspots.*) | STANDALONE (api_report.*) | BOTH236 coverageBreakdown: false # true → also write coverage_breakdown.yml: the audit237 # trail behind every coverage number (per-file counts;238 # per-endpoint per-method covered/executable lines)239 path: ./hotspot-report240 topN: 30 # 0 = all rows241```242243Env vars substitute as `${VAR_NAME}` in any string value; YAML comment lines244(`#`) are left untouched.245246### `sharedComponentMode`247248- `CUMULATIVE` — shared methods counted inside every endpoint's aggregate; no separate list.249- `SEPARATE` — shared methods excluded from endpoint aggregates and reported once on their own.250- `BOTH` (default) — endpoint aggregates include them **and** a separate shared list is emitted.251252### `analyze` options253254| Option | Effect |255|---|---|256| `--config, -c <file>` | Path to the YAML config (required) |257| `--output-dir, -o <dir>` | Directory to write the reports into (overrides `output.path`) |258| `--quiet, -q` | Suppress the stdout summary |259| `--strict, -s` | Exit code **3** on empty result (zero commits or zero files) — for CI gating |260261Exit codes: `0` ok · `1` config/pipeline failure · `2` usage error · `3` `--strict` empty result.262263## Decision rules (IF → THEN)264265- **IF** the user wants the order to write API tests in **THEN** read266 `api_report.yml` and iterate `apiHotspots` by ascending `compositeRank`.267- **IF** `apiHotspots` is empty but the app clearly has endpoints **THEN** check,268 in order: `apiAnalysis.enabled: true`, controllers carry269 `@RestController`/`@Controller` + a mapping annotation, and270 `apiAnalysis.classpathDirectories` includes the dependency jars/classes so271 cross-type calls resolve. Do **not** report "no endpoints".272- **IF** the run warns that files are "not present in the JaCoCo report" (their273 `coverageMultiplier` stays `1.0`) **THEN** the report doesn't cover those274 files — supply a report from the **same** build/module; do not conclude275 "nothing is tested". Broken/zero-coverage/partial reports are all276 auto-detected and treated as unknown coverage (multiplier `1.0` + warning),277 never as a silent 10x penalty. Regenerate with e.g.278 `./gradlew test jacocoTestReport` from the same checkout.279- **IF** the summary shows `Files: 0` **THEN** fix `scope.include` (single-module280 needs `src/main/java/**/*.java`, multi-module needs `**/src/main/java/**/*.java`;281 list both).282- **IF** the summary shows `Commits: 0` **THEN** widen `window.days` or switch to283 absolute `window.since`/`window.until` overlapping real activity.284- **IF** `target.type` is `github` **THEN** clone the repo locally and re-run with285 `target.type: local-git` (Phase 1 wires only `local-git` end-to-end).286- **IF** running in CI **THEN** pass `--strict` so an empty result fails loudly.287- **IF** you only need observational coverage, not coverage-driven scoring288 **THEN** set `scoring.excludeCoverage: true` (Composite becomes `CC × Decay`).289290## Anti-patterns & pitfalls291292- **Don't fabricate or estimate the ranking.** Run the jar and read the actual293 `api_report.yml`; the whole point is determinism, not a model guess.294- **Don't treat an empty `apiHotspots` as "no endpoints."** It almost always295 means `apiAnalysis` is off or the call graph couldn't resolve (missing296 `classpathDirectories`).297- **Don't feed a JaCoCo report from a different module/build.** Path mismatch298 reads as 0% coverage → every multiplier maxes at 10 and the ranking is bogus.299- **Don't run the jar on JDK < 21** — it's compiled for 21 (`UnsupportedClassVersionError`);300 `ensure-java.sh` guards this by only accepting 21+.301- **Don't analyze generated or build output** — keep `**/generated/**`,302 `**/build/**`, `**/target/**`, `**/test/**` in `scope.exclude`.303- **Don't present the Composite Score as a verdict.** It's prioritization304 evidence; surface the factors (churn, recency, complexity, coverage) so the305 choice is explainable.306- **Don't reorder by Simple Score when the goal is risk.** `compositeRank`, not307 `simpleRank`, is the test-priority signal.308309## Testing310311The project's own suite exercises every layer (parser, scoring, output, E2E):312313```bash314./gradlew test # comprehensive; run before trusting a build315```316317Skill-level smoke check — analyze this very repo and assert a non-empty result:318319```bash320bash -n skills/hotspot-analysis/scripts/ensure-java.sh skills/hotspot-analysis/scripts/get-jar.sh skills/hotspot-analysis/scripts/run-analysis.sh321JAVA="$(skills/hotspot-analysis/scripts/ensure-java.sh)" # resolves/downloads a java 21+322JAR="$(skills/hotspot-analysis/scripts/get-jar.sh)" # resolves/downloads the jar323"$JAVA" -jar "$JAR" init -o /tmp/h.yml -f324# set analysis.target.path in /tmp/h.yml to this repo's absolute path, then:325"$JAVA" -jar "$JAR" analyze --config /tmp/h.yml --strict326echo "exit=$?" # 0 = produced output; 3 = empty (misconfigured)327```328329A green `./gradlew test` plus a `0` exit on the smoke run means the skill's330toolchain is sound end-to-end.331332## Changelog333334- **0.1.6** — scoring-trust and report fixes from a fresh-eyes evaluation335 round: unknown coverage is never a 10x penalty (files absent from a336 partial JaCoCo report, line-less sourcefile entries, and uninstrumented337 methods all get multiplier 1.0 + a warning); switch expressions count338 toward cognitive complexity; duplicate sourcefile entries OR-merge;339 stale-report and shallow-clone warnings; unparseable files are skipped340 instead of aborting; HTML column sorting works on every table (pinned by341 a JS-executing smoke test) and the X-Ray drill-down is documented;342 clearer config errors (field + value + allowed values), `~/` expansion,343 `days` vs `since`/`until` now mutually exclusive, `init` refuses344 directory targets, `analyze` skips nothing silently.345- **0.1.5** — **zero-config**: `analyze` now runs without a config file346 (auto-detects git root, single/multi-module layout, JaCoCo report, Spring347 API), takes an optional `[path]`, `--print-config` dumps the synthesized348 config, the first run prints the top-3 hotspots + the report path, and349 linked git worktrees are supported. A one-line installer350 (`curl ... install.sh | bash`) provides the `hotspot` command. And the351 JDK-21 friction is gone: `scripts/ensure-java.sh` (and the352 `hotspot` wrapper installed by install.sh) finds an installed Java 21+ or353 auto-downloads a sha256-verified Temurin 21 JRE; `brew install354 baekchangjoon/tap/hotspot` installs with the JDK as a brew dependency; each355 release ships self-contained `hotspot-<tag>-<os>-<arch>.tar.gz` archives356 (bundled JRE, verified on 4 native CI runners before attach); an all-zero357 JaCoCo report (no execution data) now warns and disables coverage instead358 of silently inflating every multiplier to 10x; `analyze -o/--output-dir`359 overrides the report directory.360- **0.1.4** — endpoint coverage is now line-weighted (Σcovered/Σexecutable over361 the call graph) instead of a mean of per-method ratios, so a large untested362 method can no longer hide behind a small covered one; new opt-in363 `output.coverageBreakdown` writes `coverage_breakdown.yml`, the calculation364 trace behind every coverage number; releases enforce 4-way version365 consistency (tag = gradle = CLI = plugin/marketplace manifests).366- **0.1.3** — one-click `release` button + skills-validation CI gate + tag367 protection; the button reliably fans out to jar/image via `workflow_call`368 (a GITHUB_TOKEN-created release doesn't re-trigger event workflows).369- **0.1.2** — releases are now event-driven: every published release (incl. one370 created by `gh skill publish`) auto-attaches `hotspot.jar` and builds the371 Docker image, so a new release never breaks the download. `license` added to372 frontmatter.373- **0.1.1** — distribute the jar via GitHub Releases (version-stable374 `hotspot.jar` asset) + ghcr Docker image; a missing `jacocoReportPath` now375 warns and disables coverage instead of silently penalizing every artifact.376- **0.1.0** — initial skill: file / method / REST API endpoint / shared-component377 prioritization driving the Phase 1 CLI; RestAssured consumption guide;378 `apiAnalysis` + JaCoCo + `--strict` exposed; per-granularity scoring docs.379380## References381382- API report field schema: [`references/api-report-schema.md`](references/api-report-schema.md).383- Scoring derivations: [`docs/scoring/`](../../docs/scoring/README.en.md).384- Jar resolver / wrapper: [`scripts/get-jar.sh`](scripts/get-jar.sh) · [`scripts/run-analysis.sh`](scripts/run-analysis.sh).385- Prebuilt jar: [GitHub Releases](https://github.com/baekchangjoon/hotspot-analysis/releases/latest).386- Project README and `docs/` (architecture, advanced techniques, theory).387- Adam Tornhill, *Your Code as a Crime Scene*.