feat(workspace): add Maven multi-module workspace discovery#493
Merged
Conversation
Reviewer's GuideImplements Maven multi-module workspace discovery by adding a Maven manifest discovery pipeline (including wrapper resolution and recursive module traversal) and wiring it into existing workspace/ecosystem detection, with supporting tests and new Maven fixture projects. Sequence diagram for Maven workspace manifest detectionsequenceDiagram
actor Developer
participant App as NodeApp
participant Detect as detectWorkspaceManifests
participant MavenDiscover as discoverMavenModules
participant ResolveBin as resolveMavenBinary
participant Traverse as traverseForWrapper
participant ListModules as listMavenModules
participant InvokeCmd as invokeCommand
participant MavenCli as Maven
Developer->>App: run_workspace_analysis(root, opts)
App->>Detect: detectWorkspaceManifests(root, opts)
Detect->>Detect: check Cargo.toml and Cargo.lock
alt has_Cargo_workspace
Detect-->>App: ecosystem cargo, manifestPaths
else no_Cargo_workspace
Detect->>Detect: check pom.xml
alt pom_xml_exists
Detect->>MavenDiscover: discoverMavenModules(root, opts)
MavenDiscover->>ResolveBin: resolveMavenBinary(root, opts)
alt wrapper_preferred
ResolveBin->>Traverse: traverseForWrapper(root, mvnw)
alt mvnw_found
Traverse-->>ResolveBin: mvnw_path
else mvnw_not_found
Traverse-->>ResolveBin: undefined
ResolveBin->>ResolveBin: fall back to mvn
end
else wrapper_not_preferred
ResolveBin->>ResolveBin: use mvn
end
ResolveBin-->>MavenDiscover: mvnBin
MavenDiscover->>MavenDiscover: init visited, manifestPaths with root pom.xml
MavenDiscover->>MavenDiscover: collectMavenModules(root, mvnBin, visited, manifestPaths)
loop per_module_directory
MavenDiscover->>ListModules: listMavenModules(dir, mvnBin)
ListModules->>InvokeCmd: invokeCommand(mvnBin, help:evaluate, ...)
InvokeCmd->>MavenCli: mvn help:evaluate -Dexpression=project.modules
MavenCli-->>InvokeCmd: stdout [module-a, module-b]
InvokeCmd-->>ListModules: Buffer stdout
ListModules->>ListModules: parseMavenModuleList(stdout)
alt parse_success
ListModules-->>MavenDiscover: [modules]
MavenDiscover->>MavenDiscover: add module pom.xml paths
MavenDiscover->>MavenDiscover: recurse collectMavenModules(moduleDir,...)
else mvn_fails_or_no_modules
ListModules-->>MavenDiscover: []
end
end
MavenDiscover->>MavenDiscover: resolveWorkspaceDiscoveryIgnore(opts)
MavenDiscover->>MavenDiscover: filterManifestPathsByDiscoveryIgnore
MavenDiscover-->>Detect: manifestPaths (pom.xml list)
Detect-->>App: ecosystem maven, manifestPaths
else no_pom_xml
Detect->>Detect: fall back to JavaScript detection
Detect-->>App: ecosystem javascript or unknown
end
end
App-->>Developer: workspace manifests and ecosystem
Class diagram for new Maven discovery utilities and exportsclassDiagram
class WorkspaceModule {
+resolveMavenBinary(startDir, opts)
+traverseForWrapper(startDir, wrapperName, repoRoot)
+discoverMavenModules(workspaceRoot, opts)
+collectMavenModules(dir, mvnBin, visited, manifestPaths)
+listMavenModules(dir, mvnBin)
+parseMavenModuleList(raw)
+resolveWorkspaceDiscoveryIgnore(opts)
+filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns)
}
class ToolsModule {
+getCustom(name, opts)
+getCustomPath(name, opts)
+getGitRootDir(startDir)
+getWrapperPreference(toolName, opts)
+invokeCommand(bin, args, options)
}
class IndexModule {
+discoverMavenModules(workspaceRoot, opts)
+discoverWorkspaceCrates(root, opts)
+discoverWorkspacePackages(root, opts)
+detectWorkspaceManifests(root, opts)
}
WorkspaceModule --> ToolsModule : uses
IndexModule --> WorkspaceModule : imports_discoverMavenModules
IndexModule --> ToolsModule : uses_in_other_functions
Flow diagram for Maven multi-module discovery and recursionflowchart TD
A["discoverMavenModules(workspaceRoot, opts)"] --> B["root = resolve(workspaceRoot)\nrootPom = root/pom.xml"]
B --> C{pom.xml exists?}
C -->|no| D["return []"]
C -->|yes| E["mvnBin = resolveMavenBinary(root, opts)"]
E --> F["visited = new Set()\nmanifestPaths = [rootPom]"]
F --> G["collectMavenModules(root, mvnBin, visited, manifestPaths)"]
G --> H["ignorePatterns = resolveWorkspaceDiscoveryIgnore(opts)"]
H --> I["filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns)"]
I --> J["return filtered manifestPaths"]
subgraph CollectModules["collectMavenModules(dir, mvnBin, visited, manifestPaths)"]
K["resolvedDir = resolve(dir)"] --> L{visited has resolvedDir?}
L -->|yes| M["return"]
L -->|no| N["visited.add(resolvedDir)"]
N --> O["modules = listMavenModules(resolvedDir, mvnBin)"]
O --> P{for each mod in modules}
P -->|loop| Q["moduleDir = resolve(resolvedDir, mod)\nmodulePom = moduleDir/pom.xml"]
Q --> R{modulePom exists?}
R -->|no| P
R -->|yes| S["manifestPaths.push(modulePom)"]
S --> T["collectMavenModules(moduleDir, mvnBin, visited, manifestPaths)"]
T --> P
P -->|done| U["return"]
end
subgraph ListModules["listMavenModules(dir, mvnBin)"]
V["try invokeCommand(mvnBin, help:evaluate ... -Dexpression=project.modules)"] --> W{invokeCommand throws?}
W -->|yes| X["return []"]
W -->|no| Y["raw = stdout.toString().trim()"]
Y --> Z{raw empty or 'null'?}
Z -->|yes| X
Z -->|no| AA["modules = parseMavenModuleList(raw)"]
AA --> AB["return modules"]
end
subgraph ParseModules["parseMavenModuleList(raw)"]
AC["match = raw.match(/^[\[](+)\]$/)"] --> AD{match truthy?}
AD -->|no| AE["return []"]
AD -->|yes| AF["inner = match[1]"]
AF --> AG["split on ','\ntrim entries\nfilter nonempty"]
AG --> AH["return module names"]
end
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 4 issues, and left some high level feedback:
- In
traverseForWrapper, any non-ENOENT error fromfs.accessSync(e.g. EACCES on a non-executable wrapper) causes a hard failure; consider treating those as "not a usable wrapper" and continuing the upward search instead of throwing to avoid surprising crashes on misconfigured repos. - The
parseMavenModuleListhelper assumes themvn help:evaluateoutput is exactly[module-a, module-b], but in real projects Maven may emit extra log lines or whitespace even with-q; consider making the parsing more robust by scanning lines for the bracketed list rather than relying on a full-string regex match.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `traverseForWrapper`, any non-ENOENT error from `fs.accessSync` (e.g. EACCES on a non-executable wrapper) causes a hard failure; consider treating those as "not a usable wrapper" and continuing the upward search instead of throwing to avoid surprising crashes on misconfigured repos.
- The `parseMavenModuleList` helper assumes the `mvn help:evaluate` output is exactly `[module-a, module-b]`, but in real projects Maven may emit extra log lines or whitespace even with `-q`; consider making the parsing more robust by scanning lines for the bracketed list rather than relying on a full-string regex match.
## Individual Comments
### Comment 1
<location path="src/workspace.js" line_range="264-273" />
<code_context>
+ repoRoot = repoRoot || getGitRootDir(currentDir) || path.parse(currentDir).root
+ const wrapperPath = path.join(currentDir, wrapperName)
+
+ try {
+ fs.accessSync(wrapperPath, fs.constants.X_OK)
+ return wrapperPath
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ const rootDir = path.parse(currentDir).root
+ if (currentDir === repoRoot || currentDir === rootDir) {
+ return undefined
+ }
+ const parentDir = path.dirname(currentDir)
+ if (parentDir === currentDir || parentDir === rootDir) {
+ return undefined
+ }
+ return traverseForWrapper(parentDir, wrapperName, repoRoot)
+ }
+ throw new Error(`failure searching for ${wrapperName}`, { cause: err })
+ }
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Non-ENOENT errors during wrapper lookup abort the search instead of continuing upwards.
Because only `ENOENT` triggers upward traversal, any other `fs.accessSync` error (e.g. `EACCES` for unreadable/non-executable) stops the search and can block discovery of a valid wrapper in parent directories, especially in locked-down environments. It may be safer to treat non-`ENOENT` errors as “wrapper not usable here” and keep traversing (optionally logging unexpected codes), only aborting on genuinely unrecoverable conditions.
</issue_to_address>
### Comment 2
<location path="test/providers/workspace.test.js" line_range="192" />
<code_context>
})
})
+
+suite('discoverMavenModules', () => {
+ test('returns empty when no pom.xml at root', async () => {
+ const result = await discoverMavenModules('test/providers/tst_manifests/npm')
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests that exercise Maven wrapper resolution and getWrapperPreference behavior
The new `resolveMavenBinary`/`traverseForWrapper` logic (with `getWrapperPreference` and `getGitRootDir`) isn’t covered by tests; only `invokeCommand` is exercised. Please add tests that mock `getWrapperPreference`, `getGitRootDir`, and filesystem access to verify:
- `TRUSTIFY_DA_PREFER_MVNW` causes `mvnw` to be used when present in the root or a parent directory.
- The search stops at the git root or filesystem root.
- Fallback to `getCustomPath('mvn', ...)` when no wrapper is found.
These can be additional `discoverMavenModules` tests (checking which binary is passed to `invokeCommand`) or a small dedicated resolver suite in this file.
Suggested implementation:
```javascript
suite('discoverMavenModules', () => {
test('returns empty when no pom.xml at root', async () => {
const result = await discoverMavenModules('test/providers/tst_manifests/npm')
expect(result).to.be.an('array')
expect(result).to.have.lengthOf(0)
})
test('returns root pom only when mvn reports no modules', async () => {
const root = path.resolve('test/providers/tst_manifests/maven/maven_no_modules')
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getWrapperPreference: () => 'mvn',
},
'../../src/command.js': {
invokeCommand: sinon.spy(async () => ({
stdout: '',
stderr: '',
status: 0,
})),
},
})
const result = await discoverMavenModules(root)
expect(result).to.be.an('array')
expect(result).to.have.lengthOf(1)
expect(result[0]).to.equal(path.join(root, 'pom.xml'))
})
test('prefers mvnw in workspace root when TRUSTIFY_DA_PREFER_MVNW is set', async () => {
const root = path.resolve('test/providers/tst_manifests/maven/maven_wrapper_root')
const invokeCommand = sinon.spy(async () => ({
stdout: '',
stderr: '',
status: 0,
}))
const gitRoot = root
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getWrapperPreference: () => 'mvnw',
},
'../../src/git.js': {
getGitRootDir: async () => gitRoot,
},
'fs/promises': {
access: async p => {
// Simulate mvnw existing only in the workspace root
if (p === path.join(root, 'mvnw')) {
return
}
const err = new Error('ENOENT')
err.code = 'ENOENT'
throw err
},
},
'../../src/command.js': {
invokeCommand,
},
})
await discoverMavenModules(root)
expect(invokeCommand.called).to.be.true
const [binary] = invokeCommand.firstCall.args
expect(path.basename(binary)).to.equal(process.platform === 'win32' ? 'mvnw.cmd' : 'mvnw')
})
test('search for mvnw does not traverse above git root', async () => {
const workspaceRoot = path.resolve('test/providers/tst_manifests/maven/maven_wrapper_nested')
const gitRoot = path.dirname(workspaceRoot) // pretend git root is the parent of the workspace
const invokeCommand = sinon.spy(async () => ({
stdout: '',
stderr: '',
status: 0,
}))
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getWrapperPreference: () => 'mvnw',
},
'../../src/git.js': {
getGitRootDir: async () => gitRoot,
},
'fs/promises': {
access: async p => {
// Ensure we never probe above gitRoot; fail the test if we do
const dir = path.dirname(p)
if (!dir.startsWith(gitRoot)) {
throw new Error(`access() called above git root: ${p}`)
}
// Simulate wrapper only at the git root
if (p === path.join(gitRoot, 'mvnw')) {
return
}
const err = new Error('ENOENT')
err.code = 'ENOENT'
throw err
},
},
'../../src/command.js': {
invokeCommand,
},
})
await discoverMavenModules(workspaceRoot)
expect(invokeCommand.called).to.be.true
const [binary] = invokeCommand.firstCall.args
expect(path.dirname(binary)).to.equal(gitRoot)
expect(path.basename(binary)).to.equal(process.platform === 'win32' ? 'mvnw.cmd' : 'mvnw')
})
test('falls back to mvn when mvnw is preferred but not found', async () => {
const root = path.resolve('test/providers/tst_manifests/maven/maven_no_wrapper')
const invokeCommand = sinon.spy(async () => ({
stdout: '',
stderr: '',
status: 0,
}))
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getWrapperPreference: () => 'mvnw',
},
'../../src/git.js': {
getGitRootDir: async () => root,
},
'fs/promises': {
access: async () => {
const err = new Error('ENOENT')
err.code = 'ENOENT'
throw err
},
},
'../../src/command.js': {
invokeCommand,
},
})
await discoverMavenModules(root)
expect(invokeCommand.called).to.be.true
const [binary] = invokeCommand.firstCall.args
expect(path.basename(binary)).to.equal('mvn')
})
```
1. Ensure `sinon`, `path`, and `esmock` are imported at the top of `test/providers/workspace.test.js` if they are not already:
- `import sinon from 'sinon'`
- `import path from 'path'`
- `import esmock from 'esmock'`
2. Adjust mocked module paths (`'../../src/git.js'`, `'../../src/command.js'`, and `'fs/promises'`) to match the actual locations and names used in your codebase if they differ.
3. Create (or point to) suitable fixture directories for:
- `test/providers/tst_manifests/maven/maven_wrapper_root`
- `test/providers/tst_manifests/maven/maven_wrapper_nested`
- `test/providers/tst_manifests/maven/maven_no_wrapper`
Each fixture only needs a minimal `pom.xml`; the filesystem presence of `mvnw` is simulated by the `fs/promises.access` mocks, so you do not need real wrapper scripts.
4. If `discoverMavenModules` uses a different command/invocation helper than `'../../src/command.js'`/`invokeCommand`, update the mocked module accordingly so the tests can observe the resolved Maven binary.
</issue_to_address>
### Comment 3
<location path="test/providers/workspace.test.js" line_range="199-214" />
<code_context>
+ expect(result).to.have.lengthOf(0)
+ })
+
+ test('returns root pom only when mvn reports no modules', async () => {
+ const root = path.resolve('test/providers/tst_manifests/maven/maven_no_modules')
+ const { discoverMavenModules } = await esmock('../../src/workspace.js', {
+ '../../src/tools.js': {
+ getCustom: () => null,
+ getCustomPath: () => 'mvn',
+ getGitRootDir: () => null,
+ getWrapperPreference: () => false,
+ invokeCommand: () => Buffer.from('null'),
+ },
+ })
+ const result = await discoverMavenModules(root)
+ expect(result).to.be.an('array')
+ expect(result).to.have.lengthOf(1)
+ expect(result[0]).to.equal(path.join(root, 'pom.xml'))
+ })
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for additional mvn output shapes (e.g. empty list `[]` and extra whitespace/log noise)
This test already covers the `'null'` output, but `parseMavenModuleList` also handles other mvn output variants. Please add cases where `invokeCommand` returns:
- `'[]'` (empty list) to confirm it is treated as “no modules”, and
- output with leading/trailing whitespace and extra Maven log lines (e.g., warnings before/after the `[module-a, module-b]` line).
These will document and lock in how the parser should behave for these shapes of output.
```suggestion
test('returns root pom only when mvn reports no modules', async () => {
const root = path.resolve('test/providers/tst_manifests/maven/maven_no_modules')
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getGitRootDir: () => null,
getWrapperPreference: () => false,
invokeCommand: () => Buffer.from('null'),
},
})
const result = await discoverMavenModules(root)
expect(result).to.be.an('array')
expect(result).to.have.lengthOf(1)
expect(result[0]).to.equal(path.join(root, 'pom.xml'))
})
test('returns root pom only when mvn reports empty module list []', async () => {
const root = path.resolve('test/providers/tst_manifests/maven/maven_no_modules')
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getGitRootDir: () => null,
getWrapperPreference: () => false,
// Simulate mvn -pl output as an empty JSON-like list
invokeCommand: () => Buffer.from('[]'),
},
})
const result = await discoverMavenModules(root)
expect(result).to.be.an('array')
expect(result).to.have.lengthOf(1)
expect(result[0]).to.equal(path.join(root, 'pom.xml'))
})
test('returns root and module poms when mvn output includes log noise and module list', async () => {
const root = path.resolve('test/providers/tst_manifests/maven')
const { discoverMavenModules } = await esmock('../../src/workspace.js', {
'../../src/tools.js': {
getCustom: () => null,
getCustomPath: () => 'mvn',
getGitRootDir: () => null,
getWrapperPreference: () => false,
// Simulate noisy Maven output with warnings and the module list line
invokeCommand: () =>
Buffer.from(
[
'[WARNING] Some deprecation warning',
'',
'[INFO] Scanning for projects...',
'[module-a, module-b]',
'[INFO] BUILD SUCCESS',
'',
].join('\n'),
),
},
})
const result = await discoverMavenModules(root)
expect(result).to.be.an('array')
// Expect root pom plus the two module poms
const expectedRootPom = path.join(root, 'pom.xml')
const expectedModuleAPom = path.join(root, 'module-a', 'pom.xml')
const expectedModuleBPom = path.join(root, 'module-b', 'pom.xml')
expect(result).to.include(expectedRootPom)
expect(result).to.include(expectedModuleAPom)
expect(result).to.include(expectedModuleBPom)
})
```
</issue_to_address>
### Comment 4
<location path="test/providers/workspace.test.js" line_range="290-299" />
<code_context>
+ test('excludes paths matching workspaceDiscoveryIgnore', async () => {
</code_context>
<issue_to_address>
**suggestion (testing):** Extend ignore-pattern tests to also assert that default patterns like `**/target/**` are honored
Since this test already validates custom `workspaceDiscoveryIgnore` patterns for Maven, consider adding a `pom.xml` under a `target` directory in the Maven fixtures and asserting it is excluded even without a custom `workspaceDiscoveryIgnore`. That way the default `**/target/**` behavior is also exercised for the Maven ecosystem.
Suggested implementation:
```javascript
invokeCommand: (bin, args) => {
const pomArg = args.find((a, i) => args[i - 1] === '-f')
// Ensure default ignore patterns like **/target/** are honored:
// if a pom.xml lives under a target/ directory, discoverMavenModules
// should never attempt to invoke Maven with it.
if (pomArg && pomArg.includes(`${path.sep}target${path.sep}`)) {
throw new Error('discoverMavenModules should ignore pom.xml files under target/ by default')
}
if (pomArg && (pomArg.includes('module-a') || pomArg.includes('module-b'))) {
```
To fully implement the review suggestion, the Maven test fixtures should also include a `pom.xml` under a `target` directory (for example: `test/providers/tst_manifests/maven/maven_multi_module/target/pom.xml` or a similar nested structure). With that fixture in place, this test will fail if `discoverMavenModules` does not apply the default `**/target/**` ignore pattern, because the `invokeCommand` mock will be hit with a `-f` argument pointing at the `target/pom.xml`.
</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 for TC-4259 (commit 0e5fa2b)
Acceptance Criteria Details
Overall: WARNScope Containment and Commit Traceability have minor warnings. The scope deviation is justified by review-driven refactoring. All functional criteria and CI checks pass. This comment was AI-generated by sdlc-workflow/verify-pr v0.5.11. |
ruromero
requested changes
May 6, 2026
ruromero
left a comment
Collaborator
There was a problem hiding this comment.
Only 2 observations, the rest looks fine
ruromero
previously approved these changes
May 6, 2026
Add `discoverMavenModules()` to discover all pom.xml manifest paths in Maven multi-module projects. Uses `mvn help:evaluate -Dexpression=project.modules` to list declared modules, with recursive traversal for nested aggregators. Supports Maven wrapper (mvnw) via `TRUSTIFY_DA_PREFER_MVNW` preference, reusing the same traversal pattern as provider-level wrapper detection. Adds Maven detection (`pom.xml` presence) to `detectWorkspaceManifests()` between Cargo and JavaScript in the ecosystem detection order. Also adds `**/target/**` to the default workspace discovery ignore patterns. Implements TC-4259 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Assisted-by: Claude Code
Address review feedback: provider-specific discovery functions don't belong in workspace.js. Extract traverseForWrapper to tools.js as shared utility, update base_java.js to delegate to it, and move all Maven workspace discovery functions (discoverMavenModules, resolveMavenBinary, collectMavenModules, listMavenModules, parseMavenModuleList) to their provider file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The refactored selectToolBinary call was missing the wrapperName argument, causing path.join to receive undefined. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
traverseForWrapper was extracted from base_java.js but the normalizePath call (lowercases paths on Windows for case-insensitive comparison) was left behind. Move normalizePath into tools.js and call it inside traverseForWrapper so all callers get normalization automatically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract a reusable resolveBinary(globalBinary, localWrapper, startDir, opts) into tools.js and use it from discoverMavenModules, eliminating the one-off resolveMavenBinary function that duplicated selectToolBinary logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…covery Replace the standalone resolveBinary function with Java_maven's selectToolBinary, which also runs a --version smoke test to verify the binary is executable. Gracefully fall back to returning only the root pom.xml if Maven is not available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parseMavenModuleList was expecting Java List.toString() format ([module-a, module-b]) but Maven 3.9.x with -DforceStdout produces XML (<strings><string>module-a</string>...</strings>). The empty- modules check also expected 'null' but real Maven returns <modules/>. Replace invokeCommand mocks in workspace tests with real Maven invocations so the parser is exercised against actual tool output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e test Replace process.env.TRUSTIFY_DA_MVN_PATH save/restore pattern with esmock mocking of invokeCommand to simulate missing mvn binary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
XMLParser is already imported in the file — reuse it instead of a hand-rolled regex to parse Maven's <strings><string>…</string></strings> output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ruromero
approved these changes
May 6, 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
discoverMavenModules()function to discover all pom.xml manifest paths in Maven multi-module projects usingmvn help:evaluateTRUSTIFY_DA_PREFER_MVNWpreferencedetectWorkspaceManifests()between Cargo and JavaScript**/target/**to default workspace discovery ignore patternsTest plan
invokeCommandmvnfailsworkspaceDiscoveryIgnorefilteringImplements TC-4259
🤖 Generated with Claude Code
Summary by Sourcery
Add Maven multi-module workspace discovery and integrate Maven projects into automatic workspace manifest detection.
New Features:
Enhancements:
Tests: