Repository navigation
feat(extract): fix cross-module connection detection regression (#269) - #270
Conversation
Split connection detection into two phases: per-module source-scanning (AST strategies needing a Project) and cross-module metadata-matching (subscribe/publish strategies needing all components). Remove dangerous AST fallback in detectPublishConnections (SD-001 violation) — missing publishedEventType metadata now fails fast (strict) or returns _uncertain (lenient). Co-Authored-By: Claude Opus 4.6 <[email protected]>
Deploying living-architecture with
|
| Latest commit: |
9826579
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ddb91cd2.living-architecture.pages.dev |
| Branch Preview URL: | https://issue-269-riviere-extract-mu.living-architecture.pages.dev |
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughSplits detection into per-module (source-scanning) and cross-module (metadata-matching) phases, adds detectPerModuleConnections and detectCrossModuleConnections, aggregates and deduplicates links, and changes detectPublishConnections to metadata-driven resolution without a Project parameter. Timings are restructured per-phase. Changes
Sequence DiagramsequenceDiagram
participant CLI as CLI: detect-connections-per-module
participant PM as Per-Module Detection
participant CM as Cross-Module Detection
participant DD as Deduplication
CLI->>CLI: iterate ModuleContexts
loop per-module
CLI->>PM: detectPerModuleConnections(project, moduleComponents, options, matchesGlob)
PM->>PM: buildCallGraph + detectConfigurableConnections
PM-->>CLI: {links: [sync/configurable], timings: {callGraphMs, configurableMs, setupMs}}
CLI->>CLI: accumulate links & timings
end
CLI->>CM: detectCrossModuleConnections(allComponents, {allowIncomplete, repository})
CM->>CM: detectPublishConnections(allComponents)
CM->>CM: detectSubscribeConnections(allComponents)
CM-->>CLI: {links: [publish, subscribe], timings: {asyncDetectionMs}}
CLI->>DD: deduplicateCrossStrategy(accumulatedLinks + crossLinks)
DD-->>CLI: {links: deduplicated}
CLI-->>CLI: return PerModuleConnectionResult (links, timings[])
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.16.0)packages/riviere-cli/src/features/extract/domain/detect-connections-per-module.spec.ts┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/async-detection/detect-publish-connections.spec.ts┌──────────────┐ �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m 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. Comment |
There was a problem hiding this comment.
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)
packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/async-detection/detect-publish-connections.spec.ts (1)
200-218:⚠️ Potential issue | 🟡 MinorCase-sensitivity test assertion is weaker than the test title claims.
The title states "uses exact case-sensitive matching for publishedEventType to eventName", but the assertion only checks that
_uncertaincontains'orderplaced'. Contrast with the analogous lenient/no-match test (lines 80–87) that also assertssource,target: '_unresolved', andtype: 'async'. An implementation that returned a resolved link with_uncertainset would still pass this test, which contradicts the claim in the title.✅ Proposed fix — strengthen the assertion to match the claim
expect(result).toStrictEqual([ - expect.objectContaining({ _uncertain: expect.stringContaining('orderplaced') }), + expect.objectContaining({ + source: 'orders:eventPublisher:CasePublisher', + target: '_unresolved', + type: 'async', + _uncertain: expect.stringContaining('orderplaced'), + }), ])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/async-detection/detect-publish-connections.spec.ts` around lines 200 - 218, The test title claims case-sensitive matching but the assertion only checks _uncertain contains 'orderplaced', so strengthen the assertion to mirror the analogous lenient/no-match test: assert that detectPublishConnections returns a single unresolved async link with source equal to the event fixture (the variable event), target equal to '_unresolved', type equal to 'async', and still assert _uncertain contains 'orderplaced'; update the expectation to use expect.objectContaining with those fields against the result array returned by detectPublishConnections.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/riviere-cli/src/features/extract/domain/detect-connections-per-module.spec.ts`:
- Around line 34-47: The createModule helper currently returns a full Module
with many `{ notUsed: true }` placeholders which is fragile if the Module type
gains required fields; update createModule to return a Partial<Module> or
explicitly cast the result (e.g. `return { name, path, glob, api: {}, ... } as
Module` or change the function signature to `createModule(name: string):
Partial<Module>`), or extract a shared test-fixture builder that constructs only
the fields tests rely on (refer to the createModule function and the Module
type) so type changes will surface in tests rather than silently diverge.
- Around line 88-117: The test for detectConnectionsPerModule currently only
asserts mockDetectPerModule call count; update it to also assert the call
arguments to ensure per-module filtering: after invoking
detectConnectionsPerModule, add expectations that mockDetectPerModule was called
with ordersCtx.project and [orderComp] for the first call and with
shippingCtx.project and [shippingComp] for the second call (use
expect.any(Object) for the options param and the existing mockMatchesGlob for
the glob matcher) so createModuleContext/createComponent pairing is validated.
In
`@packages/riviere-cli/src/features/extract/domain/detect-connections-per-module.ts`:
- Around line 44-50: The ConnectionTimings shape is being used for two different
semantics (per-module vs cross-module) which causes unused/zeroed fields;
introduce a discriminated union (e.g., type ConnectionTiming = PerModuleTiming |
CrossModuleTiming) or two separate types and add a discriminator field like
kind: 'perModule'|'crossModule', then update the creation sites (the
timings.push call that currently sets asyncDetectionMs=0 and the cross-module
push sites that zero callGraphMs/configurableMs/setupMs) to construct the
appropriate variant and update any consumers to narrow on the discriminator
before reading fields (search for usages of ConnectionTimings, timings.push, and
any code reading asyncDetectionMs/callGraphMs/configurableMs/setupMs).
In
`@packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/async-detection/detect-publish-connections.spec.ts`:
- Around line 55-67: Update the strict-mode tests that currently only assert the
error type to also assert an identifying message substring: for the test titled
"throws ConnectionDetectionError in strict mode when publishedEventType matches
no event" (and the similar ambiguous-match and missing-metadata tests), change
the expectation from just .toThrow(ConnectionDetectionError) to assert both the
type and message (e.g., using expect(() =>
detectPublishConnections(...)).toThrowError(new ConnectionDetectionError(...))
or expect(() => ...).toThrowError(/substring-of-expected-message/) so the test
verifies the ConnectionDetectionError contains a specific identifying message;
reference detectPublishConnections and ConnectionDetectionError when making the
change.
---
Outside diff comments:
In
`@packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/async-detection/detect-publish-connections.spec.ts`:
- Around line 200-218: The test title claims case-sensitive matching but the
assertion only checks _uncertain contains 'orderplaced', so strengthen the
assertion to mirror the analogous lenient/no-match test: assert that
detectPublishConnections returns a single unresolved async link with source
equal to the event fixture (the variable event), target equal to '_unresolved',
type equal to 'async', and still assert _uncertain contains 'orderplaced';
update the expectation to use expect.objectContaining with those fields against
the result array returned by detectPublishConnections.
- Add per-module component filtering assertions (nthCalledWith) - Verify error messages in strict-mode throw assertions - Remove unused ConnectionDetectionError import Co-Authored-By: Claude Opus 4.6 <[email protected]>
|



Closes #269
Problem
riviere extractfails to detect 15 cross-module event subscription connections. Regression from PR #267 (per-module ts-morph Projects).Verified by
npm run verify:connectionsin ecommerce-demo-app:Root Cause
File:
packages/riviere-cli/src/features/extract/domain/detect-connections-per-module.tsdetectConnections(packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/detect-connections.ts) bundles 4 strategies. Two do cross-module metadata matching and break when given only one module's components:buildCallGraph(line 77)detectConfigurableConnections(line 96)detectSubscribeConnections(line 89)handler.metadata['subscribedEvents']↔event.metadata['eventName']detectPublishConnections(line 85)publisher.metadata['publishedEventType']↔event.metadata['eventName']Subscribe: when processing the
ordersmodule, events likeShipmentDispatched(shipping module) aren't in scope →_unresolved.Publish: same cross-module matching problem. Currently hidden in ecommerce-demo-app because publishers and events are co-located within modules, but any codebase with cross-module publish→event relationships will break.
Implementation
Step 1: Create
detectPerModuleConnectionsin riviere-extract-tsFile:
packages/riviere-extract-ts/src/features/extraction/domain/connection-detection/detect-connections.tsNew function that runs only source-scanning strategies (need a Project, work per-module):
buildCallGraphdetectConfigurableConnectionsStep 2: Create
detectCrossModuleConnectionsin riviere-extract-tsFile: same as Step 1
New function that runs metadata-matching strategies (need all components, run once after per-module loop):
detectSubscribeConnectionsdetectPublishConnectionsBoth are pure metadata matching — no AST/Project needed. The previous AST fallback in
detectPublishConnectionswas removed (SD-001 violation: fail-fast over silent fallbacks). WhenpublishedEventTypemetadata is missing, strict mode throwsConnectionDetectionErrorand lenient mode returns an_uncertainlink.Inside this function:
detectPublishConnectionsis called once with all componentsdetectSubscribeConnectionsis called once with all componentsStep 3: Update
detectConnectionsPerModulein riviere-cliFile:
packages/riviere-cli/src/features/extract/domain/detect-connections-per-module.tsTwo-phase orchestration:
Step 4: Export new functions from riviere-extract-ts
File:
packages/riviere-extract-ts/src/index.tsExport
detectPerModuleConnectionsanddetectCrossModuleConnections. Keep existingdetectConnectionsexport for backward compatibility.Step 5: Tests
Unit tests in riviere-extract-ts (new spec files or extend
detect-connections.spec.ts):detectPerModuleConnections— produces call graph and configurable links only, no async linksdetectCrossModuleConnections— produces subscribe and publish links, resolves cross-module matches where handler is in module A and event is in module BpublishedEventTypemetadata (strict throws, lenient returns_uncertain)Integration test in riviere-cli (
detect-connections-per-module.spec.ts— new):Files to Modify
packages/riviere-extract-ts/.../detect-connections.tsdetectPerModuleConnectionsanddetectCrossModuleConnectionspackages/riviere-extract-ts/src/index.tspackages/riviere-cli/.../detect-connections-per-module.tspackages/riviere-extract-ts/.../detect-connections.spec.tspackages/riviere-cli/.../detect-connections-per-module.spec.ts(new)Acceptance Criteria
_unresolvedor_uncertain)--allow-incompleteflag still worksnpm run verify:connectionsin ecommerce-demo-app shows exactly 4 remaining failures: 2 missing HTTP links + 2 missing external links (these are out of scope for this issue)Verification
Amendment
AST fallback in detectPublishConnections was intentionally removed (SD-001 violation - fail-fast over silent fallbacks). When publishedEventType metadata is missing, strict mode throws ConnectionDetectionError and lenient mode returns _uncertain link. This eliminates the need for Map<string, Project> in detectCrossModuleConnections - it now takes only allComponents. The ecommerce-demo-app verify:connections check requires manual post-merge verification.
Summary by CodeRabbit
New Features
Tests
Chores