Skip to content

Use proper Gradle Property injection + extract tasks into classes - #3

Merged
ClankerGuru merged 6 commits into
mainfrom
fix-extension-injection
Apr 9, 2026
Merged

Use proper Gradle Property injection + extract tasks into classes#3
ClankerGuru merged 6 commits into
mainfrom
fix-extension-injection

Conversation

@ClankerGuru

@ClankerGuru ClankerGuru commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Refactor SettingsExtension from plain var to abstract val Property<T> with @Inject constructor
  • Extract inline doLast task logic into task/ContextTask.kt and task/CleanTask.kt
  • SettingsPlugin is now thin — just registers tasks
  • Matches wrkx architecture pattern

Changes

  • Srcx.SettingsExtension: var outputDir: Stringabstract val outputDir: Property<String>
  • Srcx.SettingsExtension: var autoGenerate: Booleanabstract val autoGenerate: Property<Boolean>
  • New task/ContextTask.kt — extracted from inline doLast
  • New task/CleanTask.kt — extracted from inline doLast
  • Updated all callers to use .get() and .set()
  • Updated DSL accessor example in SrcxDsl.kt
  • Updated all tests

Test plan

  • All existing tests pass
  • Konsist architecture tests pass
  • Coverage above threshold
  • detekt + ktlint clean

Summary by CodeRabbit

  • New Features

    • Adds a configurable "context" report generator and a dedicated clean task to manage generated output; reports now include per-project summaries, included-build reports, and a generated class diagram.
  • Bug Fixes / Safety

    • Clean task validates targets to prevent deleting outside the project and prints deletion summaries.
  • Refactor

    • Configuration now uses Gradle Property conventions and moves task behavior into dedicated task types with consolidated reporting utilities.
  • Documentation

    • DSL examples updated to use setter/provider-style configuration.
  • Tests

    • Tests updated to use Property-based configuration and new helper APIs.

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown

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

Replaces mutable DSL fields with Gradle Property-backed extension, registers ContextTask and CleanTask wired from that extension, moves scanning/reporting/parallel logic into new helpers (ReportWriter, ProjectScanner, SymbolExtractor), adds cleanOutputDir, and updates tests to use Gradle ObjectFactory and Property APIs.

Changes

Cohort / File(s) Summary
Core plugin
src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
Swapped mutable extension for an abstract Property-backed SettingsExtension, added cleanOutputDir, and moved task registration to use ContextTask/CleanTask with convention wiring.
Tasks
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt, src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt
Added ContextTask (srcx-context) to generate per-project and included-build reports and CleanTask to safely delete configured output dirs; both expose @Input val outputDir: Property<String> and disable caching by default.
Reporting utilities
src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt
New ReportWriter object encapsulating report generation: write project reports, write .gitignore, handle included-build reports, compute build edges, generate class diagrams, and run per-project work in parallel.
Scanning & extraction
src/main/kotlin/zone/clanker/gradle/srcx/scan/ProjectScanner.kt, .../SymbolExtractor.kt
New ProjectScanner for project/source discovery and build-file detection; new SymbolExtractor for extracting symbols and dependencies from source dirs and build files, returning ProjectSummary objects.
DSL docs
src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
KDoc examples updated to use Property setter/getter style (.set() / .get()).
Tests
src/test/kotlin/zone/clanker/gradle/srcx/...
Updated tests to instantiate SettingsExtension via Gradle objects.newInstance(...), apply convention(...), use outputDir.get()/set() and autoGenerate.set(...), and replace plugin-instance method calls with new utility/object entry points.
Architecture tests / boundaries
src/slopTest/.../PackageBoundaryTest.kt, TaskAnnotationTest.kt
Updated package/import boundary assertions and comments to reflect new scan, report, and task package responsibilities; clarified that tasks live under task/ and reusable logic lives on Srcx/new helpers.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Gradle
    participant Plugin as SrcxPlugin
    participant ContextTask as srcx-context
    participant ReportWriter
    participant SymbolExtractor
    participant RootProject
    participant IncludedBuilds

    Note over Plugin,Gradle: Plugin applied — registers ContextTask & CleanTask, wires outputDir via Property conventions
    User->>Gradle: run "srcx-context"
    Gradle->>ContextTask: schedule & inject outputDir
    ContextTask->>RootProject: collect projects & source sets (ProjectScanner)
    ContextTask->>SymbolExtractor: extract per-project symbols (parallel)
    ContextTask->>ReportWriter: write per-project reports and .gitignore
    ContextTask->>IncludedBuilds: iterate & Request included-build summaries (ReportWriter + SymbolExtractor)
    ContextTask->>ReportWriter: compute build edges & generate class diagram
    ContextTask->>Gradle: emit completion/log
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through code and nudged each var,

Swapped them to Properties — tidy and spar,
Tasks now march: one to gather, one to sweep,
Reports stitched neat while the build forests sleep,
A carrot of output glowing in the tar.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% 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 describes the main changes: converting SettingsExtension to use Gradle Property injection and extracting inline task logic into dedicated task classes.

✏️ 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-extension-injection

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

58-68: Avoid duplicate summary extraction

Line 59 and Line 67 call extractProjectSummary for the same projects. Reuse summaries from the parallel pass instead of recomputing, to cut runtime and keep per-project reports and dashboard data consistent within one execution.

🤖 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 58
- 68, The code recomputes project summaries: extractProjectSummary is called
inside plugin.runParallel and again when building summaries; change the parallel
pass to store each project's summary in a temporary concurrent map (e.g.,
MutableMap<Project, Summary> or ConcurrentHashMap) inside the lambda passed to
plugin.runParallel and use plugin.writeProjectReport with that stored summary,
then replace the later summaries = projects.map { ... } with a reuse of the map
values (projects.map { summariesMap[it]!! }) so the per-project report and the
dashboard use the same precomputed summaries; update references to
plugin.runParallel, extractProjectSummary, writeProjectReport and the summaries
variable accordingly.
🤖 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/Srcx.kt`:
- Around line 82-83: The KDoc for the abstract property autoGenerate in Srcx.kt
is stale: update its comment to reflect that when true,
compileKotlin/compileJava tasks will finalize with srcx-context (not
srcx-symbols). Locate the KDoc above the autoGenerate Property<Boolean>
declaration in the Srcx class/object and change the reference from
"srcx-symbols" to "srcx-context", keeping the rest of the description intact.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 24-31: CleanTask (and similarly ContextTask) currently stores live
Gradle model objects rootProject and extension on the task; change the task API
to model only explicit inputs (e.g., add properties like val rootDir:
DirectoryProperty, val outputDir: DirectoryProperty, and Property<T> or `@Nested`
data classes for settings) annotated with `@Input/`@Nested, remove the Project and
extension constructor params, and update any `@TaskAction` to read only those
properties (not Project/extension). During task registration, wire extension
values and project paths into the task properties using set(...) or
convention(...) with Providers (e.g.,
project.layout.projectDirectory.asFileTree/Providers.of(...)) so the task no
longer captures Project or the extension at configuration time. Ensure
ContextTask is updated the same way and that no code inside `@TaskAction`
references rootProject or extension directly.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 69-71: The current code computes relPath using
build.projectDir.relativeTo(rootProject.projectDir) which throws for included
builds outside the root; change this to use relativeToOrNull and fall back to
the absolute path so the task won't crash: compute relPath with
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path and pass that into
DashboardRenderer.IncludedBuildRef(build.name, relPath) to preserve behavior for
both inside- and outside-root included builds.

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 58-68: The code recomputes project summaries:
extractProjectSummary is called inside plugin.runParallel and again when
building summaries; change the parallel pass to store each project's summary in
a temporary concurrent map (e.g., MutableMap<Project, Summary> or
ConcurrentHashMap) inside the lambda passed to plugin.runParallel and use
plugin.writeProjectReport with that stored summary, then replace the later
summaries = projects.map { ... } with a reuse of the map values (projects.map {
summariesMap[it]!! }) so the per-project report and the dashboard use the same
precomputed summaries; update references to plugin.runParallel,
extractProjectSummary, writeProjectReport and the summaries variable
accordingly.
🪄 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: e7938b4b-02a4-4d9d-be27-7eaeb3ef7e72

📥 Commits

Reviewing files that changed from the base of the PR and between 023627f and 12e602e.

📒 Files selected for processing (9)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.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/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt Outdated
@ClankerGuru
ClankerGuru force-pushed the fix-extension-injection branch from 12e602e to 6853433 Compare April 9, 2026 02:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/task/ContextTask.kt (1)

65-66: ⚠️ Potential issue | 🟠 Major

Handle non-relative included build paths safely.

Line 65 uses relativeTo(...), which can throw for included builds outside the root directory. Use relativeToOrNull() and fallback to absolute path.

Proposed fix
-                val relPath = build.projectDir.relativeTo(rootProject.projectDir).path
+                val relPath =
+                    build.projectDir.relativeToOrNull(rootProject.projectDir)?.path
+                        ?: build.projectDir.absolutePath
                 DashboardRenderer.IncludedBuildRef(build.name, relPath)
#!/bin/bash
set -euo pipefail

# Verify relative path conversion uses throwing API in ContextTask.
rg -n -C2 --type kotlin 'relativeTo\(|relativeToOrNull\(' \
  src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
🤖 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 65
- 66, The code in ContextTask (around DashboardRenderer.IncludedBuildRef) uses
build.projectDir.relativeTo(rootProject.projectDir) which throws if the included
build is not inside the root; update the computation of relPath to use
relativeToOrNull and fall back to the absolute path when null so
DashboardRenderer.IncludedBuildRef(build.name, relPath) always receives a safe
path (e.g. compute relPath with
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path).
src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt (1)

39-43: ⚠️ Potential issue | 🟠 Major

Avoid Gradle model access in @TaskAction (configuration-cache risk).

Line 39 and Line 42 read project.rootProject / rootProject.gradle.includedBuilds during execution. Move these to declared task inputs and wire them during registration.

Proposed refactor
 import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.ListProperty
 import org.gradle.api.provider.Property
 import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
 import org.gradle.api.tasks.TaskAction
 import zone.clanker.gradle.srcx.Srcx
 import java.io.File
@@
 abstract class CleanTask : DefaultTask() {
+    `@get`:InputDirectory
+    abstract val rootDir: DirectoryProperty
+
+    `@get`:Input
+    abstract val includedBuildDirs: ListProperty<String>
+
     /** Output directory relative to the root project (e.g. `.srcx`). */
     `@get`:Input
     abstract val outputDir: Property<String>
@@
     `@TaskAction`
     fun clean() {
-        val rootProject = project.rootProject
-        val dir = File(rootProject.projectDir, outputDir.get())
+        val dir = File(rootDir.asFile.get(), outputDir.get())
         plugin.cleanOutputDir(dir)
-        for (build in rootProject.gradle.includedBuilds) {
-            val buildOutputDir = File(build.projectDir, outputDir.get())
+        for (buildDir in includedBuildDirs.get()) {
+            val buildOutputDir = File(buildDir, outputDir.get())
             plugin.cleanOutputDir(buildOutputDir)
         }
     }
 }
// In Srcx.SettingsPlugin.registerTasks(...)
rootProject.tasks.register(TASK_CLEAN, CleanTask::class.java).configure {
    it.outputDir.convention(extension.outputDir)
    it.rootDir.set(rootProject.layout.projectDirectory)
    it.includedBuildDirs.set(
        rootProject.provider { rootProject.gradle.includedBuilds.map { b -> b.projectDir.absolutePath } },
    )
}
#!/bin/bash
set -euo pipefail

# Verify `@TaskAction` methods that access Gradle model objects at execution time.
rg -n -C2 --type kotlin '@TaskAction|project\.rootProject|rootProject\.gradle\.includedBuilds' \
  src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt \
  src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
🤖 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/CleanTask.kt` around lines 39 -
43, The CleanTask currently accesses Gradle model objects at execution time
(project.rootProject and rootProject.gradle.includedBuilds) which breaks
configuration-cache safety; refactor by adding task input properties (e.g.,
outputDir already exists, add rootDir: RegularFileProperty and
includedBuildDirs: ListProperty<String> or SetProperty<String>) on CleanTask and
stop calling project/rootProject inside the `@TaskAction`; during plugin/task
registration (where TASK_CLEAN is registered) wire those properties from the
project model (set rootDir from rootProject.layout.projectDirectory and populate
includedBuildDirs via a provider mapping rootProject.gradle.includedBuilds to
their projectDir paths) so the `@TaskAction` only reads declared properties and
operates on the files passed in.
🤖 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/CleanTask.kt`:
- Around line 40-44: Validate and sanitize outputDir before calling
plugin.cleanOutputDir: in CleanTask where you build File(rootProject.projectDir,
outputDir.get()) and File(build.projectDir, outputDir.get()), ensure
outputDir.get() is not an absolute path and does not contain path traversal
(e.g. ".."); resolve and normalize the resulting File.toPath().toRealPath() (or
equivalent) and verify the resolved path startsWith the corresponding
projectDir.toPath().toRealPath(); if validation fails, skip deletion and surface
an error/throw so plugin.cleanOutputDir is never called on paths outside the
project workspace.

---

Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 39-43: The CleanTask currently accesses Gradle model objects at
execution time (project.rootProject and rootProject.gradle.includedBuilds) which
breaks configuration-cache safety; refactor by adding task input properties
(e.g., outputDir already exists, add rootDir: RegularFileProperty and
includedBuildDirs: ListProperty<String> or SetProperty<String>) on CleanTask and
stop calling project/rootProject inside the `@TaskAction`; during plugin/task
registration (where TASK_CLEAN is registered) wire those properties from the
project model (set rootDir from rootProject.layout.projectDirectory and populate
includedBuildDirs via a provider mapping rootProject.gradle.includedBuilds to
their projectDir paths) so the `@TaskAction` only reads declared properties and
operates on the files passed in.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 65-66: The code in ContextTask (around
DashboardRenderer.IncludedBuildRef) uses
build.projectDir.relativeTo(rootProject.projectDir) which throws if the included
build is not inside the root; update the computation of relPath to use
relativeToOrNull and fall back to the absolute path when null so
DashboardRenderer.IncludedBuildRef(build.name, relPath) always receives a safe
path (e.g. compute relPath with
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path).
🪄 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: d7b6d0e8-dd21-4f3e-8eaf-1ddebb6ea7cb

📥 Commits

Reviewing files that changed from the base of the PR and between 12e602e and 6853433.

📒 Files selected for processing (9)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.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/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (3)
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt Outdated
@ClankerGuru
ClankerGuru force-pushed the fix-extension-injection branch from 6853433 to eee2b59 Compare April 9, 2026 03:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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/SrcxSettingsPluginTest.kt (1)

404-406: ⚠️ Potential issue | 🟡 Minor

Minor: Duplicate test case for TASK_CONTEXT.

Lines 400-402 and 404-406 both test that TASK_CONTEXT equals "srcx-context". The second one appears to be a copy-paste oversight.

🧹 Proposed fix to remove duplicate
                 then("TASK_CONTEXT is srcx-context") {
                     Srcx.TASK_CONTEXT shouldBe "srcx-context"
                 }
-
-                then("TASK_CONTEXT is srcx-context") {
-                    Srcx.TASK_CONTEXT shouldBe "srcx-context"
-                }
             }
🤖 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/SrcxSettingsPluginTest.kt` around
lines 404 - 406, Remove the duplicate test assertion that checks
Srcx.TASK_CONTEXT equals "srcx-context" in SrcxSettingsPluginTest (the repeated
then block around lines 404-406); keep a single assertion for Srcx.TASK_CONTEXT
and delete the redundant then("TASK_CONTEXT is srcx-context") {
Srcx.TASK_CONTEXT shouldBe "srcx-context" } block to avoid the copy-paste
duplicate.
♻️ Duplicate comments (1)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (1)

63-67: ⚠️ Potential issue | 🟠 Major

Handle included builds outside the root directory safely.

relativeTo() throws IllegalArgumentException for included builds located outside the root directory, which is a valid Gradle composite build configuration. This will crash the task for such setups.

Use relativeToOrNull() with a fallback to the absolute path:

🛠️ Proposed fix
         val includedBuildRefs =
             includedBuilds.map { build ->
-                val relPath = build.projectDir.relativeTo(rootProject.projectDir).path
+                val relPath =
+                    build.projectDir.relativeToOrNull(rootProject.projectDir)?.path
+                        ?: build.projectDir.absolutePath
                 DashboardRenderer.IncludedBuildRef(build.name, relPath)
             }
🤖 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 63
- 67, The includedBuilds mapping uses projectDir.relativeTo(...) which throws
for builds outside the root; update the includedBuildRefs construction in
ContextTask to use
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path (or absolutePath) so it falls back to the absolute path
when relativeToOrNull returns null, and continue creating
DashboardRenderer.IncludedBuildRef(build.name, relPath) with that safe relPath.
🧹 Nitpick comments (1)
src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt (1)

336-348: Consider extracting shared newExtension() helper.

This helper is duplicated in SrcxAutoGenerateTest.kt (lines 10-22). Consider extracting to a shared test utility to avoid drift.

🤖 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/SrcxSettingsPluginTest.kt` around
lines 336 - 348, The test duplicates the helper that constructs a
Srcx.SettingsExtension (the newExtension() function that uses
ProjectBuilder.objects to newInstance Srcx.SettingsExtension and sets
outputDir.convention(Srcx.OUTPUT_DIR) and autoGenerate.convention(false));
extract that logic into a shared test utility function/class (e.g.,
TestHelpers.createSrcxSettingsExtension or a common TestExtensions object) and
replace the local newExtension() in both SrcxSettingsPluginTest and
SrcxAutoGenerateTest with calls to this shared helper to avoid duplication and
drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt`:
- Around line 404-406: Remove the duplicate test assertion that checks
Srcx.TASK_CONTEXT equals "srcx-context" in SrcxSettingsPluginTest (the repeated
then block around lines 404-406); keep a single assertion for Srcx.TASK_CONTEXT
and delete the redundant then("TASK_CONTEXT is srcx-context") {
Srcx.TASK_CONTEXT shouldBe "srcx-context" } block to avoid the copy-paste
duplicate.

---

Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 63-67: The includedBuilds mapping uses projectDir.relativeTo(...)
which throws for builds outside the root; update the includedBuildRefs
construction in ContextTask to use
build.projectDir.relativeToOrNull(rootProject.projectDir)?.path ?:
build.projectDir.path (or absolutePath) so it falls back to the absolute path
when relativeToOrNull returns null, and continue creating
DashboardRenderer.IncludedBuildRef(build.name, relPath) with that safe relPath.

---

Nitpick comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt`:
- Around line 336-348: The test duplicates the helper that constructs a
Srcx.SettingsExtension (the newExtension() function that uses
ProjectBuilder.objects to newInstance Srcx.SettingsExtension and sets
outputDir.convention(Srcx.OUTPUT_DIR) and autoGenerate.convention(false));
extract that logic into a shared test utility function/class (e.g.,
TestHelpers.createSrcxSettingsExtension or a common TestExtensions object) and
replace the local newExtension() in both SrcxSettingsPluginTest and
SrcxAutoGenerateTest with calls to this shared helper to avoid duplication and
drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65de2f63-66ce-455d-ba08-ad8c52bb5513

📥 Commits

Reviewing files that changed from the base of the PR and between 6853433 and eee2b59.

📒 Files selected for processing (9)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.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/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (3)
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt

@ClankerGuru
ClankerGuru force-pushed the fix-extension-injection branch from eee2b59 to 3d9ecaf Compare April 9, 2026 03:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt (2)

61-64: ⚠️ Potential issue | 🟠 Major

Don't let relativeTo(...) abort composite-build reporting.

Line 63 assumes every included build can be relativized against the root project dir. For valid composite builds on a different filesystem root/drive, relativeTo(...) throws and the whole task fails. Use relativeToOrNull() with a fallback.

Suggested fix
         val includedBuildRefs =
             includedBuilds.map { build ->
-                val relPath = build.projectDir.relativeTo(rootProject.projectDir).path
+                val relPath =
+                    build.projectDir.relativeToOrNull(rootProject.projectDir)?.path
+                        ?: build.projectDir.path
                 DashboardRenderer.IncludedBuildRef(build.name, relPath)
             }
🤖 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 61
- 64, The code in ContextTask.kt building includedBuildRefs uses
build.projectDir.relativeTo(rootProject.projectDir) which can throw for builds
on different filesystem roots; change the mapping in the includedBuilds ->
includedBuildRefs logic to use relativeToOrNull(rootProject.projectDir) and, if
it returns null, fall back to build.projectDir.path (or
build.projectDir.absolutePath) so the task won't abort on non-relativizable
included builds.

45-84: ⚠️ Potential issue | 🟠 Major

This task still executes against live Gradle model state.

Lines 47-69 call project.rootProject, iterate includedBuilds, and pass Project instances into helpers from inside @TaskAction. That keeps srcx-context outside configuration-cache requirements even though the settings values are now Property-backed. Move the Gradle-model reads into task registration and keep the action file/provider-based.

🤖 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 45
- 84, The generate() `@TaskAction` in ContextTask reads the live Gradle model
(project.rootProject, includedBuilds, project instances) which breaks
configuration-cache compatibility; move all Gradle API reads into task
registration and expose them as Providers/Files for the action. Specifically,
during task registration capture values via providers (e.g., rootProject
reference, includedBuilds list, buildPairs, relPath calculations, and summaries)
and wire them into task inputs like outputDir and a Provider<List<...>> or
RegularFile/DirectoryProperty; then change generate() to only consume those
Providers (call get() on them) and call Srcx.collectProjects /
Srcx.generateIncludedBuildReports / Srcx.extractProjectSummary /
Srcx.computeBuildEdges / Srcx.generateClassDiagram via precomputed provider
values or lazy mapped providers rather than directly calling project.rootProject
or iterating includedBuilds inside the `@TaskAction`; ensure task inputs are
annotated as `@Input/`@InputFiles/@Classpath as appropriate and remove direct
Gradle model access from generate().
src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt (2)

28-35: ⚠️ Potential issue | 🟠 Major

Avoid project access inside @TaskAction.

Line 30 reads project.rootProject during execution. That keeps srcx-clean tied to live Gradle model state, so the task still won't be configuration-cache safe even after the property injection refactor. Model the root dir and included-build dirs as task inputs and wire them in registerTasks(...) instead.

🤖 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/CleanTask.kt` around lines 28 -
35, The CleanTask.clean method reads project.rootProject and
rootProject.gradle.includedBuilds at execution time, breaking
configuration-cache safety; change CleanTask to expose task inputs (e.g., a
DirectoryProperty for rootProjectDir and a
ListProperty/ConfigurableFileCollection for includedBuildDirs) and stop
referencing project inside the `@TaskAction`; populate those properties from
registerTasks(...) when tasks are registered (wire root dir and included build
dirs into the task), update `@TaskAction` clean to iterate those injected
properties and call cleanSafe(...) only on them, and add appropriate
`@InputFiles/`@PathSensitive annotations so Gradle knows the inputs.

37-42: ⚠️ Potential issue | 🔴 Critical

Harden the escape check before deleting anything.

canonicalPath.startsWith(...) is still prefix-based, so a value like ../repo2/.srcx can pass against /tmp/repo because /tmp/repo2 shares the same string prefix. This can delete outside the intended workspace.

Suggested hardening
     private fun cleanSafe(baseDir: File) {
-        val dir = File(baseDir, outputDir.get())
-        require(dir.canonicalPath.startsWith(baseDir.canonicalPath)) {
+        require(!File(outputDir.get()).isAbsolute) {
+            "outputDir must be relative: ${outputDir.get()}"
+        }
+        val basePath = baseDir.canonicalFile.toPath()
+        val dir = File(baseDir, outputDir.get()).canonicalFile
+        require(dir.toPath().startsWith(basePath)) {
             "outputDir '${outputDir.get()}' escapes project directory"
         }
         Srcx.cleanOutputDir(dir)
     }
🤖 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/CleanTask.kt` around lines 37 -
42, The current escape check in cleanSafe uses string prefix matching and can be
fooled; update cleanSafe (in CleanTask.kt) to compare Path objects instead:
obtain basePath = baseDir.canonicalFile.toPath() and targetPath = File(baseDir,
outputDir.get()).canonicalFile.toPath(), normalize them and verify
targetPath.startsWith(basePath) (Path.startsWith uses path segments, not string
prefixes); if the check fails, throw the same error message and do not call
Srcx.cleanOutputDir(dir). This ensures ../ escapes are detected reliably.
🤖 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/Srcx.kt`:
- Around line 282-301: discoverSourceSets() currently detects groovy/scala dirs
but sourceSetDirs() and extractSymbolsFromDirs() only handle kotlin/java,
causing empty summaries; either stop reporting groovy/scala in
discoverSourceSets() or extend downstream logic to fully support them—update
sourceSetDirs() to include groovy/scala source directory names and update
extractSymbolsFromDirs() to scan .groovy and .scala files (and adjust any
symbol-extraction logic to parse or skip these file types safely) so discovered
groovy/scala sets produce non-empty sourceDirs and symbol counts.
- Around line 385-407: The current depPattern Regex in Srcx.kt only matches
Kotlin-DSL dependency declarations like implementation("g:a:v"), so Groovy-style
declarations in build.gradle (e.g. implementation 'g:a:v' or implementation
"g:a:v") are ignored; update depPattern (or add a second pattern) used in the
parsing loop that reads buildFile.readLines() to also match Groovy forms by
accepting single-quoted and bare-double-quoted argument syntax and the same
scopes (api/implementation/compileOnly/runtimeOnly/testImplementation), then
ensure the match handling that constructs DependencyEntry (group ->
ArtifactGroup, artifact -> ArtifactName, version -> ArtifactVersion, scope)
works for either regex match so Groovy dependencies are included in results.
- Around line 600-605: The rootProject task registration uses bare strings so
subprojects calling wireAutoGenerate() end up finalizing a relative task name;
capture the TaskProvider when registering the root tasks (the TASK_CONTEXT and
TASK_CLEAN registrations that create ContextTask/CleanTask) and then use that
TaskProvider when wiring finalizers inside wireAutoGenerate() (i.e., call
task.finalizedBy(rootContextTaskProvider) instead of
task.finalizedBy(TASK_CONTEXT)); update the registration site to keep the
provider (e.g., contextTaskProvider) and reference that provider in
wireAutoGenerate() so the finalizer points to the absolute root task.

---

Duplicate comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt`:
- Around line 28-35: The CleanTask.clean method reads project.rootProject and
rootProject.gradle.includedBuilds at execution time, breaking
configuration-cache safety; change CleanTask to expose task inputs (e.g., a
DirectoryProperty for rootProjectDir and a
ListProperty/ConfigurableFileCollection for includedBuildDirs) and stop
referencing project inside the `@TaskAction`; populate those properties from
registerTasks(...) when tasks are registered (wire root dir and included build
dirs into the task), update `@TaskAction` clean to iterate those injected
properties and call cleanSafe(...) only on them, and add appropriate
`@InputFiles/`@PathSensitive annotations so Gradle knows the inputs.
- Around line 37-42: The current escape check in cleanSafe uses string prefix
matching and can be fooled; update cleanSafe (in CleanTask.kt) to compare Path
objects instead: obtain basePath = baseDir.canonicalFile.toPath() and targetPath
= File(baseDir, outputDir.get()).canonicalFile.toPath(), normalize them and
verify targetPath.startsWith(basePath) (Path.startsWith uses path segments, not
string prefixes); if the check fails, throw the same error message and do not
call Srcx.cleanOutputDir(dir). This ensures ../ escapes are detected reliably.

In `@src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt`:
- Around line 61-64: The code in ContextTask.kt building includedBuildRefs uses
build.projectDir.relativeTo(rootProject.projectDir) which can throw for builds
on different filesystem roots; change the mapping in the includedBuilds ->
includedBuildRefs logic to use relativeToOrNull(rootProject.projectDir) and, if
it returns null, fall back to build.projectDir.path (or
build.projectDir.absolutePath) so the task won't abort on non-relativizable
included builds.
- Around line 45-84: The generate() `@TaskAction` in ContextTask reads the live
Gradle model (project.rootProject, includedBuilds, project instances) which
breaks configuration-cache compatibility; move all Gradle API reads into task
registration and expose them as Providers/Files for the action. Specifically,
during task registration capture values via providers (e.g., rootProject
reference, includedBuilds list, buildPairs, relPath calculations, and summaries)
and wire them into task inputs like outputDir and a Provider<List<...>> or
RegularFile/DirectoryProperty; then change generate() to only consume those
Providers (call get() on them) and call Srcx.collectProjects /
Srcx.generateIncludedBuildReports / Srcx.extractProjectSummary /
Srcx.computeBuildEdges / Srcx.generateClassDiagram via precomputed provider
values or lazy mapped providers rather than directly calling project.rootProject
or iterating includedBuilds inside the `@TaskAction`; ensure task inputs are
annotated as `@Input/`@InputFiles/@Classpath as appropriate and remove direct
Gradle model access from generate().
🪄 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: 87756fac-3249-481f-98a3-8051692e0ab4

📥 Commits

Reviewing files that changed from the base of the PR and between eee2b59 and 3d9ecaf.

📒 Files selected for processing (13)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
  • src/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildReportTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildTraversalTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SourceSetDiscoveryTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (4)
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt Outdated
@ClankerGuru
ClankerGuru force-pushed the fix-extension-injection branch from 3d9ecaf to b89047a Compare April 9, 2026 04:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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/SrcxSettingsPluginTest.kt (1)

398-404: ⚠️ Potential issue | 🟡 Minor

Duplicate test case.

Lines 402-404 duplicate the test on lines 398-400. This appears to be a copy-paste error—both test TASK_CONTEXT instead of one testing TASK_CLEAN.

Suggested fix
                 then("TASK_CONTEXT is srcx-context") {
                     Srcx.TASK_CONTEXT shouldBe "srcx-context"
                 }

-                then("TASK_CONTEXT is srcx-context") {
-                    Srcx.TASK_CONTEXT shouldBe "srcx-context"
+                then("TASK_CLEAN is srcx-clean") {
+                    Srcx.TASK_CLEAN shouldBe "srcx-clean"
                 }
🤖 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/SrcxSettingsPluginTest.kt` around
lines 398 - 404, The second duplicate test block in SrcxSettingsPluginTest
repeats the TASK_CONTEXT assertion; replace the duplicate then(...) that
references TASK_CONTEXT with a test for TASK_CLEAN: update the test description
to something like "TASK_CLEAN is srcx-clean" and assert Srcx.TASK_CLEAN shouldBe
"srcx-clean" so the class-level constants are both covered (use the existing
then(...) block that currently duplicates TASK_CONTEXT and change its
description and assertion to reference Srcx.TASK_CLEAN and the expected
"srcx-clean" value).
🧹 Nitpick comments (2)
src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt (2)

37-44: Unify duplicated .gitignore writing helpers.

writeGitignore and writeGitignoreAt have the same body. Keeping one helper reduces drift risk.

Also applies to: 173-176

🤖 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/ReportWriter.kt` around lines
37 - 44, The two functions writeGitignore and writeGitignoreAt in
ReportWriter.kt contain identical bodies; consolidate them by removing one and
having the remaining helper accept both calling patterns (e.g., keep
writeGitignoreAt(rootProjectDir: File, outputDir: String) or keep
writeGitignore(rootProjectDir: File, outputDir: String)) and update callers to
use the unified function; ensure the unified function creates the directory
(dir.mkdirs()) and writes ".gitignore" with "*\n" as currently implemented and
remove the duplicate implementation to avoid drift.

47-99: Extract included-build summary collection into one shared path.

generateIncludedBuildReports and collectIncludedBuildSummaries both scan included builds and extract summaries. This duplicates heavy I/O/analysis work and can slow larger builds. Consider one helper that returns summaries and reuse it in both flows.

🤖 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/ReportWriter.kt` around lines
47 - 99, Extract the included-build scanning + summary extraction into a single
helper and reuse it from both functions: create or reuse a method that takes
builds: Collection<IncludedBuild> and returns Map<String, List<ProjectSummary>>
(you already have collectIncludedBuildSummaries — keep or adapt it as the single
source of truth), then modify generateIncludedBuildReports to call that helper
to get each build's summaries (instead of calling
ProjectScanner.discoverIncludedBuildProjects and
SymbolExtractor.extractStandaloneProjectSummary inline), and update any local
variable names accordingly so generateIncludedBuildReports uses the returned
summaries to render per-project symbols.md and the build context.md; ensure
behavior (sanitized projectPath handling, reportDir creation, writeGitignoreAt)
stays the same.
🤖 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/ReportWriter.kt`:
- Around line 130-142: The runCatching block in ReportWriter.kt currently
swallows all exceptions and returns "" silently; change the final
.getOrDefault("") to .getOrElse { e -> /* log and fallback */ } and emit a
warning that includes the exception and context before returning the fallback
string. Locate the block that calls scanSources, classifyAll,
buildDependencyGraph and generateDependencyDiagram and replace the silent
fallback with a logger.warn or logger.warn("[context] failed to generate class
diagram", e) (use the ReportWriter's existing logger/LOGGER instance) so the
root cause is recorded while still returning the empty string as a last resort.
- Around line 154-167: The Executor lifecycle in ReportWriter.kt is not
failure-safe: ensure the fixed thread pool (pool created with
Executors.newFixedThreadPool(THREAD_POOL_SIZE)) is always shut down even if
futures.map { it.get() } is interrupted or hangs; wrap the submission/results
collection in a try/finally where finally calls pool.shutdownNow() (or
shutdown() then awaitTermination) to guarantee termination, and replace plain
Future.get() with get(timeout, unit) to avoid indefinite blocking, catching
InterruptedException/ExecutionException/TimeoutException and converting them
into sensible result strings for each project (use the same project identifier
from work(project)).
- Around line 146-170: runParallel currently spins up a custom thread pool and
accesses Gradle Project instances on worker threads
(Executors.newFixedThreadPool, pool.submit, futures, runParallel, work), which
violates Gradle's threading model; replace this with the Gradle Worker API by
capturing immutable project data (project.path, project.projectDir,
project.name, etc.) on the task thread, implement a Worker class with
appropriate WorkParameters to accept that immutable data and the work payload,
submit work via WorkerExecutor.noIsolation() or processIsolation() as
appropriate, and migrate the error/result handling so failures are returned from
the worker (or thrown back to the task) instead of building futures from
Executors; if any shared mutable state is required, expose it via a thread-safe
BuildService.

In `@src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt`:
- Around line 63-97: Symbol extraction currently only skips lines starting with
'//' for properties but not for classes or functions; update the logic in
SymbolExtractor (where classPattern, functionPattern, propertyPattern are used
and results.add(SymbolEntry(...)) is called) to perform the same comment check
for all symbol kinds—i.e., if line.trimStart().startsWith("//") skip matching
entirely before running classPattern.find(), functionPattern.find(), or
propertyPattern.find(); optionally factor this into a small helper like
isCommentLine(line) and call it before all three pattern checks to keep the code
DRY.

---

Outside diff comments:
In `@src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt`:
- Around line 398-404: The second duplicate test block in SrcxSettingsPluginTest
repeats the TASK_CONTEXT assertion; replace the duplicate then(...) that
references TASK_CONTEXT with a test for TASK_CLEAN: update the test description
to something like "TASK_CLEAN is srcx-clean" and assert Srcx.TASK_CLEAN shouldBe
"srcx-clean" so the class-level constants are both covered (use the existing
then(...) block that currently duplicates TASK_CONTEXT and change its
description and assertion to reference Srcx.TASK_CLEAN and the expected
"srcx-clean" value).

---

Nitpick comments:
In `@src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt`:
- Around line 37-44: The two functions writeGitignore and writeGitignoreAt in
ReportWriter.kt contain identical bodies; consolidate them by removing one and
having the remaining helper accept both calling patterns (e.g., keep
writeGitignoreAt(rootProjectDir: File, outputDir: String) or keep
writeGitignore(rootProjectDir: File, outputDir: String)) and update callers to
use the unified function; ensure the unified function creates the directory
(dir.mkdirs()) and writes ".gitignore" with "*\n" as currently implemented and
remove the duplicate implementation to avoid drift.
- Around line 47-99: Extract the included-build scanning + summary extraction
into a single helper and reuse it from both functions: create or reuse a method
that takes builds: Collection<IncludedBuild> and returns Map<String,
List<ProjectSummary>> (you already have collectIncludedBuildSummaries — keep or
adapt it as the single source of truth), then modify
generateIncludedBuildReports to call that helper to get each build's summaries
(instead of calling ProjectScanner.discoverIncludedBuildProjects and
SymbolExtractor.extractStandaloneProjectSummary inline), and update any local
variable names accordingly so generateIncludedBuildReports uses the returned
summaries to render per-project symbols.md and the build context.md; ensure
behavior (sanitized projectPath handling, reportDir creation, writeGitignoreAt)
stays the same.
🪄 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: 3d169502-f41a-4cfe-8e0c-83958f430fa4

📥 Commits

Reviewing files that changed from the base of the PR and between 3d9ecaf and b89047a.

📒 Files selected for processing (17)
  • src/main/kotlin/zone/clanker/gradle/srcx/Srcx.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/scan/ProjectScanner.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
  • src/slopTest/kotlin/zone/clanker/gradle/srcx/PackageBoundaryTest.kt
  • src/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/BuildEdgesTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildReportTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildTraversalTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SourceSetDiscoveryTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxContextCleanTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxSettingsPluginTest.kt
✅ Files skipped from review due to trivial changes (3)
  • src/slopTest/kotlin/zone/clanker/gradle/srcx/TaskAnnotationTest.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/SrcxDsl.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxAutoGenerateTest.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/test/kotlin/zone/clanker/gradle/srcx/SourceSetDiscoveryTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/IncludedBuildTraversalTest.kt
  • src/test/kotlin/zone/clanker/gradle/srcx/SrcxDslTest.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/CleanTask.kt
  • src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt
Comment on lines +146 to +170
internal fun runParallel(
projects: List<Project>,
work: (Project) -> String,
) {
if (projects.isEmpty()) {
println("srcx: No projects to process.")
return
}
val pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE)
val futures =
projects.map { project ->
pool.submit(
Callable {
runCatching { work(project) }
.getOrElse { e -> "FAIL ${project.path}: ${e.message}" }
},
)
}
val results = futures.map { it.get() }
pool.shutdown()
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)
results.forEach { println(it) }
val failed = results.count { it.startsWith("FAIL") }
println("srcx: symbols complete -- ${projects.size} projects, $failed failed")
if (failed > 0) error("srcx: $failed project(s) failed during generation")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Is org.gradle.api.Project safe to access from custom executor threads inside a task action, and what does Gradle recommend for parallel work?

💡 Result:

No—org.gradle.api.Project (and most Gradle model/configuration state reachable from it, like Configuration resolution) should not be accessed from custom executor threads you create inside a task action. Gradle can treat such access as “not … in a safe manner” when it happens from a thread “not managed by Gradle,” and it’s increasingly incompatible with newer execution models/features (e.g., configuration cache / project isolation expectations). [3], [4]

What Gradle recommends for parallel work instead

  • Use the Worker API (WorkerExecutor) to run parallel work inside a task in a Gradle-managed way. It is explicitly intended for “safe, concurrent execution of work items” and parallel execution within a single task. [2]
  • In Worker API noIsolation() mode, Gradle explicitly constrains work items so they cannot change project state (i.e., don’t touch Project/mutable Gradle state from the worker). Capture everything you need from Project on the task thread and pass it as WorkParameters. [1]
  • For shared mutable resources across parallel work, use Build Services (which “must be thread-safe”) and let Gradle coordinate/constrain concurrent usage (e.g., via maxParallelUsages). [5]

Sources: Gradle Worker API docs [1], WorkerExecutor Javadoc [2], configuration-cache guidance about Project use at execution time [3], Gradle forum warning explaining “thread not managed by Gradle” [4], Build Services docs [5].


Use WorkerExecutor instead of custom thread pools for parallel work on Gradle Projects.

Gradle Project instances must not be accessed from custom executor threads created inside a task action. This violates Gradle's thread-safety model and will become increasingly incompatible with newer features (e.g., configuration cache, project isolation). Instead, use the Worker API (WorkerExecutor), which is explicitly designed for safe parallel execution within tasks. Capture immutable data from Project (e.g., path, projectDir, name) on the task thread and pass it as WorkParameters to your workers. If workers need shared mutable state, use Gradle Build Services, which are required to be thread-safe.

🤖 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/ReportWriter.kt` around lines
146 - 170, runParallel currently spins up a custom thread pool and accesses
Gradle Project instances on worker threads (Executors.newFixedThreadPool,
pool.submit, futures, runParallel, work), which violates Gradle's threading
model; replace this with the Gradle Worker API by capturing immutable project
data (project.path, project.projectDir, project.name, etc.) on the task thread,
implement a Worker class with appropriate WorkParameters to accept that
immutable data and the work payload, submit work via
WorkerExecutor.noIsolation() or processIsolation() as appropriate, and migrate
the error/result handling so failures are returned from the worker (or thrown
back to the task) instead of building futures from Executors; if any shared
mutable state is required, expose it via a thread-safe BuildService.

Comment thread src/main/kotlin/zone/clanker/gradle/srcx/report/ReportWriter.kt Outdated
Comment thread src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt Outdated
@ClankerGuru
ClankerGuru force-pushed the fix-extension-injection branch 4 times, most recently from eb8e8fd to f46acc1 Compare April 9, 2026 05:43
- SettingsExtension: abstract class with @Inject, Property<String>, Property<Boolean>
- Conventions set after creation (outputDir, autoGenerate)
- Extract ContextTask and CleanTask into task/ package
- SettingsPlugin is now thin — just registers tasks
- Matches wrkx architecture pattern
@ClankerGuru
ClankerGuru force-pushed the fix-extension-injection branch from f46acc1 to 12886f9 Compare April 9, 2026 06:45
…tal builds

- Hub classes rendered as trees showing each dependent's file path and line number
- Added HubDependent/HubDependentRef with file path + declaration line for both hubs and dependents
- Super node label for hubs with 50+ dependents (collapsed instead of listing all)
- Added declarationLine to SourceFileMetadata via PSI textOffset
- Rewrote IncludedBuildRenderer with full context: overview, symbols, hub trees, deps, findings
- Renamed symbols.md → context.md for per-project reports
- Added @InputFiles/@OutputDirectory to ContextTask for Gradle up-to-date checking
- Wired srcx-clean to lifecycle clean task via LifecycleBasePlugin
- Renamed HubClass.dependents → dependentCount, dependentNames → dependents (List<HubDependentRef>)
- Use relativeToOrNull with fallback for included builds outside root dir
- Wire autoGenerate finalizer via TaskProvider instead of bare string
- Add timeout and safe shutdown to executor pool in runParallel
- ContextTask: all Gradle model data (project dirs, paths, deps, included
  builds) captured at configuration time via task properties. Task action
  operates only on pre-computed data and files.
- CleanTask: base directories captured via ListProperty<File> instead of
  accessing project.rootProject at execution time.
- Added extractProjectSummaryFromData to SymbolExtractor (no Project param)
- Added writeProjectReportToDir, generateIncludedBuildReportsFromData,
  runParallelMapped to ReportWriter (no Project/IncludedBuild params)
- Created IncludedBuildInfo data class for serializable included build state
- Pre-computed dependencies via extractDependenciesFromProject at config time
- Added collectIncludedBuildInfos to capture included build data at config time
- Fix duplicate TASK_CONTEXT test to test TASK_CLEAN instead
- Unify writeGitignore/writeGitignoreAt into single implementation
- Remove old generateIncludedBuildReports and collectIncludedBuildSummaries
  that took IncludedBuild; only keep the FromData variant
- Extract writeBuildReports helper to deduplicate report writing
- Srcx: lifecycle logger for clean output
- ReportWriter: lifecycle for progress, warn for diagram failures
- ContextTask: task logger (inherited from DefaultTask)
- SymbolIndex: warn for parse failures
@ClankerGuru
ClankerGuru merged commit 4b2a043 into main Apr 9, 2026
2 checks passed
@ClankerGuru
ClankerGuru deleted the fix-extension-injection branch April 9, 2026 18:16
@coderabbitai coderabbitai Bot mentioned this pull request Aug 11, 2026
3 tasks
ClankerGuru added a commit that referenced this pull request Aug 11, 2026
* Use proper Gradle Property injection + extract tasks into classes

- SettingsExtension: abstract class with @Inject, Property<String>, Property<Boolean>
- Conventions set after creation (outputDir, autoGenerate)
- Extract ContextTask and CleanTask into task/ package
- SettingsPlugin is now thin — just registers tasks
- Matches wrkx architecture pattern

* Hub class trees with file+line, rich included build context, incremental builds

- Hub classes rendered as trees showing each dependent's file path and line number
- Added HubDependent/HubDependentRef with file path + declaration line for both hubs and dependents
- Super node label for hubs with 50+ dependents (collapsed instead of listing all)
- Added declarationLine to SourceFileMetadata via PSI textOffset
- Rewrote IncludedBuildRenderer with full context: overview, symbols, hub trees, deps, findings
- Renamed symbols.md → context.md for per-project reports
- Added @InputFiles/@OutputDirectory to ContextTask for Gradle up-to-date checking
- Wired srcx-clean to lifecycle clean task via LifecycleBasePlugin
- Renamed HubClass.dependents → dependentCount, dependentNames → dependents (List<HubDependentRef>)

* Address CodeRabbit review comments

- Use relativeToOrNull with fallback for included builds outside root dir
- Wire autoGenerate finalizer via TaskProvider instead of bare string
- Add timeout and safe shutdown to executor pool in runParallel

* Configuration-cache safe tasks: no Project access at execution time

- ContextTask: all Gradle model data (project dirs, paths, deps, included
  builds) captured at configuration time via task properties. Task action
  operates only on pre-computed data and files.
- CleanTask: base directories captured via ListProperty<File> instead of
  accessing project.rootProject at execution time.
- Added extractProjectSummaryFromData to SymbolExtractor (no Project param)
- Added writeProjectReportToDir, generateIncludedBuildReportsFromData,
  runParallelMapped to ReportWriter (no Project/IncludedBuild params)
- Created IncludedBuildInfo data class for serializable included build state
- Pre-computed dependencies via extractDependenciesFromProject at config time
- Added collectIncludedBuildInfos to capture included build data at config time

* Address latest CodeRabbit review

- Fix duplicate TASK_CONTEXT test to test TASK_CLEAN instead
- Unify writeGitignore/writeGitignoreAt into single implementation
- Remove old generateIncludedBuildReports and collectIncludedBuildSummaries
  that took IncludedBuild; only keep the FromData variant
- Extract writeBuildReports helper to deduplicate report writing

* Replace println/System.err with Gradle logger

- Srcx: lifecycle logger for clean output
- ReportWriter: lifecycle for progress, warn for diagram failures
- ContextTask: task logger (inherited from DefaultTask)
- SymbolIndex: warn for parse failures
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