archspec 1.0.0 → 1.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +30 -16
- data/lib/archspec/analyzer.rb +639 -261
- data/lib/archspec/architectures.rb +7 -3
- data/lib/archspec/component_spec.rb +13 -2
- data/lib/archspec/diagnostic.rb +19 -3
- data/lib/archspec/dsl.rb +69 -38
- data/lib/archspec/evaluator.rb +6 -1
- data/lib/archspec/formatters/explanation.rb +33 -5
- data/lib/archspec/formatters/json.rb +1 -0
- data/lib/archspec/formatters/text.rb +19 -1
- data/lib/archspec/model.rb +266 -65
- data/lib/archspec/rubydex_index.rb +379 -0
- data/lib/archspec/rules/component_rules.rb +3 -4
- data/lib/archspec/rules/cycle_rule.rb +130 -33
- data/lib/archspec/rules/naming_rules.rb +11 -22
- data/lib/archspec/rules/protocol_rules.rb +117 -22
- data/lib/archspec/rules/reasoned.rb +37 -0
- data/lib/archspec/source_location.rb +1 -3
- data/lib/archspec/version.rb +1 -1
- data/lib/archspec.rb +4 -2
- metadata +21 -6
- data/lib/archspec/value_object.rb +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d1149a18e2ffb69c03d15e95ca539d3c42c1fda406376069313b9e07365a1b1
|
|
4
|
+
data.tar.gz: 290b05eb65ad246ebd1d81a025abe013a3150d5afc7e0bb6cb834dae3f64a429
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e02c22290a4616b60edb2a5fd97ba991237e6da3b2fe495a5fde7802ac520fa432c821a9eb895c98333ce1acb170aff476ffa016c1de924199a9b47463d3994
|
|
7
|
+
data.tar.gz: 99d07ea4daff657b1823a81beff200f2139a3646c1ef7ad1bb3f041f13a9a03d7bd39a8b5b4b0176bda00ca13506043a691a1c93f65f54eed33a17c129e0f6af
|
data/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# ArchSpec
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<strong>Executable architecture specifications for Ruby and Rails</strong>
|
|
6
|
+
|
|
7
|
+
<p>Declare your components and boundaries in one <code>Archspec.rb</code>, and every change gets checked in CI, whether a person or a coding agent wrote it. No AI involved, just static analysis.</p>
|
|
8
|
+
|
|
9
|
+
Battle tested in [<picture><source media="(prefers-color-scheme: dark)" srcset="https://rubyllm.com/assets/images/logotype_dark.svg"><img src="https://rubyllm.com/assets/images/logotype.svg" alt="RubyLLM" height="30" align="absmiddle"></picture>](https://rubyllm.com) and at [<picture><source media="(prefers-color-scheme: dark)" srcset="https://chatwithwork.com/logotype-dark.svg"><img src="https://chatwithwork.com/logotype.svg" alt="Chat with Work" height="30" align="absmiddle"></picture>](https://chatwithwork.com)
|
|
10
|
+
|
|
11
|
+
[](https://rubygems.org/gems/archspec)
|
|
12
|
+
[](https://rubygems.org/gems/archspec)
|
|
13
|
+
[](https://github.com/crmne/archspec/actions/workflows/ci.yml)
|
|
4
14
|
|
|
5
|
-
|
|
6
|
-
your components, dependencies, and boundaries in one file, then check every
|
|
7
|
-
change in CI, whether a person or a coding agent wrote it. It is plain static
|
|
8
|
-
analysis: it reads Ruby source with Prism, never boots the app, and no AI is
|
|
9
|
-
involved in checking your code.
|
|
15
|
+
<img src="https://archspecrb.dev/assets/images/archspec-check.png" alt="archspec check reporting an architecture violation with a code frame, the offending span underlined, and the evidence as a note" width="760">
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
you write down: components, layers, constant references, inheritance, mixins,
|
|
13
|
-
named method calls, method protocols, cycles, and Rails boundaries.
|
|
17
|
+
</div>
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
ArchSpec turns your architecture into executable checks: components, layers, constant references, inheritance, mixins, named method calls, method protocols, naming conventions, cycles, and Rails boundaries. It is plain static analysis: Rubydex builds the semantic index, Prism supplies a small syntax overlay, the app never boots, and no AI is involved in checking your code.
|
|
18
22
|
|
|
19
23
|
## Why ArchSpec?
|
|
20
24
|
|
|
@@ -32,6 +36,10 @@ to remember, and checks them on every change:
|
|
|
32
36
|
- query objects do not call obvious write methods
|
|
33
37
|
- generated code follows the same boundaries as hand-written code
|
|
34
38
|
|
|
39
|
+
ArchSpec does not try to infer the "true" design pattern of arbitrary Ruby code.
|
|
40
|
+
You describe the architecture your team wants. ArchSpec checks whether the code
|
|
41
|
+
still matches it.
|
|
42
|
+
|
|
35
43
|
## Show me the code
|
|
36
44
|
|
|
37
45
|
Start with conventional Rails boundaries:
|
|
@@ -92,10 +100,13 @@ Write local rules in plain Ruby:
|
|
|
92
100
|
component :controllers, in: "app/controllers/**/*.rb"
|
|
93
101
|
component :models, in: "app/models/**/*.rb"
|
|
94
102
|
component :services, in: "app/services/**/*.rb"
|
|
103
|
+
component :records, descendants_of: "ApplicationRecord"
|
|
95
104
|
|
|
96
105
|
controllers.can_only_use :models, :services
|
|
97
106
|
models.cannot_use :controllers
|
|
98
|
-
services.cannot_call :render, :redirect_to, :params, :session
|
|
107
|
+
services.cannot_call :render, :redirect_to, :params, :session,
|
|
108
|
+
because: "services do not own the HTTP response"
|
|
109
|
+
models.cannot_call :find_by_sql, receiver: "ActiveRecord::Base"
|
|
99
110
|
services.cannot_instantiate_and_invoke
|
|
100
111
|
```
|
|
101
112
|
|
|
@@ -116,7 +127,7 @@ architecture :cqrs,
|
|
|
116
127
|
- **Layers:** dependency direction and cycles
|
|
117
128
|
- **Rails:** controller APIs kept out of models and services
|
|
118
129
|
- **Architectures:** Rails, vanilla Rails, layered, hexagonal, clean, modular monolith, CQRS, event-driven, and Ruby conventions bundles
|
|
119
|
-
- **Protocols:**
|
|
130
|
+
- **Protocols:** instance/class APIs and callable signatures such as `call(amount, actor:)`
|
|
120
131
|
- **Naming:** conventions on a component's public API, such as banning `get_`/`set_` or pairing `with_x` with `without_x`
|
|
121
132
|
- **Objects:** rules against one-shot `Something.new(...).whatever` command objects
|
|
122
133
|
- **Empty components:** directories that must stay empty, like `app/services` in vanilla Rails
|
|
@@ -128,6 +139,8 @@ ArchSpec does not check Zeitwerk constant names. Zeitwerk does that itself. Add
|
|
|
128
139
|
|
|
129
140
|
## Installation
|
|
130
141
|
|
|
142
|
+
ArchSpec requires Ruby 3.2 or newer.
|
|
143
|
+
|
|
131
144
|
Add ArchSpec to your Gemfile:
|
|
132
145
|
|
|
133
146
|
```ruby
|
|
@@ -164,8 +177,9 @@ bundle exec archspec check --update-todo
|
|
|
164
177
|
bundle exec archspec explain app/models/user.rb
|
|
165
178
|
```
|
|
166
179
|
|
|
167
|
-
`explain` shows why a file or constant belongs to a component
|
|
168
|
-
facts
|
|
180
|
+
`explain` shows why a file or constant belongs to a component, its resolved
|
|
181
|
+
ancestry, outgoing facts, incoming dependencies, and anything the analysis
|
|
182
|
+
could not prove:
|
|
169
183
|
|
|
170
184
|
```text
|
|
171
185
|
app/models/user.rb
|