feat(workspace): add Go workspace (go.work) discovery#495
Conversation
Reviewer's GuideAdds first-class multi-module workspace discovery for Maven, Gradle, and Go workspaces (go.work), wires them into workspace detection, and exposes the new discovery helpers via the public API, along with expanded default ignore patterns and comprehensive tests/fixtures. Sequence diagram for discoverGoWorkspaceModules using go work edit -jsonsequenceDiagram
participant Caller
participant Workspace as discoverGoWorkspaceModules
participant FS as FileSystem
participant Tools as tools_getCustomPath
participant Go as go_binary
Caller->>Workspace: discoverGoWorkspaceModules(workspaceRoot, opts)
Workspace->>Workspace: root = path.resolve(workspaceRoot)
Workspace->>Workspace: goWork = path.join(root, go.work)
Workspace->>FS: existsSync(goWork)
alt go.work missing
FS-->>Workspace: false
Workspace-->>Caller: []
else go.work present
FS-->>Workspace: true
Workspace->>Tools: getCustomPath(go, opts)
Tools-->>Workspace: goBin
Workspace->>Go: invokeCommand(goBin, [work, edit, -json, goWork], { cwd: root })
alt command fails
Go-->>Workspace: throw error
Workspace-->>Caller: []
else command succeeds
Go-->>Workspace: stdout JSON
Workspace->>Workspace: JSON.parse(stdout)
alt invalid JSON
Workspace-->>Caller: []
else valid JSON
Workspace->>Workspace: useEntries = workspace.Use or []
alt useEntries empty
Workspace-->>Caller: []
else useEntries nonempty
loop for each entry in useEntries
Workspace->>Workspace: diskPath = entry.DiskPath
alt diskPath defined
Workspace->>Workspace: moduleDir = path.resolve(root, diskPath)
Workspace->>Workspace: goMod = path.join(moduleDir, go.mod)
Workspace->>FS: existsSync(goMod)
alt go.mod exists
FS-->>Workspace: true
Workspace->>Workspace: manifestPaths.push(goMod)
else go.mod missing
FS-->>Workspace: false
end
else diskPath missing
Workspace->>Workspace: skip entry
end
end
Workspace->>Workspace: ignorePatterns = resolveWorkspaceDiscoveryIgnore(opts)
Workspace->>Workspace: filtered = filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns)
Workspace-->>Caller: filtered
end
end
end
end
Flow diagram for updated detectWorkspaceManifests ecosystem selectionflowchart TD
A[start detectWorkspaceManifests root opts] --> B[Check Cargo.toml and Cargo.lock]
B -->|both exist| C[discoverWorkspaceCrates]
C --> C1[Return ecosystem cargo and manifestPaths]
B -->|missing| D[Check pom.xml]
D -->|exists| E[discoverMavenModules]
E -->|manifestPaths length > 0| E1[Return ecosystem maven and manifestPaths]
E -->|manifestPaths length = 0| F[Check settings.gradle or settings.gradle.kts]
D -->|missing| F
F -->|exists| G[discoverGradleSubprojects]
G -->|manifestPaths length > 0| G1[Return ecosystem gradle and manifestPaths]
G -->|manifestPaths length = 0| H[Check go.work]
F -->|missing| H
H -->|exists| I[discoverGoWorkspaceModules]
I -->|manifestPaths length > 0| I1[Return ecosystem gomodules and manifestPaths]
I -->|manifestPaths length = 0| J[Check JS lock files and package.json]
H -->|missing| J
J --> K[Check for pnpm-lock.yaml, yarn.lock, package-lock.json, or npm-shrinkwrap.json]
K -->|has lock and package.json exists| L[discoverWorkspacePackages]
L --> L1[Return ecosystem javascript and manifestPaths]
K -->|no JS workspace| M[Return ecosystem unknown and empty manifestPaths]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
traverseForWrapper, onlyENOENTis treated as a normal "not found" case while otherfs.accessSyncerrors (e.g.EACCESon a non-executable wrapper) cause an exception that aborts discovery; consider treating non-executable wrappers as "not found" and continuing traversal instead of throwing. - The Gradle init script is written to a deterministic path in the OS temp directory (
da-list-projects-${process.pid}.gradle); to avoid collisions or stale files when multiple runs share a PID (e.g. tests or forks), consider usingfs.mkdtemp/a random suffix or reusing a shared script embedded in the repo.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `traverseForWrapper`, only `ENOENT` is treated as a normal "not found" case while other `fs.accessSync` errors (e.g. `EACCES` on a non-executable wrapper) cause an exception that aborts discovery; consider treating non-executable wrappers as "not found" and continuing traversal instead of throwing.
- The Gradle init script is written to a deterministic path in the OS temp directory (`da-list-projects-${process.pid}.gradle`); to avoid collisions or stale files when multiple runs share a PID (e.g. tests or forks), consider using `fs.mkdtemp`/a random suffix or reusing a shared script embedded in the repo.
## Individual Comments
### Comment 1
<location path="src/workspace.js" line_range="439" />
<code_context>
+ manifestPaths.push(rootBuild)
+ }
+
+ const initScriptPath = path.join(os.tmpdir(), `da-list-projects-${process.pid}.gradle`)
+ try {
+ fs.writeFileSync(initScriptPath, GRADLE_INIT_SCRIPT)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Using a predictable temp file path for the Gradle init script can cause collisions and subtle race conditions.
Because the path only uses the PID, multiple Gradle invocations from the same process (or reused PIDs) can overwrite or delete each other’s init scripts. Prefer using `fs.mkdtemp` to create a unique temp directory and place the init script inside it to avoid cross-process interference.
```suggestion
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'da-list-projects-'))
const initScriptPath = path.join(tmpDir, 'init.gradle')
```
</issue_to_address>
### Comment 2
<location path="src/workspace.js" line_range="561" />
<code_context>
+ const goBin = getCustomPath('go', opts)
+ let output
+ try {
+ output = invokeCommand(goBin, ['work', 'edit', '-json', goWork], { cwd: root })
+ } catch {
+ return []
</code_context>
<issue_to_address>
**issue (bug_risk):** The go work edit invocation likely passes the go.work path incorrectly, causing discovery to fail.
`go work edit -json` uses the current directory (or an explicit flag) to locate `go.work`, and treats positional arguments as edit directives (e.g. `use ./dir`), not as a filename. Passing `goWork` positionally will likely be ignored or misinterpreted, so discovery may always return an empty list. Consider running `go work edit -json` with `cwd: root` and no extra `go.work` argument, or use the proper flag to target a specific file.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Verification Report -- TC-42631. Review Feedback
Overall review feedback assessment: Both Sourcery comments are either out-of-scope or factually incorrect. No human reviewer comments. No blocking feedback. 2. Scope ContainmentChanged files (13):
Verdict: PASS -- All changes are directly related to Go workspace discovery. Minor whitespace fixes in 3. Commit Traceability
Verdict: PASS -- 2 commits, both use conventional commit format, logically separated (feature then refactor to proper location). 4. Sensitive PatternsScanned diff for passwords, secrets, tokens, API keys, credentials, private keys, and auth headers. Verdict: PASS -- No sensitive patterns detected. 5. CI Status
Verdict: PASS -- All 5 CI checks passed. 6. Acceptance Criteria
Verdict: PASS -- All 6 acceptance criteria met. 7. Test Quality9 new tests in
Test results: 21/21 workspace tests pass (12 pre-existing + 9 new). Missing from Jira spec: The Jira description requested a test for parent-relative paths ( Verdict: PASS -- Comprehensive coverage of happy paths, error cases, edge cases, and filter integration. 8. Overall VerdictPASS -- PR is ready to merge.
Minor note: The Jira spec mentions a test for parent-relative paths ( This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11. |
…es.js Move discoverGoWorkspaceModules to its provider file so workspace.js only retains generic scaffolding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace esmock invokeCommand mocks with real `go work edit -json` invocations for happy-path tests. Keep esmock only for error paths that cannot be reproduced with a real binary (command failure, invalid JSON output). Add go_workspace_empty fixture for the null-Use case. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
discoverGoWorkspaceModules()for discoveringgo.modmanifest paths in Go multi-module workspaces viago work edit -jsondetectWorkspaceManifests()(between Gradle and JavaScript in the detection order)discoverGoWorkspaceModulesfrom the public APITest plan
discoverGoWorkspaceModulessuite covering: no go.work, multi-module, nested dirs, single module, missing module dirs, command failure, invalid JSON, empty Use array, ignore pattern filteringJira
TC-4263
🤖 Generated with Claude Code
Summary by Sourcery
Add multi-ecosystem workspace discovery for Maven, Gradle, and Go workspaces and integrate them into workspace manifest detection.
New Features:
Enhancements:
Tests: