Skip to content

feat(workspace): add Go workspace (go.work) discovery#445

Open
Strum355 wants to merge 2 commits intomainfrom
TC-4264
Open

feat(workspace): add Go workspace (go.work) discovery#445
Strum355 wants to merge 2 commits intomainfrom
TC-4264

Conversation

@Strum355
Copy link
Copy Markdown
Member

@Strum355 Strum355 commented Apr 28, 2026

Summary

  • Add GoWorkspace record for deserializing go work edit -json output
  • Add discoverGoWorkspaceModules() method in ExhortApi for discovering go.mod manifest paths in Go multi-module workspaces
  • Integrate Go workspace detection into discoverWorkspaceManifests() (between Gradle and JavaScript in the detection order)

Test plan

  • 4 GoWorkspace deserialization tests (standard output, null Use, empty Use, unknown fields)
  • 7 discovery tests (multi-module, nested, single, missing dir, command failure, empty Use, ignore patterns)
  • CI verification (no JDK available locally)

Dependencies

Depends on #444 (Gradle workspace discovery - TC-4262) for the base branch

Jira

TC-4264

🤖 Generated with Claude Code

Summary by Sourcery

Extend workspace manifest discovery to support Go multi-module workspaces and Gradle multi-project builds, while tightening default ignore patterns and adding comprehensive tests for the new discovery logic.

New Features:

  • Detect Go workspaces and discover go.mod manifests using go work edit -json output
  • Detect Gradle multi-project builds and collect build.gradle and build.gradle.kts manifests via a custom init script

Enhancements:

  • Expand default workspace discovery ignore patterns to include build, target, and .gradle directories

Tests:

  • Add Go workspace deserialization tests covering standard, null, empty, and unknown-field cases
  • Add Go workspace discovery tests for multi-module, nested, single-module, failure, missing module, and ignore-pattern scenarios
  • Add Gradle workspace discovery tests for various project structures, command failures, missing subprojects, and ignore-pattern filtering

Strum355 and others added 2 commits April 28, 2026 12:36
Discover subprojects in Gradle multi-project builds using a custom init
script that emits structured project listings. Supports Groovy and Kotlin
DSL variants, gradlew wrapper detection, and workspaceDiscoveryIgnore
filtering. Adds **/build/** and **/.gradle/** to default ignore patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add discoverGoWorkspaceModules() for discovering go.mod manifest paths
in Go multi-module workspaces via `go work edit -json`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 28, 2026

Reviewer's Guide

Adds Go multi-module workspace discovery based on go work edit -json, integrates it into the manifest discovery flow, enhances Gradle multi-project discovery, and extends default ignore patterns, with comprehensive unit tests and fixtures for both Gradle and Go workspaces.

Sequence diagram for Go workspace (go.work) module discovery

sequenceDiagram
  participant ExhortApi
  participant Operations
  participant GoCLI
  participant ObjectMapper
  participant WorkspaceUtils

  ExhortApi->>ExhortApi: discoverWorkspaceManifests(workspaceDir, ignorePatterns)
  ExhortApi->>ExhortApi: Files.isRegularFile(workspaceDir.resolve(go.work))
  alt go.work exists
    ExhortApi->>ExhortApi: discoverGoWorkspaceModules(workspaceDir, ignorePatterns)
    ExhortApi->>Operations: getCustomPathOrElse(go)
    Operations-->>ExhortApi: goBin

    ExhortApi->>Operations: runProcessGetFullOutput(workspaceDir, [goBin, work, edit, -json, goWork], null)
    Operations->>GoCLI: Execute go work edit -json
    GoCLI-->>Operations: exitCode, stdout
    Operations-->>ExhortApi: ProcessExecOutput

    ExhortApi->>ExhortApi: check exitCode
    alt exitCode != 0
      ExhortApi-->>ExhortApi: return emptyList
    else exitCode == 0
      ExhortApi->>ObjectMapper: readValue(stdout, GoWorkspace)
      ObjectMapper-->>ExhortApi: GoWorkspace workspace

      ExhortApi->>ExhortApi: iterate workspace.use()
      ExhortApi->>ExhortApi: resolve moduleDir and go.mod for each UseEntry
      ExhortApi->>ExhortApi: collect existing go.mod paths

      ExhortApi->>WorkspaceUtils: filterByIgnorePatterns(workspaceDir, manifests, ignorePatterns)
      WorkspaceUtils-->>ExhortApi: filtered manifests
      ExhortApi-->>ExhortApi: return manifests to discoverWorkspaceManifests
    end
  else go.work missing
    ExhortApi-->>ExhortApi: Skip Go workspace discovery
  end
Loading

Class diagram for GoWorkspace and updated ExhortApi workspace discovery

classDiagram
  class ExhortApi {
    -static Set DEFAULT_WORKSPACE_DISCOVERY_IGNORE
    +Set resolveIgnorePatterns(Set callerPatterns)
    +List discoverWorkspaceManifests(Path workspaceDir, Set ignorePatterns)
    -List discoverCargoManifests(Path workspaceDir, Set ignorePatterns)
    -List discoverGoWorkspaceModules(Path workspaceDir, Set ignorePatterns)
    -String resolveGradleBinary(Path startDir)
    -List discoverGradleSubprojects(Path workspaceDir, Set ignorePatterns)
    -static List parseGradleInitScriptOutput(String raw)
  }

  class GoWorkspace {
    +List use()
  }

  class GoWorkspace_UseEntry {
    +String diskPath()
  }

  class GradleProject {
    +String path()
    +String dir()
  }

  class Operations {
    +static String getCustomPathOrElse(String binary)
    +static boolean getWrapperPreference(String tool)
    +static boolean isWindows()
    +static ProcessExecOutput runProcessGetFullOutput(Path workspaceDir, String[] command, Map env)
  }

  class WorkspaceUtils {
    +static List filterByIgnorePatterns(Path workspaceDir, List manifests, Set ignorePatterns)
  }

  class ObjectMapper {
    +GoWorkspace readValue(String json, Class type)
  }

  class JavaMavenProvider {
    +static String traverseForMvnw(String wrapperName, String startPath)
  }

  %% Relationships
  ExhortApi ..> GoWorkspace : uses
  ExhortApi ..> GoWorkspace_UseEntry : iterates
  GoWorkspace o-- GoWorkspace_UseEntry : contains
  ExhortApi ..> GradleProject : uses
  ExhortApi ..> Operations : uses
  ExhortApi ..> WorkspaceUtils : uses
  ExhortApi ..> ObjectMapper : uses
  ExhortApi ..> JavaMavenProvider : uses
  Operations ..> ProcessExecOutput : returns
Loading

Flow diagram for updated workspace manifest discovery order

flowchart TD
  start["Start workspace discovery"]
  A["discoverWorkspaceManifests(workspaceDir, ignorePatterns)"]

  A --> B{Workspace is Rust?}
  B -- "Yes" --> B1["discoverCargoManifests"]
  B -- "No" --> C{Has Gradle settings.gradle or settings.gradle.kts?}

  C -- "Yes" --> C1["discoverGradleSubprojects"]
  C -- "No" --> D{Has go.work?}

  D -- "Yes" --> D1["discoverGoWorkspaceModules"]
  D1 --> D2{Found go.mod manifests?}
  D2 -- "Yes" --> returnGo["Return Go manifests"]
  D2 -- "No" --> E

  D -- "No" --> E["Check for JS workspace (package.json + lock file)"]
  E --> E1{Has JS workspace?}
  E1 -- "Yes" --> returnJs["Return JS manifests"]
  E1 -- "No" --> F["Fallback to other manifest discovery mechanisms"]

  B1 --> endRust["Return Cargo manifests"]
  C1 --> endGradle["Return Gradle manifests"]
  F --> endOther["Return discovered manifests or empty list"]
Loading

File-Level Changes

Change Details Files
Integrate Go workspace and Gradle multi-project detection into the workspace manifest discovery flow, and broaden default ignore patterns.
  • Extend default workspace ignore patterns to include Maven/Gradle build output and Gradle metadata directories.
  • Update manifest discovery ordering to check Gradle settings before JavaScript manifests and to invoke Go workspace discovery when a go.work file is present.
  • Filter discovered manifests using existing ignore pattern utilities after language-specific discovery.
src/main/java/io/github/guacsec/trustifyda/impl/ExhortApi.java
Add Go workspace discovery using go work edit -json and a JSON model for its output.
  • Introduce a GoWorkspace record type, with nested UseEntry, to model the JSON output from go work edit -json, ignoring unknown fields.
  • Implement discoverGoWorkspaceModules to execute the Go CLI, deserialize the workspace definition, resolve member module directories, and collect existing go.mod files.
  • Short-circuit on Go command failures, null or empty workspace member lists, and missing DiskPath values, returning an empty manifest list in those cases.
src/main/java/io/github/guacsec/trustifyda/impl/ExhortApi.java
src/main/java/io/github/guacsec/trustifyda/providers/golang/model/GoWorkspace.java
Implement Gradle multi-project discovery via a custom init script and parsing of structured Gradle output.
  • Add a Gradle init script string that defines a daListProjects task to print project paths and directories in a machine-readable format.
  • Implement resolveGradleBinary to prefer the Gradle wrapper when configured, falling back to a system gradle binary otherwise.
  • Implement discoverGradleSubprojects to run Gradle with the init script, parse the structured output into project entries, resolve build.gradle/build.gradle.kts files for each project, and clean up the temporary init script file.
  • Introduce a GradleProject record and parseGradleInitScriptOutput helper to transform init script output lines into typed project descriptors, ignoring unrelated log lines.
src/main/java/io/github/guacsec/trustifyda/impl/ExhortApi.java
Add focused unit tests and fixtures for Gradle workspace discovery.
  • Cover parseGradleInitScriptOutput behavior for standard, nested, null, empty, and noisy outputs.
  • Test discoverWorkspaceManifests when resolving Gradle multi-project workspaces under several scenarios: standard multi-project, nested subprojects, mixed Groovy/Kotlin DSL, no subprojects, Gradle command failure, missing subproject directories, and ignore pattern filtering.
  • Verify that the default ignore patterns include build and .gradle directories.
  • Add Gradle workspace fixture directories and build files for the tested scenarios.
src/test/java/io/github/guacsec/trustifyda/impl/GradleWorkspaceDiscoveryTest.java
src/test/resources/tst_manifests/workspace/gradle/**
Add Go workspace discovery tests and corresponding workspace fixtures.
  • Test GoWorkspace deserialization for standard JSON output, null Use, empty Use, and presence of extra/unknown fields.
  • Test discoverWorkspaceManifests for Go workspaces with multiple modules, nested modules, a single module, missing module directories, Go command failure, empty Use lists, and ignore pattern filtering.
  • Provide Go workspace fixture directories with go.work and go.mod files matching the test scenarios.
src/test/java/io/github/guacsec/trustifyda/impl/GoWorkspaceDiscoveryTest.java
src/test/resources/tst_manifests/workspace/go/**

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link
Copy Markdown
Contributor

Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit 157d3c1.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/io/github/guacsec/trustifyda/impl/ExhortApi.java" line_range="1090-1099" />
<code_context>
+      return List.of();
+    }
+    List<GradleProject> projects = new ArrayList<>();
+    for (String line : raw.split("\n")) {
+      if (!line.startsWith("::DA_PROJECT::")) {
+        continue;
+      }
+      String[] parts = line.split("::");
+      List<String> nonEmpty = new ArrayList<>();
+      for (String part : parts) {
+        if (!part.isEmpty()) {
+          nonEmpty.add(part);
+        }
+      }
+      if (nonEmpty.size() >= 3) {
+        projects.add(new GradleProject(nonEmpty.get(1), nonEmpty.get(2)));
+      }
+    }
</code_context>
<issue_to_address>
**issue:** Handle Windows CRLF and stray whitespace when parsing Gradle output lines.

This logic splits only on "\n" and never trims lines or parts. On Windows, Gradle often emits CRLF, so `line` and `proj.dir()` can include a trailing `\r`, leading to invalid paths and subprojects being skipped. Normalize the input (e.g., `line = line.trim()`) and/or trim each parsed part before creating `GradleProject` so `\r` and extra whitespace don’t end up in paths.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1090 to +1099
for (String line : raw.split("\n")) {
if (!line.startsWith("::DA_PROJECT::")) {
continue;
}
String[] parts = line.split("::");
List<String> nonEmpty = new ArrayList<>();
for (String part : parts) {
if (!part.isEmpty()) {
nonEmpty.add(part);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: Handle Windows CRLF and stray whitespace when parsing Gradle output lines.

This logic splits only on "\n" and never trims lines or parts. On Windows, Gradle often emits CRLF, so line and proj.dir() can include a trailing \r, leading to invalid paths and subprojects being skipped. Normalize the input (e.g., line = line.trim()) and/or trim each parsed part before creating GradleProject so \r and extra whitespace don’t end up in paths.

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