Skip to content

Feature Proposal: Context-Level Cycle Detection #5

@qiuethan

Description

@qiuethan

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:

  1. Treats each context as a node in a graph
  2. Adds a directed edge fromContext → toContext when any file in fromContext depends on a file in toContext
  3. Detects strongly connected components (SCCs) in the context graph
  4. 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:

  1. Map each file to a context using existing contextMappings
  2. For each file dependency fromFile → toFile:
    • Resolve fromContext and toContext
    • If both exist and fromContext !== toContext, add an edge fromContext → toContext in a Map<string, Set<string>>

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 via contextMappings)
    • 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

  1. Context resolution helper

    • Implement getFileContext(filePath: string, archConfig: ArchConfig): string | null
    • Use contextMappings with glob patterns to map file paths to contexts
  2. Context graph builder

    • Walk ctx.dependencies
    • For each edge, map file paths to contexts
    • Build Map<string, Set<string>> for context dependencies
  3. Tarjan-based SCC detector

    • Reuse existing strongly connected component logic (from CyclicDependencyRule) and adapt it to operate on the context graph
  4. Rule implementation (ContextCycleRule)

    • Extend BaseRule
    • In check(ctx), build context graph, run SCC, emit RuleViolations
  5. 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 shared context 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions