Skip to content

feat(core): add subflow resolution module and UnresolvedSubflow rule#292

Open
RubenHalman wants to merge 2 commits intomainfrom
subflow-resolution
Open

feat(core): add subflow resolution module and UnresolvedSubflow rule#292
RubenHalman wants to merge 2 commits intomainfrom
subflow-resolution

Conversation

@RubenHalman
Copy link
Member

@RubenHalman RubenHalman linked an issue Jan 25, 2026 that may be closed by this pull request
- Add SubflowResolver to detect DML operations in subflows called within loops
- Add UnresolvedSubflow rule to warn when subflows cannot be resolved
- Add FileSystemResolver for CLI and VSX packages
- Add example flows and comprehensive tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@RubenHalman RubenHalman closed this Feb 1, 2026
@RubenHalman RubenHalman reopened this Feb 1, 2026
@RubenHalman RubenHalman closed this Feb 1, 2026
@RubenHalman RubenHalman reopened this Feb 1, 2026
@diffray diffray bot added the diffray-review-started diffray review status: started label Feb 1, 2026
@diffray
Copy link

diffray bot commented Feb 1, 2026

Changes Summary

This PR introduces a comprehensive subflow resolution system that enables the Lightning Flow Scanner to analyze flows that call other flows (subflows). The implementation includes a pluggable resolver architecture with filesystem-based and preloaded resolvers, recursive subflow chain detection for DML-in-loop violations, and a new 'unresolved-subflow' system rule to detect missing flow references.

Type: feature

Components Affected: core-scanning-engine, subflow-resolution-system, rule-registry, loop-violation-detection, cli-package, vsx-package, documentation

Files Changed
File Summary Change Impact
.github/docs/SUBFLOW_RESOLUTION.md Comprehensive documentation covering subflow resolution usage across all environments (CLI, VSX, browser, MCP, GitHub Action) 🟢
packages/core/src/main/libs/SubflowResolver.ts Core SubflowResolver interface with NoOpResolver and PreloadedResolver implementations for browser/API environments 🔴
packages/cli/src/libs/FileSystemResolver.ts FileSystemResolver implementation for Node.js environments with lazy/eager loading and glob-based flow discovery 🔴
packages/vsx/src/libs/FileSystemResolver.ts VSX-specific FileSystemResolver implementation for VS Code extension 🟡
packages/core/src/main/models/LoopRuleCommon.ts Enhanced to recursively detect violations in subflow call chains within loops, tracking visited flows to prevent cycles ✏️ 🔴
packages/core/src/main/rules/UnresolvedSubflow.ts New system rule to detect subflow references that cannot be resolved (missing flows, typos, inaccessible managed packages) 🟡
packages/core/src/main/models/Flow.ts Added helper methods: hasSubflows(), getSubflowNames(), getSubflowNodes() for subflow discovery ✏️ 🟡
packages/core/src/main/config/RuleRegistry.ts Updated to register UnresolvedSubflow rule and support system rule filtering ✏️ 🟡
packages/core/src/index.ts Exported new SubflowResolver types and implementations (NoOpResolver, PreloadedResolver, interfaces) ✏️ 🟡
packages/core/src/main/libs/ScanFlows.ts Plumbed subflowResolver from IRulesConfig through to rule checking logic ✏️ 🟡
packages/core/src/main/interfaces/IRulesConfig.ts Added subflowResolver optional property to IRulesConfig interface ✏️ 🟢
packages/core/tests/SubflowResolver.test.ts Unit tests for NoOpResolver and PreloadedResolver implementations 🟢
packages/core/tests/SubflowDMLInLoop.test.ts Integration tests verifying DML-in-loop detection through subflow chains 🟢
packages/core/tests/UnresolvedSubflow.test.ts Tests for UnresolvedSubflow rule covering missing flows, managed packages, and error cases 🟢
...solution/Parent_With_Subflow_Loop.flow-meta.xml Example parent flow that loops through accounts and calls a subflow 🟢
...subflow-resolution/Child_With_DML.flow-meta.xml Example child flow containing DML operations 🟢
...ng/subflow-resolution/Middle_Flow.flow-meta.xml Example middle flow for testing nested subflow chains 🟢
...lution/Parent_With_Nested_Subflow.flow-meta.xml Example parent flow testing nested subflow resolution (A→B→C chains) 🟢
packages/cli/src/index.ts Exported FileSystemResolver for CLI usage ✏️ 🟢
packages/cli/test/commands/flow/scan.e2e.test.ts Minor test adjustments for subflow resolution integration ✏️ 🟢
packages/core/src/main/libs/GetRuleDefinitions.ts Updated to pass subflowResolver option to rule instances ✏️ 🟢
packages/core/src/main/models/RuleInfo.ts Added support for system rule category ✏️ 🟢
Architecture Impact
  • New Patterns: Strategy Pattern for SubflowResolver implementations (NoOpResolver, PreloadedResolver, FileSystemResolver), Visitor Pattern extension for recursive subflow traversal in LoopRuleCommon, Lazy loading pattern in FileSystemResolver with optional eager mode, Caching layer in resolvers to prevent redundant file I/O
  • Dependencies: Uses glob package for filesystem flow discovery in FileSystemResolver
  • Coupling: Introduces optional coupling between rules and SubflowResolver through IRulesConfig. Rules can optionally use resolver for enhanced analysis but gracefully degrade when not provided. LoopRuleCommon now depends on SubflowResolver for recursive violation detection.

Risk Areas: Recursive subflow resolution with cycle detection - needs thorough testing for deeply nested and circular flow references, FileSystemResolver glob pattern matching and file indexing - potential performance impact on large codebases, Memory usage with PreloadedResolver when loading many flows in browser/MCP contexts, Synchronous getSync() API on resolvers - FileSystemResolver requires pre-loading via eager mode for this to work, Error handling when subflows fail to parse or are inaccessible (managed packages), Cache invalidation in FileSystemResolver when flows are modified during long-running sessions

Suggestions
  • Consider adding telemetry/metrics for subflow resolution performance (cache hit rate, resolution time)
  • Add configuration option to limit recursion depth for subflow chains to prevent stack overflow
  • Consider lazy indexing in FileSystemResolver to defer glob scanning until first resolution request
  • Add integration tests for edge cases: circular references with 3+ flows, managed package flows, symlinks
  • Document performance implications of eager vs lazy loading in FileSystemResolver
  • Consider adding a warm-up API for FileSystemResolver to pre-load commonly referenced subflows

🔗 See progress

Full review in progress... | Powered by diffray

@RubenHalman RubenHalman closed this Feb 1, 2026
@RubenHalman RubenHalman reopened this Feb 1, 2026
Comment on lines 188 to 250
async resolve(flowName: string, context?: SubflowResolutionContext): Promise<ResolvedSubflow> {
// If we have additional search paths from context, rebuild index
if (context?.searchPaths && context.searchPaths.length > 0) {
const newPaths = context.searchPaths.filter(
(p) => !this.options.searchPaths.includes(p)
);
if (newPaths.length > 0) {
this.options.searchPaths.push(...newPaths);
await this.buildIndex();
}
}

const isManaged = this.managedFlows.has(flowName);

// Skip managed flows if configured
if (isManaged && this.options.skipManaged) {
return {
flowName,
flow: undefined,
isManaged: true,
error: "Managed package flow - skipped",
};
}

const flow = await this.loadFlow(flowName);

return {
flowName,
flow,
isManaged,
error: flow ? undefined : `Flow "${flowName}" not found in search paths`,
};
}

/**
* Resolve multiple subflows at once.
*/
async resolveMany(
flowNames: string[],
context?: SubflowResolutionContext
): Promise<Map<string, ResolvedSubflow>> {
const results = new Map<string, ResolvedSubflow>();

// Rebuild index once if needed
if (context?.searchPaths && context.searchPaths.length > 0) {
const newPaths = context.searchPaths.filter(
(p) => !this.options.searchPaths.includes(p)
);
if (newPaths.length > 0) {
this.options.searchPaths.push(...newPaths);
await this.buildIndex();
}
}

// Load all in parallel
await Promise.all(
flowNames.map(async (name) => {
results.set(name, await this.resolve(name));
})
);

return results;
}
Copy link

@diffray diffray bot Feb 1, 2026

Choose a reason for hiding this comment

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

🟡 MEDIUM - Duplicated search path mutation logic across resolve methods
Agent: architecture

Category: quality

Description:
The logic for handling context.searchPaths and rebuilding the index is duplicated in both resolve() (lines 190-197) and resolveMany() (lines 232-239). This code duplication violates DRY principles.

Suggestion:
Extract the common search path handling logic into a private helper method like ensureSearchPaths(context?: SubflowResolutionContext): Promise. Both resolve() and resolveMany() should call this helper at the beginning.

Confidence: 85%
Rule: arch_extract_helper_functions
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray


Fixed in e340695

Comment on lines +244 to +246
flowNames.map(async (name) => {
results.set(name, await this.resolve(name));
})
Copy link

@diffray diffray bot Feb 1, 2026

Choose a reason for hiding this comment

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

🟡 MEDIUM - resolveMany calls resolve without passing context
Agent: architecture

Category: quality

Description:
The resolveMany method rebuilds the index once (lines 232-239), but then calls this.resolve(name) without passing context. Since resolve() checks context.searchPaths again at the start, this causes the rebuild check to run again (though it won't rebuild since newPaths would be empty). More importantly, if there are any edge cases with context usage, passing it would be more defensive.

Suggestion:
Pass context to each resolve call: results.set(name, await this.resolve(name, context)); to ensure consistent behavior and avoid redundant checks.

Confidence: 70%
Rule: arch_inappropriate_algorithm_choice
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray


Fixed in e340695

Comment on lines 191 to 193
const newPaths = context.searchPaths.filter(
(p) => !this.options.searchPaths.includes(p)
);
Copy link

@diffray diffray bot Feb 1, 2026

Choose a reason for hiding this comment

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

🟡 MEDIUM - Linear search in array for path deduplication
Agent: performance

Category: performance

Description:
The code uses Array.includes() to check if paths exist in this.options.searchPaths. With many search paths, this becomes O(n) per check. During resolution with many context paths, this could compound.

Suggestion:
Convert searchPaths to a Set for O(1) lookup when filtering new paths: const existingPaths = new Set(this.options.searchPaths); const newPaths = context.searchPaths.filter(p => !existingPaths.has(p));

Confidence: 65%
Rule: perf_linear_search_to_set
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray


Fixed in e340695

Comment on lines 160 to 168
if (context?.searchPaths && context.searchPaths.length > 0) {
const newPaths = context.searchPaths.filter(
(p) => !this.options.searchPaths.includes(p)
);
if (newPaths.length > 0) {
this.options.searchPaths.push(...newPaths);
await this.buildIndex();
}
}
Copy link

@diffray diffray bot Feb 1, 2026

Choose a reason for hiding this comment

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

🟡 MEDIUM - Potential path traversal via context.searchPaths
Agent: security

Category: security

Description:
The resolve() method accepts searchPaths from SubflowResolutionContext and adds them to the resolver's search paths without validation. If an attacker can control the context parameter, they could inject paths outside intended directories.

Suggestion:
Validate and sanitize context.searchPaths before adding them. Use path.resolve() to normalize paths and verify they are within allowed directories.

Confidence: 60%
Rule: sec_semgrep_scan
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray


Fixed in e340695

Comment on lines 190 to 198
if (context?.searchPaths && context.searchPaths.length > 0) {
const newPaths = context.searchPaths.filter(
(p) => !this.options.searchPaths.includes(p)
);
if (newPaths.length > 0) {
this.options.searchPaths.push(...newPaths);
await this.buildIndex();
}
}
Copy link

@diffray diffray bot Feb 1, 2026

Choose a reason for hiding this comment

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

🟡 MEDIUM - Potential path traversal via context.searchPaths
Agent: security

Category: security

Description:
The resolve() method accepts searchPaths from SubflowResolutionContext and adds them to the resolver's search paths without validation. If an attacker can control the context parameter, they could inject paths outside intended directories.

Suggestion:
Validate and sanitize context.searchPaths before adding them. Use path.resolve() to normalize paths and verify they are within allowed directories.

Confidence: 60%
Rule: sec_semgrep_scan
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray


Fixed in e340695

Comment on lines 103 to +115
}
return statementsInLoops;
}

/**
* Find subflows called from within loops and check if they contain violating statements.
* Recursively checks nested subflow chains.
* Uses synchronous resolution via getSync().
*/
private findStatementsInSubflowsSync(
flow: Flow,
loopElements: FlowNode[],
resolver: SubflowResolver
Copy link

Choose a reason for hiding this comment

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

🟠 HIGH - Quadratic loops - O(n²) complexity
Agent: performance

Category: performance

Description:
The method findStatementsInLoops contains nested loops iterating over loopElements and then loopElems for each loop element. With many loops and elements inside each loop, this creates O(n²) complexity.

Suggestion:
Consider caching loop elements or restructuring to collect all loop elements first, then filter by statement type in a single pass.

Confidence: 85%
Rule: perf_quadratic_loops
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +112 to +147
private findStatementsInSubflowsSync(
flow: Flow,
loopElements: FlowNode[],
resolver: SubflowResolver
): SubflowViolation[] {
const violations: SubflowViolation[] = [];

// Check if resolver supports synchronous access
if (!resolver.getSync) {
return violations;
}

for (const loopElement of loopElements) {
const loopElems = flow.graph.getLoopElements(loopElement.name);

for (const elemName of loopElems) {
const node = flow.graph.getNode(elemName);
if (!node || node.subtype !== "subflows") continue;

const subflowName = node.flowName;
if (!subflowName) continue;

// Recursively find violations in this subflow and its children
const subflowViolations = this.findViolationsInSubflowRecursive(
subflowName,
node,
resolver,
new Set<string>() // Track visited flows to prevent infinite loops
);

violations.push(...subflowViolations);
}
}

return violations;
}
Copy link

Choose a reason for hiding this comment

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

🟡 MEDIUM - Complex recursive subflow traversal should be extracted
Agent: architecture

Category: quality

Description:
The findStatementsInSubflowsSync and findViolationsInSubflowRecursive methods implement complex graph traversal logic with cycle detection, caching, and nested recursion. This logic is embedded in LoopRuleCommon, making it responsible for both loop detection AND graph traversal.

Suggestion:
Extract the subflow graph traversal logic into a separate SubflowTraverser class. LoopRuleCommon should use this traverser as a dependency.

Confidence: 75%
Rule: arch_srp_violation
Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray
Copy link

diffray bot commented Feb 1, 2026

Review Summary

Validated 95 issues: 28 kept, 67 filtered

Issues Found: 28

💬 See 27 individual line comment(s) for details.

📊 21 unique issue type(s) across 28 location(s)

📋 Full issue list (click to expand)

🟠 HIGH - Quadratic loops - O(n²) complexity

Agent: performance

Category: performance

File: packages/core/src/main/models/LoopRuleCommon.ts:103-115

Description: The method findStatementsInLoops contains nested loops iterating over loopElements and then loopElems for each loop element. With many loops and elements inside each loop, this creates O(n²) complexity.

Suggestion: Consider caching loop elements or restructuring to collect all loop elements first, then filter by statement type in a single pass.

Confidence: 85%

Rule: perf_quadratic_loops


🟠 HIGH - NEW FileSystemResolver completely missing test coverage (3 occurrences)

Agent: testing

Category: quality

📍 View all locations
File Description Suggestion Confidence
packages/cli/src/libs/FileSystemResolver.ts:1-308 The FileSystemResolver class (308 lines) is a new implementation with NO test coverage. This is a cr... Create comprehensive test file packages/cli/test/libs/FileSystemResolver.test.ts covering: 1) Buildi... 95%
packages/vsx/src/libs/FileSystemResolver.ts:1-254 The VSX FileSystemResolver class (254 lines) is a new implementation with NO test coverage. It's nea... Create comprehensive test file for VSX FileSystemResolver covering all resolver methods and VS Code ... 95%
packages/cli/test/commands/flow/scan.e2e.test.ts:18 The scan.e2e.test.ts tests various scanning features but omits testing FileSystemResolver integratio... Add E2E tests that verify subflow resolution works correctly when scanning flows that reference othe... 75%

Rule: test_coverage_new_functionality


🟡 MEDIUM - Confusing test name contradicts assertion (2 occurrences)

Agent: testing

Category: quality

Why this matters: Misleading test names cause confusion during debugging and can lead to tests being trusted when they don't validate the intended behavior.

📍 View all locations
File Description Suggestion Confidence
packages/core/tests/UnresolvedSubflow.test.ts:126-145 Test named 'should flag nested unresolved subflows' expects 0 violations. The comment explains the b... Rename to 'should not flag parent flow when nested subflow is unresolved in child' to match the actu... 90%
packages/core/tests/SubflowResolver.test.ts:111-120 The resolveMany test verifies individual entries but doesn't check that results.size equals 3 or tha... Add assertion: expect(results.size).toBe(3) to verify complete result set 70%

Rule: test_missing_assertion


🟡 MEDIUM - Missing type annotations on method parameters

Agent: typescript

Category: quality

Why this matters: Missing type annotations bypass TypeScript's type checking and can lead to runtime errors that could have been caught at compile time.

File: packages/core/src/main/models/FlowNode.ts:239

Description: The getConnectors method has parameters 'subtype' and 'element' without type annotations, resulting in implicit 'any' types.

Suggestion: Add explicit type annotations: private getConnectors(subtype: string, element: any): FlowElementConnector[]

Confidence: 90%

Rule: ts_prefer_unknown_over_any


🟡 MEDIUM - Circular reference test only validates one scenario

Agent: testing

Category: quality

Why this matters: Different cycle patterns could have edge cases in the detection algorithm. Testing multiple scenarios ensures robustness.

File: packages/core/tests/SubflowDMLInLoop.test.ts:164-213

Description: The circular reference test only checks Flow_A -> Flow_B -> Flow_A (2-node cycle). It doesn't test self-reference (A -> A) or 3-node cycles (A -> B -> C -> A).

Suggestion: Add test cases for self-referencing flow and 3-node circular chain to ensure cycle detection handles all scenarios

Confidence: 75%

Rule: test_comprehensive_coverage_systematic


🟡 MEDIUM - Duplicated search path mutation logic across resolve methods (2 occurrences)

Agent: architecture

Category: quality

📍 View all locations
File Description Suggestion Confidence
packages/cli/src/libs/FileSystemResolver.ts:188-250 The logic for handling context.searchPaths and rebuilding the index is duplicated in both resolve() ... Extract the common search path handling logic into a private helper method like ensureSearchPaths(co... 85%
packages/core/src/main/models/LoopRuleCommon.ts:84-90 The private methods findLoopElements() (line 84-86) and findLoopEnd() (line 88-90) are defined but n... Remove the unused private methods findLoopElements() and findLoopEnd() to reduce code clutter and ma... 90%

Rule: arch_extract_helper_functions


🟡 MEDIUM - resolveMany calls resolve without passing context

Agent: architecture

Category: quality

File: packages/cli/src/libs/FileSystemResolver.ts:244-246

Description: The resolveMany method rebuilds the index once (lines 232-239), but then calls this.resolve(name) without passing context. Since resolve() checks context.searchPaths again at the start, this causes the rebuild check to run again (though it won't rebuild since newPaths would be empty). More importantly, if there are any edge cases with context usage, passing it would be more defensive.

Suggestion: Pass context to each resolve call: results.set(name, await this.resolve(name, context)); to ensure consistent behavior and avoid redundant checks.

Confidence: 70%

Rule: arch_inappropriate_algorithm_choice


🟡 MEDIUM - Use of 'as any' type assertion bypasses type safety

Agent: react

Category: quality

File: packages/core/src/main/models/LoopRuleCommon.ts:81

Description: The code uses 'as any' to access nested properties noMoreValuesConnector.targetReference, which bypasses TypeScript's type checking. This could hide potential runtime errors if the element structure changes.

Suggestion: Define a proper interface for the element type or use type guards instead of casting to any.

Confidence: 75%

Rule: ts_prefer_specific_types_over_any_unknown_w


🟡 MEDIUM - Live file I/O in test - should use mocks or be marked as integration test

Agent: microservices

Category: quality

File: packages/core/tests/SubflowDMLInLoop.test.ts:13-25

Description: This test file performs live file system operations using the parse() function to read actual XML flow files from disk in the beforeAll() hook. While this is valid for integration testing, it's not clearly marked as such.

Suggestion: Either rename to SubflowDMLInLoop.integration.test.ts to mark it as an integration test, or add a comment explaining why live I/O is needed here.

Confidence: 65%

Rule: gen_no_live_io_in_unit_tests


🟡 MEDIUM - Linear search in array for path deduplication

Agent: performance

Category: performance

File: packages/cli/src/libs/FileSystemResolver.ts:191-193

Description: The code uses Array.includes() to check if paths exist in this.options.searchPaths. With many search paths, this becomes O(n) per check. During resolution with many context paths, this could compound.

Suggestion: Convert searchPaths to a Set for O(1) lookup when filtering new paths: const existingPaths = new Set(this.options.searchPaths); const newPaths = context.searchPaths.filter(p => !existingPaths.has(p));

Confidence: 65%

Rule: perf_linear_search_to_set


🟡 MEDIUM - Potential path traversal via context.searchPaths (2 occurrences)

Agent: security

Category: security

📍 View all locations
File Description Suggestion Confidence
packages/vsx/src/libs/FileSystemResolver.ts:160-168 The resolve() method accepts searchPaths from SubflowResolutionContext and adds them to the resolver... Validate and sanitize context.searchPaths before adding them. Use path.resolve() to normalize paths ... 60%
packages/cli/src/libs/FileSystemResolver.ts:190-198 The resolve() method accepts searchPaths from SubflowResolutionContext and adds them to the resolver... Validate and sanitize context.searchPaths before adding them. Use path.resolve() to normalize paths ... 60%

Rule: sec_semgrep_scan


🟡 MEDIUM - Complex method with deep nesting

Agent: quality

Category: quality

File: packages/core/src/main/models/LoopRuleCommon.ts:157-219

Description: The findViolationsInSubflowRecursive method is complex with 4+ levels of nesting and spans 62 lines with multiple responsibilities: cycle detection, flow resolution, caching, violation detection, and recursive traversal.

Suggestion: Break this method into smaller focused methods: shouldProcessSubflow(), resolveAndCacheSubflow(), findDirectViolations(), findNestedViolations().

Confidence: 70%

Rule: quality_early_return_instead_nested


🟡 MEDIUM - Complex recursive subflow traversal should be extracted

Agent: architecture

Category: quality

File: packages/core/src/main/models/LoopRuleCommon.ts:112-147

Description: The findStatementsInSubflowsSync and findViolationsInSubflowRecursive methods implement complex graph traversal logic with cycle detection, caching, and nested recursion. This logic is embedded in LoopRuleCommon, making it responsible for both loop detection AND graph traversal.

Suggestion: Extract the subflow graph traversal logic into a separate SubflowTraverser class. LoopRuleCommon should use this traverser as a dependency.

Confidence: 75%

Rule: arch_srp_violation


🟡 MEDIUM - Large function with multiple responsibilities

Agent: architecture

Category: quality

File: packages/core/src/main/libs/ScanFlows.ts:85

Description: The main loop in ScanFlows (lines 74-132) handles multiple responsibilities: flow conversion, rule iteration, configuration merging, execution, message customization, URL generation, line number enrichment, error handling, and caching.

Suggestion: Decompose into smaller functions: processFlow(), executeRule(), enrichRuleResult().

Confidence: 65%

Rule: arch_large_function_decomposition


🟡 MEDIUM - Nested loops with subflow scanning could lead to O(n²) complexity

Agent: performance

Category: performance

File: packages/core/src/main/models/LoopRuleCommon.ts:124-146

Description: The findStatementsInSubflowsSync method has nested loops iterating over loopElements and loopElems, then recursively traversing subflows. However, this is mitigated by: 1) early exit at line 120-122 if resolver lacks getSync, 2) caching at line 179-183, 3) visited set preventing cycles.

Suggestion: The concern is valid but overstated. The caching strategy (resolvedSubflows Map) already mitigates repeated resolutions. Consider documenting the O(loops × elementsPerLoop × uniqueSubflows) complexity.

Confidence: 62%

Rule: perf_expensive_in_loop


🟡 MEDIUM - Missing edge case: PreloadedResolver should test duplicate flow names (3 occurrences)

Agent: testing

Category: quality

📍 View all locations
File Description Suggestion Confidence
packages/core/tests/SubflowResolver.test.ts:36-98 PreloadedResolver tests don't verify behavior when the same flow name is added twice. This is import... Add test case: 'should overwrite flow when adding same name twice' that adds a flow, then adds a dif... 75%
packages/core/src/main/libs/SubflowResolver.ts:161-164 The addFlowFromXml() method creates a Flow from raw XML data but has no tests for what happens if th... Add test cases for: 1) addFlowFromXml with null/undefined xmlData, 2) addFlowFromXml with malformed ... 75%
packages/core/tests/SubflowDMLInLoop.test.ts:145-162 Tests cover 2-level nesting (Parent -> Middle -> Child) but not deeper chains (5+ levels) which coul... Add test case with 5+ levels of subflow nesting to verify both detection and performance characteris... 65%

Rule: test_missing_edge_case_coverage


🟡 MEDIUM - Missing test coverage for SubflowResolutionContext parameter

Agent: testing

Category: quality

File: packages/core/src/main/libs/SubflowResolver.ts:54-70

Description: The SubflowResolutionContext interface defines parentFlow, parentFlowPath, and searchPaths fields, but the core tests never pass a context parameter to resolve() or resolveMany(). The context parameter behavior is completely untested.

Suggestion: Add tests that pass SubflowResolutionContext to verify: 1) PreloadedResolver ignores context (expected), 2) NoOpResolver ignores context (expected), 3) Document which resolvers use which context fields

Confidence: 80%

Rule: test_new_parameter_coverage


🔵 LOW - Trivial test checks instanceof rather than behavior

Agent: testing

Category: quality

Why this matters: Type checks pass even if behavior is broken. Testing behavior ensures the resolver works correctly.

File: packages/core/tests/SubflowResolver.test.ts:167-171

Description: The test only checks instanceof NoOpResolver, not that defaultResolver actually behaves correctly (returns undefined for all resolutions).

Suggestion: Add behavior verification: const result = await defaultResolver.resolve('AnyFlow'); expect(result.flow).toBeUndefined();

Confidence: 65%

Rule: testing_behavior_not_implementation


🔵 LOW - Loop-invariant computation inside recursive function

Agent: performance

Category: performance

File: packages/core/src/main/models/LoopRuleCommon.ts:165

Description: getStatementTypes() is called on every recursive invocation of findViolationsInSubflowRecursive. Since the result doesn't change, it should be computed once and passed as a parameter.

Suggestion: Pass statementTypes as a parameter to avoid repeated calls across recursive invocations.

Confidence: 80%

Rule: perf_cache_expensive_computations


🔵 LOW - Multiple array operations on same data

Agent: performance

Category: performance

File: packages/core/src/main/models/Flow.ts:305-309

Description: The getSubflowNames() method performs multiple array operations in sequence: filter(), map(), filter(), then creates a Set and converts back to array. This creates multiple intermediate arrays.

Suggestion: Combine operations into a single pass using a for loop that populates a Set directly.

Confidence: 70%

Rule: perf_unnecessary_intermediate_allocations


🔵 LOW - Unused private method

Agent: react

Category: quality

File: packages/core/src/main/models/LoopRuleCommon.ts:84-86

Description: The private method 'findLoopElements' is defined but never called. The code at line 39 directly calls flow.graph.getLoopNodes() instead.

Suggestion: Remove this unused method to reduce code complexity.

Confidence: 90%

Rule: ts_extract_duplicated_logic_into_functions


ℹ️ 1 issue(s) outside PR diff (click to expand)

These issues were found in lines not modified in this PR.

🟡 MEDIUM - Missing type annotations on method parameters

Agent: typescript

Category: quality

Why this matters: Missing type annotations bypass TypeScript's type checking and can lead to runtime errors that could have been caught at compile time.

File: packages/core/src/main/models/FlowNode.ts:239

Description: The getConnectors method has parameters 'subtype' and 'element' without type annotations, resulting in implicit 'any' types.

Suggestion: Add explicit type annotations: private getConnectors(subtype: string, element: any): FlowElementConnector[]

Confidence: 90%

Rule: ts_prefer_unknown_over_any


🔇 6 low-severity issue(s) not posted (min: medium)

Issues below medium severity are saved but not posted as comments.
View all issues in the full review details.

📝 11 additional issue(s) shown in summary only (max: 10 inline)

To reduce noise, only 10 inline comments are posted.
All issues are listed above in the full issue list.

🔗 View full review details


Review ID: 3f6113ae-1ddc-4e81-98a7-5ec058053b15
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray diffray bot added diffray-review-completed diffray review status: completed and removed diffray-review-started diffray review status: started labels Feb 1, 2026
@Flow-Scanner Flow-Scanner deleted a comment from diffray bot Feb 1, 2026
@Flow-Scanner Flow-Scanner deleted a comment from diffray bot Feb 1, 2026
@Flow-Scanner Flow-Scanner deleted a comment from diffray bot Feb 1, 2026
@diffray diffray bot added diffray-review-started diffray review status: started and removed diffray-review-completed diffray review status: completed labels Feb 2, 2026
@diffray
Copy link

diffray bot commented Feb 2, 2026

Changes Summary

Security hardening of FileSystemResolver implementations in both CLI and VSX packages by removing dynamic search path expansion feature and adding XML validation to prevent parsing of arbitrary files. Also includes documentation updates moving SUBFLOW_RESOLUTION.md to the docs directory and updating the SubflowResolutionContext interface.

Type: mixed

Components Affected: FileSystemResolver (CLI), FileSystemResolver (VSX), SubflowResolver Core Interface, Documentation

Files Changed
File Summary Change Impact
/tmp/workspace/docs/SUBFLOW_RESOLUTION.md Moved from .github/docs to docs directory and updated interface definition to remove searchPaths from SubflowResolutionContext 📝 🟢
...ace/packages/cli/src/libs/FileSystemResolver.ts Added XML validation before parsing files and removed dynamic search path expansion from resolve/resolveMany methods ✏️ 🔴
...ace/packages/vsx/src/libs/FileSystemResolver.ts Added XML validation before parsing files and removed dynamic search path expansion from resolve/resolveMany methods ✏️ 🔴
.../packages/core/src/main/libs/SubflowResolver.ts Removed searchPaths property from SubflowResolutionContext interface and improved documentation ✏️ 🟡
Architecture Impact
  • New Patterns: Content validation before parsing, Immutable configuration pattern (search paths fixed at construction)
  • Dependencies: added: fs.promises (in both FileSystemResolver implementations)
  • Coupling: Reduced coupling between parent flows and resolver configuration by removing dynamic search path expansion
  • Breaking Changes: SubflowResolutionContext.searchPaths property removed - clients passing this property will need to update, FileSystemResolver search paths now fixed at construction time and cannot be dynamically expanded

Risk Areas: XML validation pattern might be too strict or cause false negatives if Salesforce Flow files have unusual formatting, Removing dynamic search path expansion could break workflows that relied on context-based path resolution, Reading first 1KB of files could have performance implications when resolving many subflows, Buffer allocation for every file validation adds memory overhead

Suggestions
  • Consider adding unit tests for the validateFlowContent method with various XML formats
  • Add integration tests to verify that the removed dynamic search path feature doesn't break existing workflows
  • Consider caching validation results to avoid re-reading files on subsequent resolutions
  • Document the breaking change regarding SubflowResolutionContext.searchPaths removal in a CHANGELOG or migration guide

🔗 See progress

Full review in progress... | Powered by diffray

Comment on lines +161 to +173
private async validateFlowContent(filePath: string): Promise<boolean> {
try {
// Read only the first 1KB to check for Flow root element
const fd = await fs.open(filePath, "r");
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
await fd.close();
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} catch {
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

🟠 HIGH - File handle not closed on error path
Agent: performance

Category: performance

Description:
File descriptor opened with fs.open() is not closed if fd.read() throws an error. The catch block at line 170 prevents fd.close() from executing, causing file handle leak in long-running processes.

Suggestion:
Wrap fd.close() in a finally block or use try-finally pattern to ensure cleanup happens on all code paths:

try {
const fd = await fs.open(filePath, "r");
try {
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} finally {
await fd.close();
}
} catch {
return false;
}

Confidence: 95%
Rule: perf_unclosed_resources
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +137 to +149
private async validateFlowContent(filePath: string): Promise<boolean> {
try {
// Read only the first 1KB to check for Flow root element
const fd = await fs.open(filePath, "r");
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
await fd.close();
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} catch {
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

🟠 HIGH - File handle not closed on error path
Agent: performance

Category: performance

Description:
File descriptor opened with fs.open() is not closed if fd.read() throws an error. The catch block at line 146 prevents fd.close() from executing, causing file handle leak in long-running VS Code extension.

Suggestion:
Wrap fd.close() in a finally block or use try-finally pattern to ensure cleanup happens on all code paths:

try {
const fd = await fs.open(filePath, "r");
try {
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} finally {
await fd.close();
}
} catch {
return false;
}

Confidence: 95%
Rule: perf_unclosed_resources
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +161 to +173
private async validateFlowContent(filePath: string): Promise<boolean> {
try {
// Read only the first 1KB to check for Flow root element
const fd = await fs.open(filePath, "r");
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
await fd.close();
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} catch {
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

🟡 MEDIUM - Low-level file I/O could be extracted into helper
Agent: architecture

Category: quality

Description:
The validateFlowContent() method contains low-level file descriptor operations (open, read, close) mixed with validation logic. This violates single responsibility principle.

Suggestion:
Extract the file reading logic into a separate helper function like readFileHeader(filePath: string, bytes: number): Promise<string>. This would make validateFlowContent() focused solely on validation and improve testability.

Confidence: 65%
Rule: arch_extract_helper_functions
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +137 to +149
private async validateFlowContent(filePath: string): Promise<boolean> {
try {
// Read only the first 1KB to check for Flow root element
const fd = await fs.open(filePath, "r");
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
await fd.close();
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} catch {
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

🟡 MEDIUM - Low-level file I/O could be extracted into helper
Agent: architecture

Category: quality

Description:
The validateFlowContent() method contains low-level file descriptor operations mixed with validation logic. This is duplicated code from the CLI package.

Suggestion:
Extract the file reading logic into a shared helper function. Since both CLI and VSX packages have identical implementations, consider moving this to a shared utility module to avoid duplication.

Confidence: 65%
Rule: arch_extract_helper_functions
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +219 to +240
async resolve(flowName: string, _context?: SubflowResolutionContext): Promise<ResolvedSubflow> {
const isManaged = this.managedFlows.has(flowName);

// Skip managed flows if configured
if (isManaged && this.options.skipManaged) {
return {
flowName,
flow: undefined,
isManaged: true,
error: "Managed package flow - skipped",
};
}

const flow = await this.loadFlow(flowName);

return {
flowName,
flow,
isManaged,
error: flow ? undefined : `Flow "${flowName}" not found in search paths`,
};
}
Copy link

Choose a reason for hiding this comment

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

🟠 HIGH - Removed parameter behavior lacks test coverage for regression
Agent: testing

Category: quality

Description:
The 'context.searchPaths' parameter functionality was removed from the resolve() and resolveMany() methods. The previous implementation allowed dynamic addition of search paths via the context parameter (lines 221-232 in commit b254ffd), which would rebuild the index with new paths. This behavior is now completely removed, but there are no tests to verify: (1) the old behavior is intentionally removed, (2) existing code that might depend on this feature won't break, (3) the security concern mentioned in comments ('Search paths are fixed at construction time for security') is actually enforced.

Suggestion:
Add tests to verify the security constraint that search paths cannot be modified after construction. Create test cases in packages/cli/test/libs/FileSystemResolver.test.ts that verify: (1) passing context with searchPaths to resolve() does not add new search paths, (2) passing context with searchPaths to resolveMany() does not add new search paths, (3) the flowIndex remains unchanged when context.searchPaths is provided, (4) only flows from the initial searchPaths in constructor options are resolved. Also consider adding a test that documents why this was removed (security fix).

Confidence: 75%
Rule: test_new_parameter_coverage
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +157 to +173
/**
* Validate that file content appears to be a Salesforce Flow XML.
* Checks for <Flow xmlns= pattern to prevent parsing arbitrary XML files.
*/
private async validateFlowContent(filePath: string): Promise<boolean> {
try {
// Read only the first 1KB to check for Flow root element
const fd = await fs.open(filePath, "r");
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
await fd.close();
const header = buffer.toString("utf8");
return FLOW_ROOT_PATTERN.test(header);
} catch {
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

🟠 HIGH - Duplicated validation logic across packages
Agent: react

Category: quality

Description:
The validateFlowContent() method is duplicated identically in both packages/cli and packages/vsx FileSystemResolver implementations. This violates DRY principle and creates maintenance burden - bug fixes or improvements must be applied in both locations.

Suggestion:
Extract the validateFlowContent() method to a shared utility module in the core package (e.g., @flow-scanner/lightning-flow-scanner-core/src/utils/validateFlowContent.ts) and import it in both FileSystemResolver implementations.

Confidence: 85%
Rule: ts_consolidate_duplicated_schemas
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +164 to +167
const fd = await fs.open(filePath, "r");
const buffer = Buffer.alloc(1024);
await fd.read(buffer, 0, 1024, 0);
await fd.close();
Copy link

Choose a reason for hiding this comment

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

🟡 MEDIUM - File read result not validated before use
Agent: bugs

Category: bug

Description:
The fd.read() method returns { bytesRead, buffer }, but bytesRead is not checked. If the file is empty, buffer may contain uninitialized zeros, though in this case the regex test would still return false correctly.

Suggestion:
Store the read result and validate bytesRead: const { bytesRead } = await fd.read(buffer, 0, 1024, 0); then check if (bytesRead === 0) return false; before testing the pattern.

Confidence: 65%
Rule: ts_validate_nullable_before_use
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +85 to +100
static async fromFlow(flow: Flow, additionalPaths?: string[]): Promise<FileSystemResolver> {
const searchPaths: string[] = [];

// Add directory containing the parent flow
if (flow.fsPath) {
searchPaths.push(path.dirname(flow.fsPath));
} else if (flow.uri) {
searchPaths.push(path.dirname(flow.uri));
}

// Add additional search paths
if (additionalPaths) {
searchPaths.push(...additionalPaths);
}

return FileSystemResolver.create({ searchPaths });
Copy link

Choose a reason for hiding this comment

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

🟡 MEDIUM - Empty searchPaths array when flow has no path
Agent: bugs

Category: bug

Description:
In fromFlow(), if both flow.fsPath and flow.uri are undefined/null/falsy, and no additionalPaths are provided, searchPaths will be empty. This creates a resolver that cannot find any flows.

Suggestion:
Add validation after building searchPaths: if (searchPaths.length === 0) { throw new Error('Cannot create resolver: flow has no path and no additional paths provided'); } or document that this is acceptable behavior.

Confidence: 60%
Rule: bug_array_bounds
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +7 to +11
export interface SubflowResolutionContext {
/** The parent flow containing the subflow reference */
parentFlow: Flow;
/** File path of the parent flow (if available) */
parentFlowPath?: string;
Copy link

Choose a reason for hiding this comment

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

🟠 HIGH - Breaking API change: removed optional property from public interface
Agent: architecture

Category: quality

Description:
The searchPaths optional property was removed from SubflowResolutionContext interface. This interface is exported publicly in packages/core/src/index.ts line 82. Consumers passing searchPaths will get TypeScript errors after upgrading.

Suggestion:
For backward compatibility, either: (1) Keep the searchPaths?: string[] property in the interface and ignore it in implementations, or (2) Mark this as a major version change.

Confidence: 85%
Rule: api_backward_compatibility
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

return parsed[0].flow;
}
} catch (error) {
console.warn(`Failed to load subflow "${flowName}" from ${filePath}:`, error);
Copy link

Choose a reason for hiding this comment

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

🟡 MEDIUM - Console.warn statement in production code for error handling
Agent: accessibility

Category: quality

Description:
console.warn is used in catch blocks (lines 166, 176) to log failures. In a VS Code extension, console.warn output goes to the extension host log which users rarely monitor. This provides no user feedback for errors.

Suggestion:
For VS Code extension code, consider using vscode.window.showWarningMessage() for user-facing errors or an OutputChannel for detailed logs that users can optionally access.

Confidence: 75%
Rule: fe_console_in_production
Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray
Copy link

diffray bot commented Feb 2, 2026

Review Summary

✅ Fixed Issues: 5

View fixed issues
  • Duplicated search path mutation logic across resolve methods (packages/cli/src/libs/FileSystemResolver.ts)
  • resolveMany calls resolve without passing context (packages/cli/src/libs/FileSystemResolver.ts)
  • Potential path traversal via context.searchPaths (packages/vsx/src/libs/FileSystemResolver.ts)
  • Potential path traversal via context.searchPaths (packages/cli/src/libs/FileSystemResolver.ts)
  • Linear search in array for path deduplication (packages/cli/src/libs/FileSystemResolver.ts)

Validated 33 issues: 12 kept, 21 filtered

Issues Found: 12

💬 See 12 individual line comment(s) for details.

📊 10 unique issue type(s) across 12 location(s)

📋 Full issue list (click to expand)

🟠 HIGH - File handle not closed on error path (2 occurrences)

Agent: performance

Category: performance

📍 View all locations
File Description Suggestion Confidence
packages/cli/src/libs/FileSystemResolver.ts:161-173 File descriptor opened with fs.open() is not closed if fd.read() throws an error. The catch block at... Wrap fd.close() in a finally block or use try-finally pattern to ensure cleanup happens on all code ... 95%
packages/vsx/src/libs/FileSystemResolver.ts:137-149 File descriptor opened with fs.open() is not closed if fd.read() throws an error. The catch block at... Wrap fd.close() in a finally block or use try-finally pattern to ensure cleanup happens on all code ... 95%

Rule: perf_unclosed_resources


🟠 HIGH - Removed parameter behavior lacks test coverage for regression

Agent: testing

Category: quality

File: packages/cli/src/libs/FileSystemResolver.ts:219-240

Description: The 'context.searchPaths' parameter functionality was removed from the resolve() and resolveMany() methods. The previous implementation allowed dynamic addition of search paths via the context parameter (lines 221-232 in commit b254ffd), which would rebuild the index with new paths. This behavior is now completely removed, but there are no tests to verify: (1) the old behavior is intentionally removed, (2) existing code that might depend on this feature won't break, (3) the security concern mentioned in comments ('Search paths are fixed at construction time for security') is actually enforced.

Suggestion: Add tests to verify the security constraint that search paths cannot be modified after construction. Create test cases in packages/cli/test/libs/FileSystemResolver.test.ts that verify: (1) passing context with searchPaths to resolve() does not add new search paths, (2) passing context with searchPaths to resolveMany() does not add new search paths, (3) the flowIndex remains unchanged when context.searchPaths is provided, (4) only flows from the initial searchPaths in constructor options are resolved. Also consider adding a test that documents why this was removed (security fix).

Confidence: 75%

Rule: test_new_parameter_coverage


🟠 HIGH - Duplicated validation logic across packages

Agent: react

Category: quality

File: packages/cli/src/libs/FileSystemResolver.ts:157-173

Description: The validateFlowContent() method is duplicated identically in both packages/cli and packages/vsx FileSystemResolver implementations. This violates DRY principle and creates maintenance burden - bug fixes or improvements must be applied in both locations.

Suggestion: Extract the validateFlowContent() method to a shared utility module in the core package (e.g., @flow-scanner/lightning-flow-scanner-core/src/utils/validateFlowContent.ts) and import it in both FileSystemResolver implementations.

Confidence: 85%

Rule: ts_consolidate_duplicated_schemas


🟠 HIGH - Breaking API change: removed optional property from public interface

Agent: architecture

Category: quality

File: packages/core/src/main/libs/SubflowResolver.ts:7-11

Description: The searchPaths optional property was removed from SubflowResolutionContext interface. This interface is exported publicly in packages/core/src/index.ts line 82. Consumers passing searchPaths will get TypeScript errors after upgrading.

Suggestion: For backward compatibility, either: (1) Keep the searchPaths?: string[] property in the interface and ignore it in implementations, or (2) Mark this as a major version change.

Confidence: 85%

Rule: api_backward_compatibility


🟠 HIGH - Blocking synchronous glob operation in async context

Agent: performance

Category: performance

File: packages/cli/src/libs/FileSystemResolver.ts:114-118

Description: glob.sync() blocks the event loop during filesystem scanning. This is called from buildIndex() inside async factory methods (create, fromDirectory, fromFlow). For large codebases, this blocks other async operations.

Suggestion: Replace glob.sync() with the async glob() from the glob library. Example: const flowFiles = await glob("**/*.{flow-meta.xml,flow}", { cwd: normalizedPath, ignore: ignorePatterns, absolute: true });

Confidence: 85%

Rule: node_blocking_sync_operations


🟡 MEDIUM - Low-level file I/O could be extracted into helper (2 occurrences)

Agent: architecture

Category: quality

📍 View all locations
File Description Suggestion Confidence
packages/cli/src/libs/FileSystemResolver.ts:161-173 The validateFlowContent() method contains low-level file descriptor operations (open, read, close) m... Extract the file reading logic into a separate helper function like `readFileHeader(filePath: string... 65%
packages/vsx/src/libs/FileSystemResolver.ts:137-149 The validateFlowContent() method contains low-level file descriptor operations mixed with validation... Extract the file reading logic into a shared helper function. Since both CLI and VSX packages have i... 65%

Rule: arch_extract_helper_functions


🟡 MEDIUM - File read result not validated before use

Agent: bugs

Category: bug

File: packages/cli/src/libs/FileSystemResolver.ts:164-167

Description: The fd.read() method returns { bytesRead, buffer }, but bytesRead is not checked. If the file is empty, buffer may contain uninitialized zeros, though in this case the regex test would still return false correctly.

Suggestion: Store the read result and validate bytesRead: const { bytesRead } = await fd.read(buffer, 0, 1024, 0); then check if (bytesRead === 0) return false; before testing the pattern.

Confidence: 65%

Rule: ts_validate_nullable_before_use


🟡 MEDIUM - Empty searchPaths array when flow has no path

Agent: bugs

Category: bug

File: packages/cli/src/libs/FileSystemResolver.ts:85-100

Description: In fromFlow(), if both flow.fsPath and flow.uri are undefined/null/falsy, and no additionalPaths are provided, searchPaths will be empty. This creates a resolver that cannot find any flows.

Suggestion: Add validation after building searchPaths: if (searchPaths.length === 0) { throw new Error('Cannot create resolver: flow has no path and no additional paths provided'); } or document that this is acceptable behavior.

Confidence: 60%

Rule: bug_array_bounds


🟡 MEDIUM - Console.warn statement in production code for error handling

Agent: accessibility

Category: quality

File: packages/vsx/src/libs/FileSystemResolver.ts:176

Description: console.warn is used in catch blocks (lines 166, 176) to log failures. In a VS Code extension, console.warn output goes to the extension host log which users rarely monitor. This provides no user feedback for errors.

Suggestion: For VS Code extension code, consider using vscode.window.showWarningMessage() for user-facing errors or an OutputChannel for detailed logs that users can optionally access.

Confidence: 75%

Rule: fe_console_in_production


🟡 MEDIUM - Code duplication between CLI and VSX FileSystemResolver

Agent: dry

Category: quality

File: packages/cli/src/libs/FileSystemResolver.ts:1-317

Description: CLI (317 lines) and VSX (265 lines) FileSystemResolver implementations share significant structure but aren't 99% identical. The main difference is buildIndex() implementation. Both also have duplicated FindFlows.ts utilities that are nearly identical.

Suggestion: Consider extracting shared logic into packages/core as a base class with abstract file discovery method. The FindFlows.ts files in both packages are also nearly identical and could be consolidated.

Confidence: 70%

Rule: dry_violations


📝 2 additional issue(s) shown in summary only (max: 10 inline)

To reduce noise, only 10 inline comments are posted.
All issues are listed above in the full issue list.

🔗 View full review details


Review ID: 501cb6a3-241d-4a88-8a7f-1d126fe58eb8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray diffray bot added diffray-review-completed diffray review status: completed and removed diffray-review-started diffray review status: started labels Feb 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diffray-review-completed diffray review status: completed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing Sub Flow

1 participant