Skip to content

Aggregate hub classes and entry points from per-build analysis#8

Merged
ClankerGuru merged 4 commits into
mainfrom
fix/aggregate-per-build-analysis
Apr 13, 2026
Merged

Aggregate hub classes and entry points from per-build analysis#8
ClankerGuru merged 4 commits into
mainfrom
fix/aggregate-per-build-analysis

Conversation

@ClankerGuru
Copy link
Copy Markdown
Owner

@ClankerGuru ClankerGuru commented Apr 13, 2026

Summary

  • Removed monolithic analyzeCrossBuild() which tried to PSI-parse every source file across all included builds at once — this OOMed on large repos, causing hub classes, entry points, and cross-build reports to silently return empty
  • Now aggregates results from per-build ProjectSummary.analysis data that SymbolExtractor already computes per included build (same path anti-patterns use, which always worked)
  • Hub classes merged and ranked by dependent count across all builds (top 30)
  • Entry points classified from source set metadata (test vs app vs mock)

Test plan

  • ./gradlew build passes (all tests, detekt, ktlint, kover)
  • Workspace srcx-context generates hub classes (419 lines, was "No hub classes detected")
  • Dashboard warning count now includes all builds (was 0, now 8)
  • Test on large legacy repo that previously failed

Summary by CodeRabbit

  • Refactor
    • Simplified cross-build analysis aggregation.
    • Modified entry-point classification to use project source summaries.
    • Applied a 30-item hub limit for hub listings.
  • Bug Fixes
    • Made analysis extraction more resilient: failures are logged and produce null summaries instead of crashing.
  • Tests
    • Added unit tests validating aggregation, hub ranking, deduplication of findings and cycles, and null-handling across builds.

The cross-build PSI analysis tried to parse every source file across
all included builds in one shot, which OOMs on large repos. Hub classes,
entry points, and cross-build reports all silently returned empty.

Now aggregates results from per-build ProjectSummary.analysis data
that SymbolExtractor already computes per included build. This is the
same path anti-patterns use (which always worked).

Removed the monolithic analyzeCrossBuild() and its collectAllSourceDirs()
helper. Hub classes are merged and ranked by dependent count across
all builds. Entry points are classified from source set metadata.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Warning

Rate limit exceeded

@ClankerGuru has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 5 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 5 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c7f80a4-72fb-474d-a01f-73324071514a

📥 Commits

Reviewing files that changed from the base of the PR and between e31d2a8 and 08f3495.

📒 Files selected for processing (2)
  • src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
📝 Walkthrough

Walkthrough

Replaces cross-build source scanning and full analysis pipeline in ContextTask with aggregation of existing per-project AnalysisSummary results; entry-point discovery now derives from ProjectSummary.sourceSets symbols with heuristic TEST/MOCK labeling. Aggregated summary is passed to DashboardRenderer and CrossBuildRenderer for rendering.

Changes

Cohort / File(s) Summary
ContextTask core change
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
Removed cross-build scanning/analysis pipeline and helper methods. Added aggregateAnalysis(summaryList, includedBuildSummaries) and HUB_LIMIT = 30. Entry-point generation now uses ProjectSummary.sourceSets symbols with heuristic TEST/MOCK labeling. Aggregated summary wired into DashboardRenderer and CrossBuildRenderer; hub-classes.md now uses aggregated hubs.
Symbol extraction resilience
src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt
Wraps project analysis in runCatching, logs failures (special-case OOM message) via Gradle logger, and returns null analysis on failure to avoid task crash.
Tests
src/test/kotlin/zone/clanker/gradle/srcx/task/AggregateAnalysisTest.kt
Adds BehaviorSpec tests for aggregateAnalysis covering null-only inputs, single/multi-build hub aggregation and ranking, deduplication of findings and cycles, and mixed null/non-null summaries.

Sequence Diagram(s)

sequenceDiagram
    participant ContextTask as ContextTask
    participant Summaries as ProjectSummaries
    participant Aggregator as aggregateAnalysis()
    participant Dashboard as DashboardRenderer
    participant CrossBuild as CrossBuildRenderer

    Note over ContextTask,Summaries: start aggregation
    ContextTask->>Summaries: collect root + included-build ProjectSummary lists
    ContextTask->>Aggregator: aggregateAnalysis(summaryList, includedBuildSummaries)
    Aggregator->>Aggregator: merge hubs (top-N by dependentCount), dedupe findings & cycles
    Aggregator-->>ContextTask: AnalysisSummary? (aggregatedSummary)
    ContextTask->>Dashboard: render with aggregatedSummary
    ContextTask->>CrossBuild: render cross-build pages with aggregatedSummary
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
I nibble summaries, merge and sort,
Hop past tangled graphs and court,
Hubs ranked high, duplicates shed,
Entry points labeled, neatly led,
A tidy burrow, carrot-fed. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main architectural change: aggregating hub classes and entry points from per-build analysis instead of using the previous monolithic cross-build parsing approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/aggregate-per-build-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (1)

252-256: Make hub ranking deterministic for equal dependentCount.

Current ordering only uses dependentCount; ties can produce unstable output across runs. Add a secondary key (e.g., name) to reduce report churn.

Proposed refinement
         val allHubs =
             allAnalyses
                 .flatMap { it.hubs }
-                .sortedByDescending { it.dependentCount }
+                .sortedWith(
+                    compareByDescending<zone.clanker.gradle.srcx.model.HubClass> { it.dependentCount }
+                        .thenBy { it.name },
+                )
                 .take(HUB_LIMIT)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt` around lines
252 - 256, The current hub ranking in ContextTask.kt that builds allHubs (the
expression using allAnalyses.flatMap { it.hubs }.sortedByDescending {
it.dependentCount }.take(HUB_LIMIT)) is non-deterministic for equal
dependentCount; change the sort to a stable comparator that breaks ties by hub
name (e.g., sort by dependentCount descending then by name ascending) so allHubs
ordering is deterministic across runs while still honoring HUB_LIMIT.
src/test/kotlin/zone/clanker/gradle/srcx/task/AggregateAnalysisTest.kt (1)

37-168: Good coverage on merge/dedupe, but add regression tests for limit + entry-point classification.

Please add cases for (1) HUB_LIMIT truncation at 30 and (2) buildEntryPointsFromSummaries classifying APP/TEST/MOCK (including mock source-set naming). These are central to this refactor and currently unguarded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/kotlin/zone/clanker/gradle/srcx/task/AggregateAnalysisTest.kt`
around lines 37 - 168, Add two tests: one that verifies HUB_LIMIT truncation by
creating >30 HubClass entries (use HubClass constructor) across summaries and
asserting task.aggregateAnalysis(...).hubs.size == 30 and that they are the top
30 by dependent count; and another that exercises buildEntryPointsFromSummaries
by providing summaries with entry-point metadata whose sourceSet names indicate
APP, TEST and MOCK (include common mock naming like "testFixtures" or "mock" and
explicit test source-set) and assert resulting EntryPoint classifications are
APP/TEST/MOCK as expected. Reference aggregateAnalysis, HUB_LIMIT, HubClass, and
buildEntryPointsFromSummaries when locating where to add these tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 196-231: The current buildEntryPointsFromSummaries function drops
non-test/non-mock classes by returning null for their kind; update the mapping
so that when a symbol is neither isTest nor isMock you assign
EntryPointsRenderer.EntryKind.APP (instead of null) so all classes are emitted
as EntryPointsRenderer.ClassifiedEntry(name, symbol.packageName.value,
EntryKind.APP) and continue to deduplicate with distinctBy; refer to
buildEntryPointsFromSummaries, EntryPointsRenderer.EntryKind and
EntryPointsRenderer.ClassifiedEntry to locate and change the else branch.

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 252-256: The current hub ranking in ContextTask.kt that builds
allHubs (the expression using allAnalyses.flatMap { it.hubs }.sortedByDescending
{ it.dependentCount }.take(HUB_LIMIT)) is non-deterministic for equal
dependentCount; change the sort to a stable comparator that breaks ties by hub
name (e.g., sort by dependentCount descending then by name ascending) so allHubs
ordering is deterministic across runs while still honoring HUB_LIMIT.

In `@src/test/kotlin/zone/clanker/gradle/srcx/task/AggregateAnalysisTest.kt`:
- Around line 37-168: Add two tests: one that verifies HUB_LIMIT truncation by
creating >30 HubClass entries (use HubClass constructor) across summaries and
asserting task.aggregateAnalysis(...).hubs.size == 30 and that they are the top
30 by dependent count; and another that exercises buildEntryPointsFromSummaries
by providing summaries with entry-point metadata whose sourceSet names indicate
APP, TEST and MOCK (include common mock naming like "testFixtures" or "mock" and
explicit test source-set) and assert resulting EntryPoint classifications are
APP/TEST/MOCK as expected. Reference aggregateAnalysis, HUB_LIMIT, HubClass, and
buildEntryPointsFromSummaries when locating where to add these tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ec941d85-64b7-4fcd-a6a0-4c4c3a7acfbf

📥 Commits

Reviewing files that changed from the base of the PR and between 2159b13 and 5087d1e.

📒 Files selected for processing (2)
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/task/AggregateAnalysisTest.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
When PSI analysis runs out of memory, now logs:
  "srcx: Out of memory analyzing 'project'. Increase heap with
   org.gradle.jvmargs=-Xmx8g in gradle.properties"

Other failures log the specific error message instead of silently
returning null.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt`:
- Around line 45-54: handleAnalysisFailure currently logs OutOfMemoryError but
callers use getOrNull() which swallows OOM; change behavior so OOM is rethrown
to fail fast: add a helper (e.g., finalizeAnalysisFailure(e: Throwable,
projectName: String): AnalysisSummary?) that calls handleAnalysisFailure and
then throws when e is an OutOfMemoryError, otherwise returns null, and replace
occurrences that do .onFailure { e -> handleAnalysisFailure(e, ...)
}.getOrNull() with .getOrElse { e -> finalizeAnalysisFailure(e, projectName) }
(or equivalent) so non-fatal exceptions still return null but OutOfMemoryError
is propagated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d499ba35-f1e5-423e-9777-0084fb37b4b7

📥 Commits

Reviewing files that changed from the base of the PR and between 5087d1e and e31d2a8.

📒 Files selected for processing (1)
  • src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt Outdated
…hub sort

- APP entry points were dropped (mapped to null) — now classified as APP
- OOM rethrows after logging hint instead of silently returning null
- Hub ranking uses secondary sort by name for deterministic output
@ClankerGuru ClankerGuru merged commit 76e2e6c into main Apr 13, 2026
2 checks passed
@ClankerGuru ClankerGuru deleted the fix/aggregate-per-build-analysis branch April 13, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant