# How It Works

After reading this guide, you will know:

- The five stages between your source code and a diagnostic.
- What facts ArchSpec extracts and what it refuses to guess.
- Why checks are fast and safe to run anywhere.

## The Pipeline

ArchSpec never loads or executes your application. It reads source code with
[Prism](https://github.com/ruby/prism), Ruby's own parser, and everything
downstream is plain data:

```text
glob files -> parse -> extract facts -> assign components -> evaluate rules
```

1. **Collect.** The patterns from `source` (defaulting to `app/**/*.rb`,
   `lib/**/*.rb`, and pack/engine paths) are globbed from the project
   directory, minus `ignore` patterns. (`Analyzer.ruby_files`)
2. **Parse.** Each file goes through `Prism.parse_file`. Syntax errors
   become `parser.syntax` diagnostics instead of crashes, and
   `archspec:disable` comments are collected as suppressions.
3. **Extract facts.** A visitor walks the syntax tree and records edges in
   a graph — who defines what, who touches what. (`Analyzer::SourceVisitor`)
4. **Assign components.** Each `component` declaration claims files by glob
   and constants by namespace. A file can belong to several components; the
   `explain` command shows why. (`Graph.assign_components`)
5. **Evaluate.** Every rule reads the graph and emits diagnostics, which are
   then filtered through suppressions and the baseline, sorted, and printed.
   (`Evaluator.evaluate`)

## The Facts

Everything a rule can check is one of these edge types:

| Fact                       | Recorded when                                  |
| -------------------------- | ---------------------------------------------- |
| `references_constant`      | `UsersController` appears in an expression     |
| `inherits_from`            | `class User < ApplicationRecord`               |
| `includes` / `prepends` / `extends` | `include Billable` and friends        |
| `calls_named_method`       | any method call, by name                       |
| `instantiates_and_invokes` | `UserBuilder.new(params).build`                |
| `requires` / `requires_relative` | `require "csv"` with a literal string    |
| `dynamic_feature`          | `send`, `const_get`, `define_method`, `method_missing`, ... |

Alongside edges, the graph keeps each constant's methods and mixins (for
protocol rules) and each file's expected constant from its path (for the
Zeitwerk rule: `app/models/user.rb` should define `User`).

Run `archspec explain app/models/user.rb` to see the facts for one file —
it prints the expected constant, component assignments with reasons, and
every outgoing edge.

## A Violation, Traced

Given `models.cannot_use :controllers` and this file:

```ruby
class User
  def profile_path = UsersController
end
```
{: data-title="app/models/user.rb"}

Parsing records a `references_constant` edge from `app/models/user.rb` to
`UsersController`. Component assignment puts the file in `models` and puts
`UsersController` (defined in `app/controllers`) in `controllers`. The
`dependencies.forbid` rule walks dependency edges from `models`, resolves
`UsersController` the way Ruby would — innermost namespace outward — finds
it lands in a forbidden component, and emits:

```text
[dependencies.forbid] app/models/user.rb:2:22
  models must not depend on controllers
  evidence: User references_constant UsersController
  confidence: high
```

## Dynamic Code

Static analysis cannot see through `send`, `const_get`, or `method_missing`.
ArchSpec records these as `dynamic_feature` facts with confidence
`unknown_due_to_dynamic_feature` instead of ignoring them, and every
diagnostic carries the evidence it was derived from, so you can verify a
report against the source line it points to.

Because ArchSpec only parses code and never loads it, checks need no Rails
boot, no database, and have no side effects. They are safe to run in CI, in
a git hook, or after an AI-assisted change.
