feat(workspace): add uv workspace (pyproject.toml) discovery#496
Merged
Conversation
Reviewer's GuideAdds first-class multi-ecosystem workspace discovery (Maven, Gradle, Go, uv/pyproject) to the workspace analysis pipeline, including CLI wrapper resolution, TOML parsing via smol-toml, enhanced default ignore patterns, and comprehensive tests/fixtures for the new discovery paths and error/ignore handling. Class diagram for new workspace discovery utilities and exportsclassDiagram
class WorkspaceModule {
<<module>>
+discoverMavenModules(workspaceRoot, opts) Promise~string[]~
+discoverGradleSubprojects(workspaceRoot, opts) Promise~string[]~
+discoverGoWorkspaceModules(workspaceRoot, opts) Promise~string[]~
+discoverUvWorkspaceMembers(workspaceRoot, opts) Promise~string[]~
+discoverWorkspaceCrates(workspaceRoot, opts) Promise~string[]~
+discoverWorkspacePackages(workspaceRoot, opts) Promise~string[]~
+resolveMavenBinary(startDir, opts) string
+resolveGradleBinary(startDir, opts) string
+traverseForWrapper(startDir, wrapperName, repoRoot) string
+listMavenModules(dir, mvnBin) string[]
+collectMavenModules(dir, mvnBin, visited, manifestPaths) void
+parseMavenModuleList(raw) string[]
+parseGradleInitScriptOutput(raw) ProjectInfo[]
+hasProjectMetadata(pyprojectPath) boolean
+resolveWorkspaceDiscoveryIgnore(opts) string[]
+buildWorkspaceDiscoveryGlobOptions(root, ignorePatterns) GlobOptions
+filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns) string[]
+toManifestGlobPatterns(memberPatterns, manifestFileName) string[]
}
class ToolsModule {
<<module>>
+getCustom(toolName, opts) string
+getCustomPath(toolName, opts) string
+getGitRootDir(startDir) string
+getWrapperPreference(toolName, opts) boolean
+invokeCommand(binary, args, options) Buffer
}
class IndexModule {
<<module>>
+detectWorkspaceManifests(root, opts) DetectionResult
+stackAnalysisBatch(workspaceRoot, html, opts) Promise~AnalysisResult[]~
+discoverMavenModules(workspaceRoot, opts) Promise~string[]~
+discoverGradleSubprojects(workspaceRoot, opts) Promise~string[]~
+discoverGoWorkspaceModules(workspaceRoot, opts) Promise~string[]~
+discoverUvWorkspaceMembers(workspaceRoot, opts) Promise~string[]~
}
class DetectionResult {
+ecosystem : string
+manifestPaths : string[]
}
class ProjectInfo {
+path : string
+dir : string
}
class GlobOptions {
+cwd : string
+ignore : string[]
+absolute : boolean
}
class AnalysisResult {
+workspaceRoot : string
+ecosystem : string
+total : number
+successful : number
+failed : number
}
WorkspaceModule ..> ToolsModule : uses
IndexModule ..> WorkspaceModule : calls
IndexModule ..> DetectionResult : returns
WorkspaceModule ..> GlobOptions : constructs
WorkspaceModule ..> ProjectInfo : returns
IndexModule ..> AnalysisResult : returns
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 1 issue, and left some high level feedback:
- In
discoverGradleSubprojects, the init script is written to a fixed path inos.tmpdir()keyed only byprocess.pid, which can collide if multiple calls run in the same process (or overlap across threads); consider usingfs.mkdtempor a random suffix per call and cleaning up that directory. - In
discoverUvWorkspaceMembers,hasProjectMetadatareparses the rootpyproject.tomlthat was already parsed earlier; you could pass the parsed object into this check (or inline the logic) to avoid a second read/parse and keep behavior centralized.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `discoverGradleSubprojects`, the init script is written to a fixed path in `os.tmpdir()` keyed only by `process.pid`, which can collide if multiple calls run in the same process (or overlap across threads); consider using `fs.mkdtemp` or a random suffix per call and cleaning up that directory.
- In `discoverUvWorkspaceMembers`, `hasProjectMetadata` reparses the root `pyproject.toml` that was already parsed earlier; you could pass the parsed object into this check (or inline the logic) to avoid a second read/parse and keep behavior centralized.
## Individual Comments
### Comment 1
<location path="src/workspace.js" line_range="442" />
<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):** Temp init script name based only on PID can cause collisions within the same process.
Because the filename only varies by PID, all Gradle detections in the same Node.js process will share this temp file. Concurrent or overlapping calls can clobber each other’s init scripts or delete the file while another call still needs it. Consider adding extra uniqueness (e.g. a counter, timestamp, `crypto.randomUUID()`) or using a temp-file helper to avoid intra-process races.
```suggestion
const initScriptPath = path.join(
os.tmpdir(),
`da-list-projects-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.gradle`
)
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Member
Author
Verification Report -- TC-4265Scope Check
Changed Files (19 files, +238/-6)
Acceptance Criteria Verification
Test ResultsTest Coverage vs. Jira Test Requirements
Review Comment Classification
Code Quality Notes
Verdict: PASSAll acceptance criteria are met. 8/8 tests pass with no regressions. CI is green. Code is clean and well-structured. This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11. |
ruromero
requested changes
May 7, 2026
ruromero
left a comment
Collaborator
There was a problem hiding this comment.
only a few minor comments
Strum355
force-pushed
the
TC-4265
branch
2 times, most recently
from
May 7, 2026 10:58
4bb3fa6 to
b6434f1
Compare
Move discoverUvWorkspaceMembers and hasProjectMetadata to their provider file so workspace.js only retains generic scaffolding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Export the existing toManifestGlobPatterns function from workspace.js instead of duplicating it in python_uv.js. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ruromero
approved these changes
May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
discoverUvWorkspaceMembers()for discovering workspace members in uv/Python monorepospyproject.tomlfor[tool.uv.workspace]member globs and resolves viafast-glob— nouvCLI needed[project]in root → exclude root) vs root-package workspaces (include root)excludepatterns andTRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE__pycache__and.venvto default ignore patternsTest plan
Jira
TC-4265
🤖 Generated with Claude Code
Summary by Sourcery
Extend workspace manifest discovery to support additional ecosystems and uv-based Python monorepos.
New Features:
Enhancements:
Tests: