Skip to content

Add forbidden name detection, DI violations, and severity icons#7

Merged
ClankerGuru merged 13 commits intomainfrom
enhance-anti-pattern-detection
Apr 13, 2026
Merged

Add forbidden name detection, DI violations, and severity icons#7
ClankerGuru merged 13 commits intomainfrom
enhance-anti-pattern-detection

Conversation

@ClankerGuru
Copy link
Copy Markdown
Owner

@ClankerGuru ClankerGuru commented Apr 11, 2026

Summary

  • Add FORBIDDEN severity level (🚫) alongside WARNING (⚠️) and INFO (ℹ️)
  • Detect forbidden package names: utils, helpers, managers, misc, base
  • Detect dependency inversion violations: flag concrete class usage when an interface exists
  • Update all renderers to use emoji severity icons
  • Smell classes in forbidden-named packages elevated to FORBIDDEN severity

Test plan

  • Forbidden package name detection flags com.example.helpers
  • Smell class in forbidden package gets FORBIDDEN severity
  • DI violation: concrete with interface → WARNING
  • DI violation: concrete without interface → INFO with extract suggestion
  • Severity icons render correctly in all three report types
  • Existing anti-pattern tests still pass

Summary by CodeRabbit

  • New Features

    • Added FORBIDDEN severity and configurable forbidden package/class pattern checks; added dependency-inversion detection and cross-build analysis with split detail reports (anti-patterns, hub classes, entry points, interfaces, cross-build, flows).
  • Style

    • Findings now display emoji/icon severity labels; FORBIDDEN counted alongside warnings and surfaced prominently.
  • Tests

    • Expanded test coverage for forbidden detections, dependency-inversion, renderers, and severity/icon rendering.

- Add FORBIDDEN severity level with 🚫 icon to AntiPattern.Severity and FindingSeverity
- Detect forbidden package names (utils, helpers, managers, common, misc, base, shared)
- Detect dependency inversion violations: flag concrete class usage when interface exists
- Update all renderers (Dashboard, ProjectReport, IncludedBuild) to use emoji severity icons
- Smell classes in forbidden-named packages elevated to FORBIDDEN severity
- Tests for all new detection rules
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a FORBIDDEN severity and icons; implements configurable forbidden-package and forbidden-class-name checks plus dependency-inversion detection; propagates new severity through models and renderers; introduces multiple split report renderers and files; wires new DSL settings and task inputs; updates and adds tests.

Changes

Cohort / File(s) Summary
Anti-Pattern Detection
src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt
Adds AntiPattern.Severity.FORBIDDEN and icon changes; detectAntiPatterns(...) gains forbiddenPackages and forbiddenClassPatterns; adds detectForbiddenNames, detectForbiddenClassNames, dependency-inversion detection, and dedupe-by-message.
Analysis API & Model
src/main/kotlin/zone/clanker/gradle/srcx/analysis/ProjectAnalysis.kt, src/main/kotlin/zone/clanker/gradle/srcx/model/AnalysisSummary.kt
analyzeProject(...) signature extended to accept forbidden package/class patterns; FindingSeverity adds FORBIDDEN and per-constant icon; mapping updated in toSummary().
Context Task & DSL
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt, src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
Adds task inputs forbiddenPackages and forbiddenClassSuffixes; replaces class-diagram-only flow with cross-build analysis that returns diagram+analysis; writes split files (anti-patterns, interfaces, hot-classes, entry-points, cross-build, flows); adds DSL properties and methods plus DEFAULT_* constants.
Report Rendering (overview & included builds)
src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt, .../IncludedBuildRenderer.kt, .../ProjectReportRenderer.kt
Dashboard now links to split detail files and treats FORBIDDEN as a warning for counts; renderers use severity.icon and group/format forbidden/warning/info accordingly; removed inline symbol/problem rendering.
New Split Renderers
src/main/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRenderer.kt, .../CrossBuildRenderer.kt, .../EntryPointsRenderer.kt, .../FlowRenderer.kt, .../HotClassesRenderer.kt, .../InterfacesRenderer.kt
New internal renderers producing anti-patterns.md, cross-build.md, entry-points.md, per-flow diagrams, hot-classes.md, and interfaces.md; include aggregation, dedupe, table/section formatting, and diagram splitting.
Component Classification / Entry Points / Layers
src/main/kotlin/zone/clanker/gradle/srcx/analysis/ComponentClassifier.kt
Adds ArchitecturalLayer enum and detectLayer, detectLayers; adds entry-point classification (EntryPointKind, ClassifiedEntryPoint) and classifyEntryPoints(...) with heuristics and graph-root fallback.
Tests
src/test/kotlin/zone/clanker/gradle/srcx/...
Expanded and new tests: AntiPatternDetector tests for forbidden packages/class patterns and dependency-inversion; added tests for new renderers (AntiPatterns, CrossBuild, EntryPoints, Flow, HotClasses, Interfaces); updated renderer tests to expect emoji icons and split links; other classifier tests extended.
Build/config
settings.gradle.kts
Adds explicit plugin repo declarations and applies zone.clanker.gradle.srcx plugin (dev version).

Sequence Diagram

sequenceDiagram
    participant Source as SourceFiles
    participant Detector as AntiPatternDetector
    participant Resolver as TypeResolver
    participant Analysis as ProjectAnalysis
    participant Task as ContextTask
    participant Renderer as ReportRenderers

    Source->>Detector: provide classified components & metadata
    Detector->>Detector: detectForbiddenNames(forbiddenPackages)
    Detector->>Detector: detectForbiddenClassNames(forbiddenClassPatterns)
    Detector->>Resolver: resolve imports -> concrete types & interfaces
    Resolver->>Detector: return resolved types/interfaces
    Detector->>Detector: detectDependencyInversionViolations()
    Detector->>Analysis: emit findings (FORBIDDEN/WARNING/INFO) deduped by message
    Task->>Analysis: call analyzeProject with forbidden settings (cross-build)
    Analysis->>Renderer: supply summaries and cross-build analysis
    Renderer->>Renderer: group by severity and render split markdown files
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through packages, found names to fear,
I flagged forbidden helpers loud and clear,
I chased concrete chains and rang the bell,
Three tiny icons now ring the knell,
A carrot-cheer for cleaner code, my dear! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.13% 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
Title check ✅ Passed The title 'Add forbidden name detection, DI violations, and severity icons' directly and clearly summarizes the main changes in the changeset.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 enhance-anti-pattern-detection

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 (5)
src/test/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetectorTest.kt (1)

220-232: Minor: consider restructuring the icon tests.

The when block title mentions only FORBIDDEN, but the nested then blocks test all three severity icons. Consider renaming for clarity or restructuring.

♻️ Suggested restructure
-            `when`("severity FORBIDDEN has the correct icon") {
-                then("FORBIDDEN icon is the no-entry emoji") {
+            `when`("severity icons are defined correctly") {
+                then("FORBIDDEN icon is the no-entry emoji") {
                     AntiPattern.Severity.FORBIDDEN.icon shouldBe "\uD83D\uDEAB"
                 }

                 then("WARNING icon is the warning emoji") {
🤖 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/analysis/AntiPatternDetectorTest.kt`
around lines 220 - 232, The test block in AntiPatternDetectorTest mixes
assertions for multiple severities while its `when` title only mentions
FORBIDDEN; split or rename it so each severity is clearly described: either
rename the `when` to something like "`severity icons`" or create separate
`when`/`then` blocks for AntiPattern.Severity.FORBIDDEN,
AntiPattern.Severity.WARNING, and AntiPattern.Severity.INFO, keeping each
assertion (accessing the `.icon` property) under the matching `when`/`then` to
make intent and failure messages clear.
src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt (1)

54-56: Consider extracting the warning count predicate.

The same filtering logic it.severity == FindingSeverity.WARNING || it.severity == FindingSeverity.FORBIDDEN is repeated three times. A helper would improve maintainability.

♻️ Suggested helper

Add a helper method or extension:

private fun isActionableSeverity(severity: FindingSeverity): Boolean =
    severity == FindingSeverity.WARNING || severity == FindingSeverity.FORBIDDEN

Then replace the repeated predicate:

-s.analysis?.findings?.count {
-    it.severity == FindingSeverity.WARNING || it.severity == FindingSeverity.FORBIDDEN
-} ?: 0
+s.analysis?.findings?.count { isActionableSeverity(it.severity) } ?: 0

Also applies to: 92-95, 131-133

🤖 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/report/DashboardRenderer.kt` around
lines 54 - 56, Extract the repeated predicate used to count actionable findings
into a single helper (e.g., add a private function
isActionableSeverity(severity: FindingSeverity): Boolean) and replace the three
inline predicates (the occurrences inside the s.analysis?.findings?.count { ...
} expressions) with calls to that helper; update all instances referenced in
DashboardRenderer (the counts at the original lines and the other two
occurrences mentioned) to call isActionableSeverity(it.severity) so the logic is
centralized and easier to maintain.
src/main/kotlin/zone/clanker/gradle/srcx/report/IncludedBuildRenderer.kt (1)

160-160: Consider removing the suppression annotation.

The appendProblems() method appears streamlined after the refactoring—the control flow is now a simple if-check, a single loop, and conditional cycle rendering. The suppression may no longer be necessary.

♻️ Suggested removal
-    `@Suppress`("CyclomaticComplexMethod")
     private fun StringBuilder.appendProblems() {
🤖 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/report/IncludedBuildRenderer.kt` at
line 160, The `@Suppress`("CyclomaticComplexMethod") annotation above
appendProblems() is likely no longer needed; remove that suppression annotation
(the one immediately preceding the appendProblems function declaration in
IncludedBuildRenderer) and then run the static-analysis/lint and compile to
confirm no new warnings or errors; if any remaining complexity warnings
reference appendProblems, simplify branching or narrow the suppression to
specific cases instead of the whole method.
src/main/kotlin/zone/clanker/gradle/srcx/report/ProjectReportRenderer.kt (1)

89-107: Consider consolidating the three rendering loops.

The three loops have identical rendering logic. A single sorted loop would reduce duplication and align with the approach in IncludedBuildRenderer.appendProblems().

♻️ Suggested consolidation
-        val forbidden = analysis.findings.filter { it.severity == FindingSeverity.FORBIDDEN }
-        val warnings = analysis.findings.filter { it.severity == FindingSeverity.WARNING }
-        val infos = analysis.findings.filter { it.severity == FindingSeverity.INFO }
-
-        if (forbidden.isNotEmpty() || warnings.isNotEmpty() || infos.isNotEmpty()) {
+        if (analysis.findings.isNotEmpty()) {
             appendLine("## Findings")
             appendLine()
-            for (f in forbidden) {
-                appendLine("- **${f.severity.icon}** ${f.message}")
-                appendLine("  - ${f.suggestion}")
-            }
-            for (f in warnings) {
-                appendLine("- **${f.severity.icon}** ${f.message}")
-                appendLine("  - ${f.suggestion}")
-            }
-            for (f in infos) {
+            for (f in analysis.findings.sortedBy { it.severity }) {
                 appendLine("- **${f.severity.icon}** ${f.message}")
                 appendLine("  - ${f.suggestion}")
             }
             appendLine()
         }
🤖 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/report/ProjectReportRenderer.kt`
around lines 89 - 107, The three nearly identical loops that render findings
(variables forbidden, warnings, infos derived from analysis.findings filtered by
FindingSeverity) should be consolidated into a single iteration: build one list
from analysis.findings (or group/sort by severity using FindingSeverity order),
then iterate once and call the same appendLine logic for each item (reuse the
same string formatting used currently: "- **${f.severity.icon}** ${f.message}"
and "  - ${f.suggestion}"); mirror the approach used in
IncludedBuildRenderer.appendProblems() to sort/group and render in
ProjectReportRenderer (preserve the initial check for empty lists before writing
the "## Findings" header).
src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt (1)

173-174: Consider adding a clarifying comment for the data class exclusion.

Including isDataClass in the abstraction check is a reasonable design choice (DTOs/value objects typically don't need interfaces), but it's non-obvious. A brief comment would help future readers understand the intent.

// Data classes are excluded as they're typically value objects that don't require abstraction
val isAbstraction = resolved.source.isInterface || resolved.source.isAbstract || resolved.source.isDataClass
🤖 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/analysis/AntiPatternDetector.kt`
around lines 173 - 174, Add a brief clarifying comment above the isAbstraction
check in AntiPatternDetector (the mapNotNull block where
resolved.source.isInterface / isAbstract / isDataClass are evaluated) explaining
why data classes are treated as abstractions to be excluded (e.g., "Data classes
are excluded as they're typically value/DTO objects that don't require an
interface or further abstraction"), so future readers understand the intent
behind including resolved.source.isDataClass in the condition.
🤖 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/analysis/AntiPatternDetector.kt`:
- Around line 193-201: The message in AntiPatternDetector.kt is misleading
because the detection scans imports (symbol `c` / `c.source.simpleName`) not
constructor parameters; update the `message` (and optionally `suggestion`) where
`ifaceName` and `c.source.simpleName` are used to say something like "Code
imports concrete `<class>` instead of interface `<ifaceName>`" or "Imports
concrete `<class>` instead of interface `<ifaceName>`" so it reflects
import-based detection rather than "Constructor takes..."; keep the rest of the
diagnostic (severity, file, suggestion) unchanged except to rephrase the
suggestion text to "Depend on the interface `<ifaceName>` instead of the
concrete class..." if needed.

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt`:
- Around line 173-174: Add a brief clarifying comment above the isAbstraction
check in AntiPatternDetector (the mapNotNull block where
resolved.source.isInterface / isAbstract / isDataClass are evaluated) explaining
why data classes are treated as abstractions to be excluded (e.g., "Data classes
are excluded as they're typically value/DTO objects that don't require an
interface or further abstraction"), so future readers understand the intent
behind including resolved.source.isDataClass in the condition.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt`:
- Around line 54-56: Extract the repeated predicate used to count actionable
findings into a single helper (e.g., add a private function
isActionableSeverity(severity: FindingSeverity): Boolean) and replace the three
inline predicates (the occurrences inside the s.analysis?.findings?.count { ...
} expressions) with calls to that helper; update all instances referenced in
DashboardRenderer (the counts at the original lines and the other two
occurrences mentioned) to call isActionableSeverity(it.severity) so the logic is
centralized and easier to maintain.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/IncludedBuildRenderer.kt`:
- Line 160: The `@Suppress`("CyclomaticComplexMethod") annotation above
appendProblems() is likely no longer needed; remove that suppression annotation
(the one immediately preceding the appendProblems function declaration in
IncludedBuildRenderer) and then run the static-analysis/lint and compile to
confirm no new warnings or errors; if any remaining complexity warnings
reference appendProblems, simplify branching or narrow the suppression to
specific cases instead of the whole method.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ProjectReportRenderer.kt`:
- Around line 89-107: The three nearly identical loops that render findings
(variables forbidden, warnings, infos derived from analysis.findings filtered by
FindingSeverity) should be consolidated into a single iteration: build one list
from analysis.findings (or group/sort by severity using FindingSeverity order),
then iterate once and call the same appendLine logic for each item (reuse the
same string formatting used currently: "- **${f.severity.icon}** ${f.message}"
and "  - ${f.suggestion}"); mirror the approach used in
IncludedBuildRenderer.appendProblems() to sort/group and render in
ProjectReportRenderer (preserve the initial check for empty lists before writing
the "## Findings" header).

In
`@src/test/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetectorTest.kt`:
- Around line 220-232: The test block in AntiPatternDetectorTest mixes
assertions for multiple severities while its `when` title only mentions
FORBIDDEN; split or rename it so each severity is clearly described: either
rename the `when` to something like "`severity icons`" or create separate
`when`/`then` blocks for AntiPattern.Severity.FORBIDDEN,
AntiPattern.Severity.WARNING, and AntiPattern.Severity.INFO, keeping each
assertion (accessing the `.icon` property) under the matching `when`/`then` to
make intent and failure messages clear.
🪄 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: 04299182-d4cf-4c44-b421-1d4e6bc77c9c

📥 Commits

Reviewing files that changed from the base of the PR and between 1d0f29a and 9d08aa1.

📒 Files selected for processing (9)
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/ProjectAnalysis.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/model/AnalysisSummary.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/IncludedBuildRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/ProjectReportRenderer.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetectorTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/IncludedBuildRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/ProjectReportAnalysisTest.kt

ContextTask now collects source dirs from root project AND all
included builds, runs the full analysis pipeline on the merged
set. Hub classes referenced across build boundaries now appear
in a Hot Classes (cross-build) section with a table and per-hub
dependent lists. Uses model-layer AnalysisSummary to respect
package boundary rules (report does not import from analysis).
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/report/DashboardRenderer.kt (1)

56-61: Consider: Summary label says "warnings" but includes FORBIDDEN findings.

The count includes both WARNING and FORBIDDEN severities, but the label remains "warnings". Users might expect FORBIDDEN to be counted separately or the label to reflect both. This is consistent across the file but worth considering for clarity.

🤖 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/report/DashboardRenderer.kt` around
lines 56 - 61, The calc for totalWarnings mixes FindingSeverity.WARNING and
FindingSeverity.FORBIDDEN so the label "warnings" is misleading; update
DashboardRenderer.kt to separate counts: compute totalWarnings by counting only
FindingSeverity.WARNING and compute totalForbidden by counting
FindingSeverity.FORBIDDEN (or rename totalWarnings to totalWarningsAndForbidden
if you prefer a combined metric), then use the appropriate variable(s) when
rendering labels so the UI shows distinct "warnings" and "forbidden" counts (or
a combined label that clearly indicates both).
src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt (1)

111-140: Unused rootDir parameter.

The rootDir parameter is suppressed as unused but remains in the signature. If it's intentionally unused (since package-level findings use File(".")), consider removing it to simplify the API. If other detection functions require a consistent signature, this is acceptable as-is.

♻️ Option: Remove unused parameter
-@Suppress("UnusedParameter")
-private fun detectForbiddenNames(
-    components: List<ClassifiedComponent>,
-    rootDir: File,
-): List<AntiPattern> {
+private fun detectForbiddenNames(
+    components: List<ClassifiedComponent>,
+): List<AntiPattern> {

And update the call site at line 52:

-    patterns.addAll(detectForbiddenNames(components, rootDir))
+    patterns.addAll(detectForbiddenNames(components))
🤖 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/analysis/AntiPatternDetector.kt`
around lines 111 - 140, The detectForbiddenNames function has an unused rootDir
parameter; remove rootDir from the signature of detectForbiddenNames and update
all call sites that pass a rootDir (so callers match the new signature), and
keep the body using File(".") as before (or, if you prefer to surface a real
file, switch the AntiPattern.file to use a representative file from
ClassifiedComponent.source where available); ensure you update references to
detectForbiddenNames everywhere to avoid compilation errors.
🤖 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/analysis/AntiPatternDetector.kt`:
- Around line 39-40: FORBIDDEN_PACKAGE_NAMES in AntiPatternDetector.kt is
missing "common" and "shared"; update the set declared as
FORBIDDEN_PACKAGE_NAMES to include "common" and "shared" (so it matches the PR
objectives: utils, helpers, managers, common, misc, base, shared), ensuring the
change appears inside the same val/set declaration used by the
AntiPatternDetector logic.

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt`:
- Around line 111-140: The detectForbiddenNames function has an unused rootDir
parameter; remove rootDir from the signature of detectForbiddenNames and update
all call sites that pass a rootDir (so callers match the new signature), and
keep the body using File(".") as before (or, if you prefer to surface a real
file, switch the AntiPattern.file to use a representative file from
ClassifiedComponent.source where available); ensure you update references to
detectForbiddenNames everywhere to avoid compilation errors.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt`:
- Around line 56-61: The calc for totalWarnings mixes FindingSeverity.WARNING
and FindingSeverity.FORBIDDEN so the label "warnings" is misleading; update
DashboardRenderer.kt to separate counts: compute totalWarnings by counting only
FindingSeverity.WARNING and compute totalForbidden by counting
FindingSeverity.FORBIDDEN (or rename totalWarnings to totalWarningsAndForbidden
if you prefer a combined metric), then use the appropriate variable(s) when
rendering labels so the UI shows distinct "warnings" and "forbidden" counts (or
a combined label that clearly indicates both).
🪄 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: bd34ac4f-6366-4378-b873-5138e2743bcf

📥 Commits

Reviewing files that changed from the base of the PR and between 9d08aa1 and f387428.

📒 Files selected for processing (3)
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
✅ Files skipped from review due to trivial changes (1)
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt Outdated
Add forbiddenPackages and forbiddenClassSuffixes to the srcx
extension DSL. Both are additive on top of defaults. Also adds
detectForbiddenClassNames for suffix-based class name detection.

Usage:
  srcx {
      forbiddenPackages.add("legacy")
      forbiddenClassSuffixes.add("BaseActivity")
  }
- forbiddenPackages("legacy", "internal") vararg syntax
- forbiddenClassPatterns("Base", "Impl") vararg syntax
- Class name matching uses contains instead of endsWith
  so BaseActivity, DataHelperImpl, AbstractManager all match
- Rename DEFAULT_FORBIDDEN_CLASS_SUFFIXES to DEFAULT_FORBIDDEN_CLASS_PATTERNS
- Remove class doc comments from constants
Extract long string literals into vals. Use triple-quoted templates
where needed. Remove @Suppress("MaxLineLength") annotations.
All messages stay as single strings, no + concatenation.
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: 2

♻️ Duplicate comments (1)
src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt (1)

214-215: ⚠️ Potential issue | 🟡 Minor

Misleading message: detection is import-based, not constructor-based.

The message says "Constructor takes concrete" but this detection scans imports (line 191), not constructor parameters. An import may be used anywhere—field declarations, method parameters, local variables, static calls—not necessarily in a constructor.

This was flagged in a previous review but appears unaddressed.

🔧 Proposed fix
-            "Constructor takes concrete `$concreteName` instead of interface `$ifaceName`"
+            "Depends on concrete `$concreteName` instead of interface `$ifaceName`"
🤖 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/analysis/AntiPatternDetector.kt`
around lines 214 - 215, The diagnostic message in AntiPatternDetector currently
reads "Constructor takes concrete `$concreteName` instead of interface
`$ifaceName`" but the detector scans imports (see import-checking logic around
the imports scan) not constructor parameters; update the message string `msg` to
accurately reflect import-based detection (e.g. "Import of concrete
`$concreteName` instead of interface `$ifaceName`" or similar) so it matches the
actual check performed by the code in AntiPatternDetector.
🧹 Nitpick comments (3)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (2)

91-97: Naming inconsistency: forbiddenClassSuffixes vs forbiddenClassPatterns.

The property is named forbiddenClassSuffixes but the PR description and the analyzeProject function signature (see ProjectAnalysis.kt:64-75) use forbiddenClassPatterns. The matching logic uses contains() (pattern-based), not endsWith() (suffix-based). This mismatch may confuse future maintainers.

Consider renaming to forbiddenClassPatterns for consistency with the actual behavior.

🤖 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 91
- 97, Rename the task property forbiddenClassSuffixes to forbiddenClassPatterns
to match the actual pattern-based matching behavior and the analyzeProject
signature; update the abstract property name in ContextTask (keeping `@get`:Input
and SetProperty<String>), and update all call sites/usages (e.g., where
ContextTask.forbiddenClassSuffixes is passed to analyzeProject or referenced
elsewhere) so they use forbiddenClassPatterns and preserve types and
annotations; also update any documentation/comments to reflect "patterns"
instead of "suffixes".

177-188: Duplicate work: sources are scanned and classified twice.

analyzeProject (line 178) internally calls scanSources and classifyAll (see ProjectAnalysis.kt:70-72). Lines 184-187 then repeat these same operations on the same allDirs. This doubles the work unnecessarily.

Consider either extracting the diagram generation into ProjectAnalysis or reusing its internal results.

♻️ Possible refactor
     return runCatching {
         val analysis =
             analyzeProject(
                 allDirs,
                 rootDir,
                 forbiddenPackages.get(),
                 forbiddenClassSuffixes.get(),
             )
-        val sources = scanSources(allDirs)
-        val components = classifyAll(sources)
-        val depEdges = buildDependencyGraph(components)
-        val diagram = generateDependencyDiagram(components, depEdges)
+        val diagram = analysis.generateDependencyDiagram() // if exposed
         diagram to analysis
     }
🤖 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
177 - 188, analyzeProject currently performs scanSources and classifyAll
internally, but the code then calls scanSources and classifyAll again to build
the diagram, causing duplicated work; modify the implementation so you reuse the
results from analyzeProject (e.g., extend analyzeProject to return
ProjectAnalysis that includes sources/components or expose getter methods for
sources/components) and then call generateDependencyDiagram with those returned
components/depEdges instead of re-scanning; alternatively move
generateDependencyDiagram into ProjectAnalysis (or add a toDiagram() method) and
call that to produce the diagram, ensuring analyzeProject, scanSources,
classifyAll and generateDependencyDiagram are not invoked twice for the same
allDirs.
src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt (1)

56-61: Label says "warnings" but count includes FORBIDDEN findings.

totalWarnings aggregates both WARNING and FORBIDDEN severities, but the label at line 71 only says "warnings". Users may not realize FORBIDDEN items are included in this count. Consider either renaming the variable/label to "issues" or separating the counts.

🤖 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/report/DashboardRenderer.kt` around
lines 56 - 61, The variable totalWarnings in DashboardRenderer aggregates both
FindingSeverity.WARNING and FindingSeverity.FORBIDDEN but the UI label only says
"warnings"; update the code to make this accurate by either (A) renaming
totalWarnings and its usage/label to a neutral term like
totalIssues/totalFindings and update the displayed label accordingly, or (B)
split the calculation into two distinct counters (e.g., totalWarnings for
FindingSeverity.WARNING and totalForbidden for FindingSeverity.FORBIDDEN) and
update the renderer to display both counts separately; locate the aggregation
where totalWarnings is computed and any UI label rendering (references:
totalWarnings, FindingSeverity.WARNING, FindingSeverity.FORBIDDEN,
DashboardRenderer) and make the corresponding variable/label changes so counts
and text match.
🤖 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/analysis/AntiPatternDetector.kt`:
- Around line 150-152: The filter that excludes test files in
AntiPatternDetector.kt currently only checks for "/test/" (the anonymous .filter
block around the code using it); update this to handle Windows paths too by
matching both forward and backslash separators (e.g., check contains("/test/")
OR contains("\\test\\") or normalize the path before checking), mirroring the
other checks in this file (see similar logic around lines referenced for
consistency) so test file detection is platform-independent.

In `@src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt`:
- Around line 75-79: DEFAULT_FORBIDDEN_PACKAGES is missing two entries from the
spec; update the Set assigned to DEFAULT_FORBIDDEN_PACKAGES in Srcx.kt to
include "common" and "shared" so it matches the PR objectives (keep the existing
entries: "util","utils","helper","helpers","manager","managers","misc","base"
and add "common","shared"). Ensure only the DEFAULT_FORBIDDEN_PACKAGES value is
changed and DEFAULT_FORBIDDEN_CLASS_PATTERNS remains untouched.

---

Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt`:
- Around line 214-215: The diagnostic message in AntiPatternDetector currently
reads "Constructor takes concrete `$concreteName` instead of interface
`$ifaceName`" but the detector scans imports (see import-checking logic around
the imports scan) not constructor parameters; update the message string `msg` to
accurately reflect import-based detection (e.g. "Import of concrete
`$concreteName` instead of interface `$ifaceName`" or similar) so it matches the
actual check performed by the code in AntiPatternDetector.

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt`:
- Around line 56-61: The variable totalWarnings in DashboardRenderer aggregates
both FindingSeverity.WARNING and FindingSeverity.FORBIDDEN but the UI label only
says "warnings"; update the code to make this accurate by either (A) renaming
totalWarnings and its usage/label to a neutral term like
totalIssues/totalFindings and update the displayed label accordingly, or (B)
split the calculation into two distinct counters (e.g., totalWarnings for
FindingSeverity.WARNING and totalForbidden for FindingSeverity.FORBIDDEN) and
update the renderer to display both counts separately; locate the aggregation
where totalWarnings is computed and any UI label rendering (references:
totalWarnings, FindingSeverity.WARNING, FindingSeverity.FORBIDDEN,
DashboardRenderer) and make the corresponding variable/label changes so counts
and text match.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 91-97: Rename the task property forbiddenClassSuffixes to
forbiddenClassPatterns to match the actual pattern-based matching behavior and
the analyzeProject signature; update the abstract property name in ContextTask
(keeping `@get`:Input and SetProperty<String>), and update all call sites/usages
(e.g., where ContextTask.forbiddenClassSuffixes is passed to analyzeProject or
referenced elsewhere) so they use forbiddenClassPatterns and preserve types and
annotations; also update any documentation/comments to reflect "patterns"
instead of "suffixes".
- Around line 177-188: analyzeProject currently performs scanSources and
classifyAll internally, but the code then calls scanSources and classifyAll
again to build the diagram, causing duplicated work; modify the implementation
so you reuse the results from analyzeProject (e.g., extend analyzeProject to
return ProjectAnalysis that includes sources/components or expose getter methods
for sources/components) and then call generateDependencyDiagram with those
returned components/depEdges instead of re-scanning; alternatively move
generateDependencyDiagram into ProjectAnalysis (or add a toDiagram() method) and
call that to produce the diagram, ensuring analyzeProject, scanSources,
classifyAll and generateDependencyDiagram are not invoked twice for the same
allDirs.
🪄 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: 802e59ea-64dd-4904-a27f-6a0260f9d4c6

📥 Commits

Reviewing files that changed from the base of the PR and between f387428 and 1fc9be4.

📒 Files selected for processing (5)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/ProjectAnalysis.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/ProjectAnalysis.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/analysis/AntiPatternDetector.kt Outdated
Comment on lines +75 to +79
val DEFAULT_FORBIDDEN_PACKAGES: Set<String> =
setOf("util", "utils", "helper", "helpers", "manager", "managers", "misc", "base")

val DEFAULT_FORBIDDEN_CLASS_PATTERNS: Set<String> =
setOf("Helper", "Manager", "Utils", "Util")
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if DEFAULT_FORBIDDEN_PACKAGES includes "common" and "shared" in the current code
rg -n "DEFAULT_FORBIDDEN_PACKAGES" --type=kotlin -A 3

Repository: ClankerGuru/srcx

Length of output: 2034


Add common and shared to DEFAULT_FORBIDDEN_PACKAGES.

The PR objectives specify forbidden package names as: utils, helpers, managers, common, misc, base, shared. The current DEFAULT_FORBIDDEN_PACKAGES set at line 76 is missing common and shared.

🤖 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/Srcx.kt` around lines 75 - 79,
DEFAULT_FORBIDDEN_PACKAGES is missing two entries from the spec; update the Set
assigned to DEFAULT_FORBIDDEN_PACKAGES in Srcx.kt to include "common" and
"shared" so it matches the PR objectives (keep the existing entries:
"util","utils","helper","helpers","manager","managers","misc","base" and add
"common","shared"). Ensure only the DEFAULT_FORBIDDEN_PACKAGES value is changed
and DEFAULT_FORBIDDEN_CLASS_PATTERNS remains untouched.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

╔══════════════════════════════════╗
║  ℹ️  Intentional                 ║
╚══════════════════════════════════╝

Same as the earlier comment -- "common" and "shared" were intentionally excluded from DEFAULT_FORBIDDEN_PACKAGES. These are valid, descriptive names used in many projects (e.g. Kotlin Multiplatform shared modules). The forbidden list targets genuinely vague names like "util", "helper", "manager", "misc", and "base". The plugin resolution analysis is also incorrect -- the plugin is registered in build.gradle.kts via the gradlePlugin block and resolved from the build-logic included build. settings.gradle.kts only applies "clkx-settings", not "zone.clanker.gradle.srcx".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ClankerGuru, understood and apologies for the noise! Both points are duly noted:

  1. common and shared exclusion — confirmed intentional; these are valid, widely-used package names (e.g. Kotlin Multiplatform shared modules) and the forbidden list is scoped to genuinely vague names like util, helper, manager, misc, and base.
  2. Plugin resolution — the plugin zone.clanker.gradle.srcx is registered via the gradlePlugin block in build.gradle.kts and resolved from the build-logic included build. settings.gradle.kts only applies clkx-settings, not zone.clanker.gradle.srcx.

I'll update my memory so I don't repeat these mistakes in future reviews.

(╯°□°)╯彡🐇 trips over wrong analysis, tumbles away


✏️ Learnings added
Learnt from: ClankerGuru
Repo: ClankerGuru/srcx PR: 7
File: src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt:75-79
Timestamp: 2026-04-13T14:29:46.817Z
Learning: In the srcx project (ClankerGuru/srcx), the `zone.clanker.gradle.srcx` plugin is registered via the `gradlePlugin` block in `build.gradle.kts` and resolved from the build-logic included build. `settings.gradle.kts` only applies the `clkx-settings` plugin, not `zone.clanker.gradle.srcx`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ClankerGuru
Repo: ClankerGuru/srcx PR: 0
File: :0-0
Timestamp: 2026-04-13T00:30:35.774Z
Learning: In the srcx project (ClankerGuru/srcx), the forbidden package names intentionally exclude `common` and `shared` because they are valid, widely-used package names in the codebase. Only `utils`, `helpers`, `managers`, `misc`, and `base` are treated as forbidden package names. The PR description listing `common` and `shared` as forbidden was inaccurate; the implementation is correct.

New split output files under .srcx/:
- hot-classes.md, entry-points.md, anti-patterns.md
- interfaces.md, cross-build.md, flows/{EntryPoint}.md

New analysis: ArchitecturalLayer enum, EntryPointKind classification.
context.md slimmed to overview + links to detail files.
6 new renderers with tests. Coverage at 91.8%.
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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/kotlin/zone/clanker/gradle/srcx/report/DashboardRendererTest.kt (1)

147-200: ⚠️ Potential issue | 🟡 Minor

Test intent and assertions are now misaligned

At Line 147, the scenario still says “hub classes and dependent names”, but Line 199-200 only verifies a generic project-row substring. This drops coverage for the hub/dependent behavior that the fixture is building and makes the test misleading.

Consider either renaming/simplifying the scenario setup, or restoring assertions that validate the hub/dependent-related output path.

🤖 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/report/DashboardRendererTest.kt`
around lines 147 - 200, The test description says it verifies "hub classes and
dependent names" but the assertion only checks for a generic project row; update
the test to assert the hub-related output produced by DashboardRenderer("test",
summaries, emptyList()).render() — specifically verify the rendered output
contains the hub class "Core" and the dependent names "A", "B", and "C" (and
optionally their paths or counts from HubClass/HubDependentRef) so the fixtures
(ProjectSummary, HubClass, HubDependentRef) are actually validated;
alternatively, if you intend a simpler test, rename the scenario to not mention
hubs and simplify the fixture to match the assertion.
🧹 Nitpick comments (6)
src/test/kotlin/zone/clanker/gradle/srcx/report/EntryPointsRendererTest.kt (1)

71-114: Consider adding test case for "Stub" pattern.

The test doubles test covers MockRepository and FakeService but doesn't test the Stub pattern that the implementation supports.

✨ Optional: Add Stub pattern test
                                             SymbolEntry(
                                                 SymbolName("FakeService"),
                                                 SymbolKind.CLASS,
                                                 PackageName("com.example"),
                                                 FilePath("FakeService.kt"),
                                                 1,
                                             ),
+                                            SymbolEntry(
+                                                SymbolName("StubClient"),
+                                                SymbolKind.CLASS,
+                                                PackageName("com.example"),
+                                                FilePath("StubClient.kt"),
+                                                1,
+                                            ),
                                         ),
                                         listOf("src/test/kotlin"),
                                     ),

And update the assertion:

                 then("it shows test doubles") {
                     output shouldContain "## Test Doubles"
                     output shouldContain "| `MockRepository` | com.example | Mock |"
                     output shouldContain "| `FakeService` | com.example | Fake |"
+                    output shouldContain "| `StubClient` | com.example | Stub |"
                 }
🤖 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/report/EntryPointsRendererTest.kt`
around lines 71 - 114, Add a test case entry to the existing "rendering with
test doubles" scenario to exercise the Stub pattern: in the summaries passed to
EntryPointsRenderer add a SymbolEntry with SymbolName("ExampleStub") (or
similar), SymbolKind.CLASS, PackageName("com.example"),
FilePath("ExampleStub.kt"), and ensure it's included in the same
SourceSetSummary; then extend the assertions after renderer.render() to expect
the Stub section/row—e.g., output shouldContain a table row containing
"`ExampleStub`" and "Stub" (alongside the existing Mock and Fake checks) so the
renderer's Stub handling is verified.
src/test/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRendererTest.kt (1)

113-154: Consider asserting which source is preserved during deduplication.

The deduplication test verifies the message appears once but doesn't assert which source (:a or :b) is retained. This could help catch regressions if the deduplication order changes.

✨ Optional: Verify preserved source
                 then("it shows the duplicate message only once") {
                     val occurrences = output.split("duplicate message").size - 1
                     occurrences shouldBe 1
+                    // Verify first source is preserved
+                    output shouldContain "**:a**"
                 }
🤖 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/report/AntiPatternsRendererTest.kt`
around lines 113 - 154, The test currently only asserts the duplicate message
appears once; update the test for AntiPatternsRenderer.render to also assert
which project source is preserved after deduplication — e.g., verify the output
contains the project path for the first summary (ProjectPath(":a")) and does not
contain the second (ProjectPath(":b")) to lock in the expected preservation
order; locate this behavior in the test block around
AntiPatternsRenderer(summaries).render() and add the corresponding positive and
negative assertions.
src/main/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRenderer.kt (1)

63-76: Deduplication may lose valuable source context.

The distinctBy { it.second.message } approach keeps only the first occurrence of each finding message, discarding subsequent sources. If the same anti-pattern appears in multiple projects (e.g., :app and :lib), the report will only show one source.

Consider whether you want to:

  1. Group findings by message and list all affected sources, or
  2. Keep the current behavior but document that only the first source is shown.
♻️ Optional: Group sources per finding message
-    private fun collectAllFindings(): List<Pair<String, Finding>> {
+    private fun collectAllFindings(): List<Pair<List<String>, Finding>> {
         val rootFindings =
             summaries.flatMap { s ->
                 val path = s.projectPath.value
                 s.analysis?.findings?.map { f -> path to f } ?: emptyList()
             }
         val buildFindings =
             includedBuildSummaries.flatMap { (name, projects) ->
                 projects.flatMap { s ->
                     s.analysis?.findings?.map { f -> name to f } ?: emptyList()
                 }
             }
-        return (rootFindings + buildFindings).distinctBy { it.second.message }
+        return (rootFindings + buildFindings)
+            .groupBy { it.second.message }
+            .map { (_, pairs) -> pairs.map { it.first } to pairs.first().second }
     }

Then update the rendering to show multiple sources:

appendLine("- ${finding.severity.icon} **${sources.joinToString(", ")}** — ${finding.message}")
🤖 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/report/AntiPatternsRenderer.kt`
around lines 63 - 76, collectAllFindings currently deduplicates by
finding.message using distinctBy which drops additional source context; change
it to group findings by it.second.message (use groupBy on message) and produce a
structure that preserves all source paths per message (e.g., map to
Pair<List<String>, Finding> or a small data class with sources and finding),
then update the renderer code that consumes collectAllFindings (in
AntiPatternsRenderer rendering block that builds lines) to iterate each grouped
entry and print all sources (joinToString) next to the finding message/severity;
alternatively, if you prefer minimal change, add a comment/docs near
collectAllFindings explaining that only the first source is shown.
src/main/kotlin/zone/clanker/gradle/srcx/report/InterfacesRenderer.kt (1)

124-135: Implementation counting heuristics look reasonable but may have edge cases.

The endsWith(baseName) check on line 132 could potentially over-count for short interface names. For example, if the interface is IService, the baseName becomes Service, and any class ending in Service (like UserService, OrderService) would be counted as an implementation.

This may be intentional to cast a wide net, but consider if you want tighter matching.

♻️ Optional: Tighter implementation matching
         private fun countImplementations(interfaceName: String, classNames: Set<String>): Int {
             val baseName = interfaceName.removePrefix("I")
             return classNames.count { cn ->
                 cn != interfaceName &&
                     (
                         cn == "${baseName}Impl" ||
                             cn == "Default$interfaceName" ||
                             cn == "Default$baseName" ||
-                            (cn.endsWith(baseName) && cn != baseName)
+                            (cn.endsWith(baseName) && cn != baseName && cn.length > baseName.length)
                     )
             }
         }
🤖 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/report/InterfacesRenderer.kt` around
lines 124 - 135, The heuristic in countImplementations can over-count because
cn.endsWith(baseName) will match things like UserService for IService; update
the logic in countImplementations to tighten suffix matches: keep the existing
exact-name and Default/Impl checks, but replace the broad cn.endsWith(baseName)
branch with a guard that only counts when the class actually has a CamelCase
boundary before the baseName (e.g., the character immediately before the matched
baseName is an uppercase letter) and optionally skip this suffix rule for very
short baseName values; modify the check around cn.endsWith(baseName) to verify
cn.length > baseName.length and that the char at cn.length - baseName.length - 1
isUpperCase() (and/or check baseName.length >= 3) so only true
implementation-style class names are counted.
src/main/kotlin/zone/clanker/gradle/srcx/report/CrossBuildRenderer.kt (1)

33-39: Prefer stable ordering for grouped dependencies

At Line 33-39, output order depends on incoming edge order. Sorting from groups and to entries makes generated markdown deterministic and reduces noisy diffs.

🤖 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/report/CrossBuildRenderer.kt` around
lines 33 - 39, The grouping iteration over buildEdges (val byFrom =
buildEdges.groupBy { it.from }) is non-deterministic; change the loop to iterate
over sorted group keys and sorted edge targets so output is stable: enumerate
byFrom.keys.sorted() (or byFrom.entries.sortedBy { it.key }) and for each
group's edges use edges.sortedBy { it.to } before appending lines for "from" and
each edge.to to produce deterministic markdown in CrossBuildRenderer (variables:
buildEdges, byFrom, from, edges, edge.to).
src/main/kotlin/zone/clanker/gradle/srcx/report/HotClassesRenderer.kt (1)

27-32: Make output ordering deterministic in the renderer

At Line 27 and Line 31, rendering relies on input order. If callers pass unsorted hubs, report ordering can fluctuate. Sorting locally keeps output stable and consistent with the KDoc expectation.

Proposed refactor
 internal class HotClassesRenderer(
     private val hubs: List<HubClass>,
 ) {
     fun render(): String =
         buildString {
+            val orderedHubs = hubs.sortedByDescending { it.dependentCount }
             appendLine("# Hot Classes")
             appendLine()
-            if (hubs.isEmpty()) {
+            if (orderedHubs.isEmpty()) {
                 appendLine("No hub classes detected.")
                 appendLine()
                 return@buildString
             }
             appendLine("| Class | File | Dependents | Role |")
             appendLine("|-------|------|------------|------|")
-            for (hub in hubs) {
+            for (hub in orderedHubs) {
                 appendLine("| `${hub.name}` | ${hub.filePath}:${hub.line} | ${hub.dependentCount} | ${hub.role} |")
             }
             appendLine()
-            for (hub in hubs.filter { it.dependentCount >= DETAIL_THRESHOLD }) {
+            for (hub in orderedHubs.filter { it.dependentCount >= DETAIL_THRESHOLD }) {
                 appendLine("## ${hub.name}")
                 appendLine()
                 for (dep in hub.dependents) {
🤖 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/report/HotClassesRenderer.kt` around
lines 27 - 32, The renderer iterates over the input list "hubs" in
HotClassesRenderer which makes output order non-deterministic; sort the hubs
locally before rendering (e.g., create a sorted list =
hubs.sortedWith(compareByDescending<Hub> { it.dependentCount }.thenBy { it.name
})) and use that sorted list in both table generation and the detailed section
(including the filter for DETAIL_THRESHOLD) so output is stable and
deterministic.
🤖 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/report/CrossBuildRenderer.kt`:
- Around line 19-23: The renderer currently only shows the "No cross-build
references detected." message when buildEdges.isEmpty() and crossBuildAnalysis
== null, which skips the fallback when crossBuildAnalysis exists but has no hubs
or cycles; update the conditional in CrossBuildRenderer (the block referencing
buildEdges and crossBuildAnalysis) to treat an analysis with empty hubs and
cycles as equivalent to null — e.g., change the check to if
(buildEdges.isEmpty() && (crossBuildAnalysis == null ||
(crossBuildAnalysis.hubs.isEmpty() && crossBuildAnalysis.cycles.isEmpty()))) and
keep the existing appendLine/return behavior so the no-data message is emitted
for empty-but-non-null analyses.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt`:
- Around line 50-55: totalWarnings currently sums only top-level summaries,
missing findings from included builds; update the calculation (the totalWarnings
variable) to include findings from includedBuildSummaries as well by aggregating
root summaries plus each included build's summaries' analysis.findings and
counting those with severity == FindingSeverity.WARNING or
FindingSeverity.FORBIDDEN (mirroring how totalSymbols incorporates
includedBuildSummaries). Locate totalWarnings and modify its summation to
iterate both summaries and includedBuildSummaries (or concatenate the lists) so
included-build findings are counted.
- Around line 139-160: The early return in appendSplitFileLinks() hides links to
split files that ContextTask.writeSplitFiles() always emits (entry-points.md and
interfaces.md) and may emit cross-build.md; remove the early return and always
render the "Details" section and the mandatory links for entry-points.md and
interfaces.md, while keeping the conditional additions for hot-classes (when
crossBuildAnalysis?.hubs?.isNotEmpty()), anti-patterns (when
summaries/includedBuildSummaries contain findings), and cross-build (when
buildEdges.isNotEmpty() || crossBuildAnalysis != null); update the function
appendSplitFileLinks() to rely on the existing booleans (hasHubs, hasFindings)
and variables (crossBuildAnalysis, summaries, includedBuildSummaries,
buildEdges) and reference ContextTask.writeSplitFiles behavior in a comment if
helpful.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/EntryPointsRenderer.kt`:
- Around line 91-99: findTestClasses() only detects classes ending with "Test"
but should also include "Spec" like
ComponentClassifier.classifySingleEntryPoint; update the filter in
findTestClasses() (the lambda that selects symbols where it.kind ==
SymbolKind.CLASS && it.name.value.endsWith("Test")) to also accept names ending
with "Spec" (e.g., check endsWith("Test") || endsWith("Spec")) so both
Kotest/Spek-style test classes are included; keep the rest of the pipeline
(sourceSets filtering and distinctBy) unchanged.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 191-194: The anti-patterns file is generated from per-build
summaryList/includedBuildSummaries and so misses forbidden-package/class inputs
and cross-build DI findings produced by analyzeCrossBuild(); change the
anti-patterns generation to use the repo-wide analysis result returned by
analyzeCrossBuild() (or the combined crossBuildSummary variable it produces)
instead of summaryList/includedBuildSummaries when constructing
AntiPatternsRenderer so anti-patterns.md reflects cross-build findings and
forbidden inputs.

In `@src/test/kotlin/zone/clanker/gradle/srcx/report/InterfacesRendererTest.kt`:
- Around line 102-151: Test shows mocks (e.g., MockUserService) are being
counted both as implementations and as mocks; update
InterfacesRenderer.fromSummaries to exclude mock/fake naming patterns (e.g.,
names starting with "Mock", "Fake", or matching configured mock prefixes) from
the implementation detection logic so they only set hasMock and are not
incremented in implementationCount, keep detection of real implementations like
UserServiceImpl unchanged, and then update the test in InterfacesRendererTest
(the "summaries have interface-like naming (Service suffix)" case) to expect
implementationCount = 1 while hasMock remains true.

---

Outside diff comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/report/DashboardRendererTest.kt`:
- Around line 147-200: The test description says it verifies "hub classes and
dependent names" but the assertion only checks for a generic project row; update
the test to assert the hub-related output produced by DashboardRenderer("test",
summaries, emptyList()).render() — specifically verify the rendered output
contains the hub class "Core" and the dependent names "A", "B", and "C" (and
optionally their paths or counts from HubClass/HubDependentRef) so the fixtures
(ProjectSummary, HubClass, HubDependentRef) are actually validated;
alternatively, if you intend a simpler test, rename the scenario to not mention
hubs and simplify the fixture to match the assertion.

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRenderer.kt`:
- Around line 63-76: collectAllFindings currently deduplicates by
finding.message using distinctBy which drops additional source context; change
it to group findings by it.second.message (use groupBy on message) and produce a
structure that preserves all source paths per message (e.g., map to
Pair<List<String>, Finding> or a small data class with sources and finding),
then update the renderer code that consumes collectAllFindings (in
AntiPatternsRenderer rendering block that builds lines) to iterate each grouped
entry and print all sources (joinToString) next to the finding message/severity;
alternatively, if you prefer minimal change, add a comment/docs near
collectAllFindings explaining that only the first source is shown.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/CrossBuildRenderer.kt`:
- Around line 33-39: The grouping iteration over buildEdges (val byFrom =
buildEdges.groupBy { it.from }) is non-deterministic; change the loop to iterate
over sorted group keys and sorted edge targets so output is stable: enumerate
byFrom.keys.sorted() (or byFrom.entries.sortedBy { it.key }) and for each
group's edges use edges.sortedBy { it.to } before appending lines for "from" and
each edge.to to produce deterministic markdown in CrossBuildRenderer (variables:
buildEdges, byFrom, from, edges, edge.to).

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/HotClassesRenderer.kt`:
- Around line 27-32: The renderer iterates over the input list "hubs" in
HotClassesRenderer which makes output order non-deterministic; sort the hubs
locally before rendering (e.g., create a sorted list =
hubs.sortedWith(compareByDescending<Hub> { it.dependentCount }.thenBy { it.name
})) and use that sorted list in both table generation and the detailed section
(including the filter for DETAIL_THRESHOLD) so output is stable and
deterministic.

In `@src/main/kotlin/zone/clanker/gradle/srcx/report/InterfacesRenderer.kt`:
- Around line 124-135: The heuristic in countImplementations can over-count
because cn.endsWith(baseName) will match things like UserService for IService;
update the logic in countImplementations to tighten suffix matches: keep the
existing exact-name and Default/Impl checks, but replace the broad
cn.endsWith(baseName) branch with a guard that only counts when the class
actually has a CamelCase boundary before the baseName (e.g., the character
immediately before the matched baseName is an uppercase letter) and optionally
skip this suffix rule for very short baseName values; modify the check around
cn.endsWith(baseName) to verify cn.length > baseName.length and that the char at
cn.length - baseName.length - 1 isUpperCase() (and/or check baseName.length >=
3) so only true implementation-style class names are counted.

In `@src/test/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRendererTest.kt`:
- Around line 113-154: The test currently only asserts the duplicate message
appears once; update the test for AntiPatternsRenderer.render to also assert
which project source is preserved after deduplication — e.g., verify the output
contains the project path for the first summary (ProjectPath(":a")) and does not
contain the second (ProjectPath(":b")) to lock in the expected preservation
order; locate this behavior in the test block around
AntiPatternsRenderer(summaries).render() and add the corresponding positive and
negative assertions.

In `@src/test/kotlin/zone/clanker/gradle/srcx/report/EntryPointsRendererTest.kt`:
- Around line 71-114: Add a test case entry to the existing "rendering with test
doubles" scenario to exercise the Stub pattern: in the summaries passed to
EntryPointsRenderer add a SymbolEntry with SymbolName("ExampleStub") (or
similar), SymbolKind.CLASS, PackageName("com.example"),
FilePath("ExampleStub.kt"), and ensure it's included in the same
SourceSetSummary; then extend the assertions after renderer.render() to expect
the Stub section/row—e.g., output shouldContain a table row containing
"`ExampleStub`" and "Stub" (alongside the existing Mock and Fake checks) so the
renderer's Stub handling is verified.
🪄 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: b613fc2f-2325-40fe-b33a-0b7e30e90039

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc9be4 and 51eaf5b.

📒 Files selected for processing (18)
  • src/main/kotlin/zone/clanker/gradle/srcx/analysis/ComponentClassifier.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/CrossBuildRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/EntryPointsRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/FlowRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/HotClassesRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/InterfacesRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/analysis/ComponentClassifierTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/AntiPatternsRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/CrossBuildRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/DashboardRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/EntryPointsRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/FlowRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/HotClassesRendererTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/InterfacesRendererTest.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/report/EntryPointsRenderer.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
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

♻️ Duplicate comments (2)
src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt (2)

50-55: ⚠️ Potential issue | 🟡 Minor

Include included-build findings in the overview warning total.

totalSymbols already aggregates includedBuildSummaries, but totalWarnings only counts root-project findings. If warnings exist only in an included build, the overview will still report 0 warnings.

Proposed fix
         val totalWarnings =
-            summaries.sumOf { s ->
-                s.analysis?.findings?.count {
-                    it.severity == FindingSeverity.WARNING || it.severity == FindingSeverity.FORBIDDEN
-                } ?: 0
-            }
+            summaries.sumOf { s ->
+                s.analysis?.findings?.count {
+                    it.severity == FindingSeverity.WARNING || it.severity == FindingSeverity.FORBIDDEN
+                } ?: 0
+            } +
+                includedBuildSummaries.values.sumOf { projects ->
+                    projects.sumOf { s ->
+                        s.analysis?.findings?.count {
+                            it.severity == FindingSeverity.WARNING || it.severity == FindingSeverity.FORBIDDEN
+                        } ?: 0
+                    }
+                }
🤖 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/report/DashboardRenderer.kt` around
lines 50 - 55, totalWarnings currently sums only the root `summaries` and
ignores `includedBuildSummaries`, causing included-build findings to be omitted;
update the calculation to aggregate over both collections (for example by
summing `(summaries + includedBuildSummaries)` or otherwise merging them) and
then count findings with `it.severity == FindingSeverity.WARNING || it.severity
== FindingSeverity.FORBIDDEN`, preserving the existing null-safe handling of
`s.analysis?.findings`.

147-160: ⚠️ Potential issue | 🟠 Major

Don’t return before rendering the split-report links.

Line 147 makes the crossBuildAnalysis != null check on Line 159 ineffective when there are no hubs/findings/build edges, so Cross-Build References can disappear even though cross-build analysis exists. It also hides the unconditional Entry Points and Interfaces links in the same case.

Proposed fix
-        if (!hasHubs && !hasFindings && buildEdges.isEmpty()) return
-
         appendLine("## Details")
         appendLine()
         if (hasHubs) {
             appendLine("- [Hub Classes](hub-classes.md)")
         }
         appendLine("- [Entry Points](entry-points.md)")
         if (hasFindings) {
             appendLine("- [Anti-Patterns](anti-patterns.md)")
         }
         appendLine("- [Interfaces](interfaces.md)")
         if (buildEdges.isNotEmpty() || crossBuildAnalysis != null) {
             appendLine("- [Cross-Build References](cross-build.md)")
         }
         appendLine()
🤖 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/report/DashboardRenderer.kt` around
lines 147 - 160, The early return "if (!hasHubs && !hasFindings &&
buildEdges.isEmpty()) return" prevents rendering split-report links (Entry
Points, Interfaces, Cross-Build References) when crossBuildAnalysis exists or
when you still want unconditional links; update the condition or move the return
so that crossBuildAnalysis is considered — either include "crossBuildAnalysis ==
null" in the compound check or move the return to after the block that appends
the split-report links; ensure checks reference hasHubs, hasFindings,
buildEdges, and crossBuildAnalysis in DashboardRenderer.kt so Cross-Build
References and the unconditional Entry Points and Interfaces links are emitted
when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@settings.gradle.kts`:
- Line 12: The plugin declaration zone.clanker.gradle.srcx is being applied in
settings.gradle.kts but its versioned registration lives in the root
build.gradle.kts (evaluated later), causing resolution failure; move the plugin
registration into the included build-logic's gradlePlugin block so it is
available during settings evaluation: add a gradlePlugin.register("srcx") entry
in build-logic/build.gradle.kts matching id = "zone.clanker.gradle.srcx" and
implementationClass = "zone.clanker.gradle.srcx.Srcx$SettingsPlugin" (and
displayName/description similar to clkx-settings), then in settings.gradle.kts
remove the explicit version and apply id("zone.clanker.gradle.srcx") so it
resolves from build-logic.

---

Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt`:
- Around line 50-55: totalWarnings currently sums only the root `summaries` and
ignores `includedBuildSummaries`, causing included-build findings to be omitted;
update the calculation to aggregate over both collections (for example by
summing `(summaries + includedBuildSummaries)` or otherwise merging them) and
then count findings with `it.severity == FindingSeverity.WARNING || it.severity
== FindingSeverity.FORBIDDEN`, preserving the existing null-safe handling of
`s.analysis?.findings`.
- Around line 147-160: The early return "if (!hasHubs && !hasFindings &&
buildEdges.isEmpty()) return" prevents rendering split-report links (Entry
Points, Interfaces, Cross-Build References) when crossBuildAnalysis exists or
when you still want unconditional links; update the condition or move the return
so that crossBuildAnalysis is considered — either include "crossBuildAnalysis ==
null" in the compound check or move the return to after the block that appends
the split-report links; ensure checks reference hasHubs, hasFindings,
buildEdges, and crossBuildAnalysis in DashboardRenderer.kt so Cross-Build
References and the unconditional Entry Points and Interfaces links are emitted
when appropriate.
🪄 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: fa50c1bc-25de-4a0d-9216-d00616ce451f

📥 Commits

Reviewing files that changed from the base of the PR and between 51eaf5b and d98550f.

📒 Files selected for processing (5)
  • settings.gradle.kts
  • src/main/kotlin/zone/clanker/gradle/srcx/report/DashboardRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/HotClassesRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/report/HotClassesRendererTest.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/kotlin/zone/clanker/gradle/srcx/report/HotClassesRendererTest.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/HotClassesRenderer.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt

Comment thread settings.gradle.kts Outdated
- Strip KDoc and comments before same-package reference matching
  to eliminate false circular deps from @see cross-references
- Skip self-edges in cycle detection (Case -> Case)
- Add isTest field to HubClass model
- Hub classes renderer separates production and test classes
  with 🧪 icon prefix for test classes
- Hub classes: tree format with ├── └── connectors, test paths marked 🧪
- Entry points: removed useless "First Call" column, added description
- Cross-build: tree showing dependents per hub with file paths
- Interfaces: fixed false detection of enum values, grouped by source set
- Flows: removed entirely (import-chain tracing produced identical diagrams)
- Dashboard: slimmed to overview + tables + links only, removed class diagram
- Fixed false positive cycles from KDoc @see references (strip comments)
- Fixed self-cycles in cycle detection (skip self-edges)
Remove pinned 2.1.20 version — use Gradle's embedded Kotlin version
instead. The pinned version conflicts with Gradle 9.4.1's embedded
Kotlin 2.3.0, causing ClasspathEntrySnapshotTransform failures in
composite builds.

Also remove self-applied srcx plugin from settings (causes bootstrap
conflict) and rename build-logic for composite build compatibility.
…ST/APP/MOCK split

Test classes (ending in Test/Spec, in /test/ paths) were incorrectly
listed as App Entry Points because buildEntryPoints() used
findEntryPoints() which returns graph roots without filtering tests.

Replaced with classifyEntryPoints() which already handles TEST/MOCK/APP
classification. Simplified EntryPointsRenderer to accept pre-classified
entries instead of doing its own broken source-set-based detection.
context.md lives inside .srcx/, so relative links to included build
reports need an extra ../ to escape the .srcx directory first.
- Fix DI violation message: "Dependency on concrete" instead of
  misleading "Constructor takes concrete" (detection is import-based)
- Add Windows path separator check for test file exclusion consistency
- Include included-build warnings in dashboard totalWarnings count
- Add empty-state message in CrossBuildRenderer for present but empty analysis
- Exclude mock/fake/stub classes from interface implementation counts
@ClankerGuru
Copy link
Copy Markdown
Owner Author

Missing forbidden package names per PR objectives.
The PR objectives list forbidden package names as: utils, helpers, managers, common, misc, base, shared. However, common and shared are missing from the implementation.

common and shared are intentionally excluded — they are valid, widely-used package names in our projects. The PR description was inaccurate; the implementation is correct.

   (\(\
   ( -.-)    "Not every common thing is forbidden."
   o_(")(")

@ClankerGuru
Copy link
Copy Markdown
Owner Author

@coderabbitai The missing common and shared packages from the forbidden list is intentional — not a bug. The PR description was inaccurate.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

Tip

For best results, initiate chat on the files or code changes.

@ClankerGuru Understood! The omission of common and shared from the forbidden packages list is a deliberate design choice — those are recognized as valid, widely-used package names in your projects. I've noted this for future reviews so I won't flag it again.


✏️ Learnings added
Learnt from: ClankerGuru
URL: https://github.com/ClankerGuru/srcx/pull/7

Timestamp: 2026-04-13T00:30:35.747Z
Learning: In the srcx project (ClankerGuru/srcx), the forbidden package names intentionally exclude `common` and `shared` because they are valid, widely-used package names in the codebase. Only `utils`, `helpers`, `managers`, `misc`, and `base` are treated as forbidden package names. The PR description listing `common` and `shared` as forbidden was inaccurate; the implementation is correct.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@ClankerGuru ClankerGuru merged commit 809db88 into main Apr 13, 2026
2 checks passed
@ClankerGuru ClankerGuru deleted the enhance-anti-pattern-detection branch April 13, 2026 00:41
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