-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Summary
Archctl currently detects cyclic dependencies at the file level using Tarjan's algorithm over the file dependency graph. With the introduction of vertical contexts (feature modules / bounded contexts), it becomes important to detect cycles at the context level as well.
This proposal adds a context-level cycle detection rule that collapses file dependencies into a context dependency graph and reports cycles between contexts (e.g., orders → billing → inventory → orders), independent of the specific architecture style used.
The design is intentionally flexible and does not assume Clean Architecture; contexts are arbitrary, user-defined groupings.
Problem
File-level cycle detection is useful but insufficient when projects organize their code into higher-level modules or contexts. Even if file-level cycles are absent, it is possible to create unacceptable high-level cycles:
- Context A depends on Context B
- Context B depends on Context C
- Context C depends on Context A
This kind of entanglement:
- Makes contexts impossible to reason about independently
- Encourages cross-cutting changes and hidden coupling
- Reduces the value of vertical modularization
Today, Archctl cannot express or detect these relationships.
Goal
Introduce a new rule that:
- Treats each context as a node in a graph
- Adds a directed edge
fromContext → toContextwhen any file infromContextdepends on a file intoContext - Detects strongly connected components (SCCs) in the context graph
- Reports SCCs with more than one context as context cycles
This should work regardless of the chosen architecture style (Clean Architecture, layered, hexagonal, feature-based, etc.).
Proposed Solution
1. Build a Context Dependency Graph
Given:
contextMappings: mapping file paths to contexts- Dependency edges between files:
fileFrom → fileTo
Steps:
- Map each file to a
contextusing existingcontextMappings - For each file dependency
fromFile → toFile:- Resolve
fromContextandtoContext - If both exist and
fromContext !== toContext, add an edgefromContext → toContextin aMap<string, Set<string>>
- Resolve
This produces a reduced context dependency graph.
2. Detect Strongly Connected Components (Cycles)
Reuse or adapt the existing Tarjan implementation (used for file-level cycles) to work on the context graph:
- Nodes: context names (strings)
- Edges: context-level dependencies
Any SCC with more than one context implies a cycle at the context level.
Example:
- SCC:
["orders", "billing", "inventory"] - This means:
orders → billing → inventory → orders(possibly via multiple files)
3. New Rule: ContextCycleRule (extends BaseRule)
Implement a new rule class, similar to CyclicDependencyRule:
- Inputs:
ctx.dependencies(file-level edges)ctx.files(for mapping file → context viacontextMappings)ctx.archConfig.contextMappings(or equivalent)
- Steps:
- Build context graph as above
- Run Tarjan to find SCCs
- For each SCC with more than one context, emit violations
Possible violation shape:
- One violation per context in the SCC
- Metadata includes:
cycleContexts: string[]- Optional sample paths showing file-level edges that induced the cycle
Example message:
Context 'orders' is part of a circular dependency involving 3 contexts: orders → billing → inventory → orders.
Configuration
No new user-facing configuration is strictly required if contextMappings already exist.
Optionally, a rule-specific config type could allow:
- Ignoring some contexts
- Setting severity (error vs warning)
- Enabling or disabling context cycle detection independently
Example:
{
"rules": [
{
"kind": "context-cycle",
"id": "no-context-cycles",
"title": "No Cycles Between Contexts",
"description": "Detects cycles in the context dependency graph.",
"severity": "error",
"ignoreContexts": ["shared"]
}
]
}Implementation Outline
-
Context resolution helper
- Implement
getFileContext(filePath: string, archConfig: ArchConfig): string | null - Use
contextMappingswith glob patterns to map file paths to contexts
- Implement
-
Context graph builder
- Walk
ctx.dependencies - For each edge, map file paths to contexts
- Build
Map<string, Set<string>>for context dependencies
- Walk
-
Tarjan-based SCC detector
- Reuse existing strongly connected component logic (from
CyclicDependencyRule) and adapt it to operate on the context graph
- Reuse existing strongly connected component logic (from
-
Rule implementation (
ContextCycleRule)- Extend
BaseRule - In
check(ctx), build context graph, run SCC, emitRuleViolations
- Extend
-
Documentation
- Describe context-level cycles in README / setup docs
- Provide examples showing how cycles can arise across contexts
- Explain how to interpret and fix reported cycles
Open Questions
- Should cycles involving the
sharedcontext be ignored by default? - Should we report a single consolidated violation per cycle, or one per context?
- Should we attempt to surface a sample file-level path for each context cycle to aid debugging?
These can be addressed incrementally without blocking an initial implementation.