archunit 0.0.1 → 1.0.0

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/API.md +290 -0
  3. data/CHANGELOG.md +45 -0
  4. data/README.md +389 -90
  5. data/assets/logo-rounded.png +0 -0
  6. data/lib/archunit/common/extraction/dependency_evidence.rb +44 -0
  7. data/lib/archunit/common/extraction/edge.rb +7 -2
  8. data/lib/archunit/common/extraction/import_kind.rb +2 -1
  9. data/lib/archunit/common/extraction/parse_diagnostic.rb +23 -0
  10. data/lib/archunit/common/fluentapi/check_options.rb +43 -2
  11. data/lib/archunit/common/fluentapi/checkable.rb +25 -1
  12. data/lib/archunit/common/logging/check_logger.rb +8 -0
  13. data/lib/archunit/common/logging/inspection.rb +59 -0
  14. data/lib/archunit/common/logging/logging_options.rb +5 -0
  15. data/lib/archunit/common/pattern.rb +18 -8
  16. data/lib/archunit/common/projection/cycles/johnson_cycles.rb +35 -19
  17. data/lib/archunit/common/projection/cycles/tarjan_scc.rb +30 -10
  18. data/lib/archunit/error/source_parse_error.rb +38 -0
  19. data/lib/archunit/extraction/arch_ignore.rb +91 -0
  20. data/lib/archunit/extraction/conservative_local_flow.rb +100 -0
  21. data/lib/archunit/extraction/constant_index.rb +110 -0
  22. data/lib/archunit/extraction/constant_visitor.rb +129 -0
  23. data/lib/archunit/extraction/discover_gemspec_load_paths.rb +96 -0
  24. data/lib/archunit/extraction/enumerate_source_files.rb +23 -8
  25. data/lib/archunit/extraction/extract_dependencies.rb +82 -14
  26. data/lib/archunit/extraction/extract_graph.rb +56 -12
  27. data/lib/archunit/extraction/extract_imports.rb +60 -124
  28. data/lib/archunit/extraction/extraction_profile.rb +105 -0
  29. data/lib/archunit/extraction/import_resolver.rb +56 -0
  30. data/lib/archunit/extraction/import_visitor.rb +104 -0
  31. data/lib/archunit/extraction/local_string_tracker.rb +80 -0
  32. data/lib/archunit/extraction/resolve_import.rb +5 -3
  33. data/lib/archunit/extraction/resolve_load_paths.rb +93 -0
  34. data/lib/archunit/extraction/resolved_import.rb +5 -2
  35. data/lib/archunit/extraction/validate_extraction_paths.rb +37 -0
  36. data/lib/archunit/files/extraction/extract_file_info.rb +14 -5
  37. data/lib/archunit/files/extraction/file_info.rb +1 -2
  38. data/lib/archunit/files/fluentapi/depend_on_external_module_condition.rb +2 -0
  39. data/lib/archunit/files/fluentapi/depend_on_external_module_condition_builder.rb +2 -0
  40. data/lib/archunit/files/fluentapi/depend_on_file_condition.rb +6 -0
  41. data/lib/archunit/files/fluentapi/depend_on_file_condition_builder.rb +6 -0
  42. data/lib/archunit/files/fluentapi/file_condition_builder.rb +12 -0
  43. data/lib/archunit/files/fluentapi/file_rule_support.rb +8 -1
  44. data/lib/archunit/files/fluentapi/files.rb +6 -0
  45. data/lib/archunit/files/fluentapi/match_pattern_file_condition_builder.rb +14 -0
  46. data/lib/archunit/files/fluentapi/positive_match_pattern_file_condition_builder.rb +2 -0
  47. data/lib/archunit/graph/fluentapi/graph.rb +5 -0
  48. data/lib/archunit/graph/fluentapi/project_graph_builder.rb +44 -0
  49. data/lib/archunit/graph/projection/aggregate_edges.rb +4 -2
  50. data/lib/archunit/graph/projection/graph_query_options.rb +1 -2
  51. data/lib/archunit/graph/projection/graph_report_edge.rb +12 -2
  52. data/lib/archunit/graph/rendering/json_renderer.rb +3 -1
  53. data/lib/archunit/layers/fluentapi/layer_definition_builder.rb +4 -0
  54. data/lib/archunit/layers/fluentapi/layer_dependency_rule_builder.rb +4 -0
  55. data/lib/archunit/layers/fluentapi/layered_architecture.rb +5 -0
  56. data/lib/archunit/layers/fluentapi/layers.rb +5 -0
  57. data/lib/archunit/metrics/extraction/extract_distance_info.rb +1 -2
  58. data/lib/archunit/metrics/extraction/extract_project_info.rb +6 -6
  59. data/lib/archunit/metrics/extraction/metric_info.rb +2 -4
  60. data/lib/archunit/metrics/extraction/source_metrics_visitor.rb +2 -4
  61. data/lib/archunit/metrics/fluentapi/custom_metric_builder.rb +2 -0
  62. data/lib/archunit/metrics/fluentapi/custom_metric_condition.rb +4 -1
  63. data/lib/archunit/metrics/fluentapi/logged_metric.rb +23 -0
  64. data/lib/archunit/metrics/fluentapi/metric_predicate_condition.rb +4 -1
  65. data/lib/archunit/metrics/fluentapi/metric_report_builder.rb +2 -0
  66. data/lib/archunit/metrics/fluentapi/metric_selection.rb +18 -2
  67. data/lib/archunit/metrics/fluentapi/metric_threshold_condition.rb +2 -1
  68. data/lib/archunit/metrics/fluentapi/metrics.rb +5 -0
  69. data/lib/archunit/metrics/fluentapi/metrics_builder.rb +15 -0
  70. data/lib/archunit/slices/fluentapi/diagram_slice_condition.rb +1 -0
  71. data/lib/archunit/slices/fluentapi/forbidden_slice_dependency_condition.rb +1 -0
  72. data/lib/archunit/slices/fluentapi/negative_slice_condition_builder.rb +2 -0
  73. data/lib/archunit/slices/fluentapi/positive_slice_condition_builder.rb +8 -0
  74. data/lib/archunit/slices/fluentapi/slice_scope_builder.rb +12 -0
  75. data/lib/archunit/slices/fluentapi/slices.rb +5 -0
  76. data/lib/archunit/testing/assert_passes.rb +4 -0
  77. data/lib/archunit/testing/result_factory.rb +16 -4
  78. data/lib/archunit/testing/rspec_adapter.rb +1 -2
  79. data/lib/archunit/testing.rb +7 -3
  80. data/lib/archunit/version.rb +1 -1
  81. data/lib/archunit.rb +9 -0
  82. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f46c756e84f01c2d6a5b1d4e39865dbb86181dd228cbdb1ec0bbf21110e77f37
4
- data.tar.gz: 37d35e46c78fc1afd46d6a86a998257c2157c36d09d6a2f73ff520a3437f6209
3
+ metadata.gz: 2ed496ce492119fbd9af981d5f3438d801fdcb27e4de3a620e90ec617ff00146
4
+ data.tar.gz: beacb18bfd3cf88bdd8907e58ba4e9f9eecce710893e77d934f0a5416531fc9f
5
5
  SHA512:
6
- metadata.gz: 95d51bad089bd4f9ad8b1f9f4a61e0c768221d54bbd89ea9e074ca31b39b79c107208fdf53530f0e59f971d82d3b698cb1a34d714bd5a787147ae8f5f7130218
7
- data.tar.gz: fcd6d488f34413e94a24fc4780a1feb6f15b511d5138af9510b3550299163a7821f2f3fc11049039a6d9465f02b3ea7966e5418758b958c7f38a417d2e377446
6
+ metadata.gz: 8393fc4f6c4c7606a671015dc4a6a8e6258e0fe39c6750d2c518b309e00651d72222d92da438e7623dcdee5fce71d4c4a98bc019a1ff1df2228f515da2afc35d
7
+ data.tar.gz: 9690eea349c54b022ca11a92caa72f9342795082ffd520aafec76514f8fb11cbc6368d2e4606db4ac44bc4ff3f4c44ad1ff64fae5de6fd5672b5750175596dfb
data/API.md ADDED
@@ -0,0 +1,290 @@
1
+ # ArchUnitRuby API Guide
2
+
3
+ This guide is the stable map of ArchUnitRuby's public fluent API. The
4
+ [class and method reference](class_list.html) is generated from the source, while this page explains
5
+ which entry point to choose and how the stages fit together.
6
+
7
+ All rule builders are immutable and lazy. Constructing a rule does not read the project; `check`,
8
+ `measure`, `snapshot`, and export methods are the terminals that perform work.
9
+
10
+ ## 1.x compatibility contract
11
+
12
+ This guide defines the supported 1.0 public surface: documented entry points, fluent methods,
13
+ options, public data/error types, and report formats. Additive capabilities can arrive in minor
14
+ versions; incompatible public API changes require a major version and migration notes. Private
15
+ helpers and classes marked as implementation details are not part of that compatibility promise.
16
+ Correctness fixes can change detected violations; compatibility is not a promise to preserve missed
17
+ dependencies or misleading clean results.
18
+
19
+ Static extraction remains bounded and does not execute the target application's source or gemspecs.
20
+ Import-only is the default; expanded constant analysis is explicit opt-in. The documented unresolved
21
+ lookup cases, project source/exclusion policy, strict parser failures, and explicit cache-clearing
22
+ behavior remain important when upgrading. See the README's migration notes for changes from 0.0.1.
23
+
24
+ ## Common rule shape
25
+
26
+ ~~~ruby
27
+ rule = ArchUnit.project_files
28
+ .in_folder('app/api/**')
29
+ .should_not.depend_on_files
30
+ .in_folder('app/database/**')
31
+
32
+ violations = rule.check
33
+ ~~~
34
+
35
+ | Stage | Public vocabulary | Result |
36
+ | --- | --- | --- |
37
+ | Entry | `project_files`, `project_layers`, `project_slices`, `project_graph`, `metrics` | Immutable builder |
38
+ | Scope | `with_name`, `in_folder`, `in_path`, `in_file`, `for_classes_matching` | Narrower builder |
39
+ | Mood | `should`, `should_not` | Positive or negated predicate builder |
40
+ | Predicate | File, layer, slice, or metric policy | Checkable rule |
41
+ | Terminal | `check`, `measure`, `snapshot`, `to_*`, `export_as_*` | Data or an artifact |
42
+
43
+ String patterns are anchored globs. `*` stays inside one path segment, `**` crosses zero or more
44
+ segments, and `?` matches one non-separator character. A trailing folder glob such as
45
+ `app/api/**` includes the `app/api` folder itself and every descendant folder. Regular expressions
46
+ are accepted by all pattern selectors except the exact `in_file` selector.
47
+
48
+ Every pattern selector accepts `except:`. Pass a pattern, an array of patterns, or explicit selector
49
+ keys such as `{ in_folder: 'generated', with_name: '*_spec.rb' }`.
50
+
51
+ ## File rules
52
+
53
+ Every completed rule implementing `ArchUnit::Checkable` supports `.because(reason)`. Append it
54
+ after all selectors and modifiers to get a distinct frozen rule of the same type, with a trimmed,
55
+ immutable `rationale` string. Non-string or blank reasons raise `ArgumentError`. The original rule,
56
+ violations, graph cache, and `check` options are unchanged. The shared result formatter adds
57
+ `Because: ...` exactly once to failed framework-neutral, RSpec, and Minitest assertions, including
58
+ failed negated RSpec expectations. Successful assertions and raw violation formatting omit it.
59
+
60
+ Start with `ArchUnit.project_files(project_locator = nil)`; `ArchUnit.files` is its short alias.
61
+ Without a locator, ArchUnitRuby searches upward for a `Gemfile` or gemspec.
62
+
63
+ Discovery skips symlinked source files and directories, including aliases inside the project root.
64
+ Direct `FileInfo` extraction validates canonical as well as lexical containment before reading.
65
+ The source walker reuses a single file-status result per entry for type and symlink decisions.
66
+
67
+ The detected root's `.archignore` is applied centrally to graphs and file-based metrics. It accepts
68
+ blank lines, full-line `#` comments, basename globs, root-relative path globs, leading `/` anchors,
69
+ and trailing `/` directory patterns. Backslashes normalize to `/`. Bare names match at any depth;
70
+ patterns containing a separator match from the project root. A directory pattern excludes its
71
+ descendants but not a similarly named regular file. Nested ignore files are not read.
72
+
73
+ Repository patterns combine with defaults or explicit `exclude_patterns:`. An explicit `[]` only
74
+ disables defaults, never repository patterns. The normalized effective pattern set participates in
75
+ graph cache identity; changing the file is noticed on the next check without `clear_cache`.
76
+ Changing source contents still requires explicit cache clearing. Reads are bounded to 1 MiB; UTF-8
77
+ BOMs are accepted and invalid bytes replaced. Symlinks (including project-local ones), non-regular
78
+ files, negation, NUL characters, and a bare `/` are rejected. No Ruby or gemspec code is evaluated.
79
+
80
+ | Scope or predicate | Purpose |
81
+ | --- | --- |
82
+ | `with_name(pattern)` | Select or require a filename pattern |
83
+ | `in_folder(pattern)` | Select or require a project-relative directory |
84
+ | `in_path(pattern)` | Select or require a complete project-relative path |
85
+ | `in_file(path)` | Select one exact project-relative file |
86
+ | `have_no_cycles` | Reject cycles among selected project files |
87
+ | `depend_on_files` | Allow or reject dependencies matching target selectors |
88
+ | `depend_on_external_modules` | Allow or reject standard-library or gem imports |
89
+ | `adhere_to(callable, message)` | Evaluate a custom predicate over immutable `FileInfo` values |
90
+
91
+ ~~~ruby
92
+ service_scope = ArchUnit.project_files.in_folder('app/services/**')
93
+
94
+ rules = [
95
+ service_scope.should.have_name('*_service.rb'),
96
+ service_scope.should_not.depend_on_files.in_folder('app/controllers/**'),
97
+ service_scope.should_not.depend_on_external_modules.matching('net/http'),
98
+ service_scope.should.adhere_to(
99
+ ->(file) { file.lines_of_code < 300 },
100
+ 'services must stay below 300 non-blank lines'
101
+ )
102
+ ]
103
+ ~~~
104
+
105
+ Positive dependency rules are allowlists: every selected dependency must match at least one target.
106
+ Negated dependency rules are blocklists: matching dependencies become violations.
107
+
108
+ Cycle projection uses iterative strongly-connected-component and elementary-cycle traversals, so
109
+ long dependency chains do not consume Ruby's call stack. It still enumerates all elementary cycles;
110
+ dense graphs can have exponentially many results. Scope cycle rules to meaningful components.
111
+
112
+ ## Layer rules
113
+
114
+ Use `ArchUnit.project_layers(project_locator = nil)` to name architectural layers and then declare
115
+ their allowed or forbidden relationships.
116
+
117
+ ~~~ruby
118
+ rule = ArchUnit.project_layers
119
+ .layer('api').defined_by('app/api/**/*.rb')
120
+ .layer('services').defined_by_folder('app/services/**')
121
+ .layer('database').defined_by('app/database/**/*.rb')
122
+ .where_layer('api').may_only_depend_on_layers('services')
123
+ .where_layer('services').may_only_depend_on_layers('database')
124
+ .where_layer('database').may_only_depend_on_layers
125
+ ~~~
126
+
127
+ `may_only_depend_on_layers` creates an allowlist. Calling it without targets seals the layer.
128
+ `may_not_depend_on_layers` creates a blocklist and requires at least one target. Dependencies inside
129
+ one layer are allowed; edges with an unassigned endpoint are ignored.
130
+
131
+ ## Slices and diagrams
132
+
133
+ Use `ArchUnit.project_slices(project_locator = nil)` to capture one segment from project paths.
134
+ `(**)` is the glob capture; `defined_by_regex` uses the first regular-expression capture.
135
+
136
+ ~~~ruby
137
+ slices = ArchUnit.project_slices.defined_by('lib/my_app/(**)/')
138
+
139
+ dependency_rule = slices.should_not.contain_dependency('api', 'database')
140
+ diagram_rule = slices.should
141
+ .ignoring_external_slices
142
+ .ignoring_orphan_slices
143
+ .adhere_to_diagram_in_file('docs/architecture.puml')
144
+
145
+ plantuml = slices.to_plantuml
146
+ slices.export_as_plantuml('reports/architecture.puml')
147
+ ~~~
148
+
149
+ `adhere_to_diagram` accepts inline PlantUML. The supported subset includes components, directed
150
+ dependencies, comments, `@startuml`, and `@enduml`.
151
+
152
+ ## Dependency graph reports
153
+
154
+ Use `ArchUnit.project_graph(project_locator = nil)` for queryable snapshots and reports.
155
+
156
+ ~~~ruby
157
+ report = ArchUnit.project_graph
158
+ .include_external_dependencies
159
+ .focus_on('app/services/**', 2)
160
+ .collapse_to_folder_depth(2)
161
+ .titled('Service dependencies')
162
+
163
+ summary = report.summary
164
+ snapshot = report.snapshot
165
+ report.export_as_mermaid('reports/services.mmd')
166
+ report.export_as_html('reports/services.html')
167
+ ~~~
168
+
169
+ Query modifiers are `focus_on(pattern, depth)`, `reachable_from(pattern)`, and
170
+ `dependents_of(pattern)`. Group nodes with `collapse_to_folder_depth` or
171
+ `collapse_by_pattern(pattern, replacement)`. Formats are DOT, Mermaid, D2, CSV, JSON, and
172
+ self-contained HTML; each provides both `to_<format>` and `export_as_<format>`.
173
+
174
+ ## Metrics
175
+
176
+ Start with `ArchUnit.metrics(project_locator = nil)`. Select files with the standard file selectors
177
+ and classes with `for_classes_matching`.
178
+
179
+ ~~~ruby
180
+ services = ArchUnit.metrics
181
+ .in_path('app/services/**/*.rb')
182
+ .for_classes_matching('*Service')
183
+
184
+ rules = [
185
+ services.count.method_count.should_be_below_or_equal(20),
186
+ services.lcom.lcom4.should_be(1),
187
+ services.distance.instability.should_be_below(0.8)
188
+ ]
189
+
190
+ measurements = services.count.method_count.measure
191
+ services.count.export_as_html('reports/service-counts')
192
+ ~~~
193
+
194
+ Count metrics cover methods, fields, lines, statements, imports, classes, and top-level functions.
195
+ Cohesion provides LCOM96a, LCOM96b, LCOM1-5, and LCOM*. Distance metrics include abstractness,
196
+ instability, main-sequence distance, normalized distance, and coupling factor.
197
+
198
+ Thresholds use exactly `should_be_below`, `should_be_above`, `should_be`,
199
+ `should_be_below_or_equal`, `should_be_above_or_equal`, and `should_satisfy`. Use `custom_metric`
200
+ for a calculation over `ClassInfo`; zone guards are `not_in_zone_of_pain` and
201
+ `not_in_zone_of_uselessness`.
202
+
203
+ ## Checking and test frameworks
204
+
205
+ Every architecture rule includes `ArchUnit::Checkable` and exposes `check(options = nil)`. It returns
206
+ an array of structured `Violation` values; an architecture disagreement is not an exception until
207
+ an assertion adapter translates it at the test boundary.
208
+
209
+ ~~~ruby
210
+ violations = rule.check
211
+
212
+ expect(rule).to pass # RSpec
213
+ assert_passes(rule) # Minitest
214
+ ArchUnit.assert_passes(rule) # Framework-neutral
215
+ ~~~
216
+
217
+ Use `ArchUnit.format_violations` or `ArchUnit::ResultFactory` when integrating with another test
218
+ framework or command-line interface.
219
+
220
+ ## Check options and logging
221
+
222
+ `ArchUnit::CheckOptions` keeps optional execution behavior out of the fluent sentence:
223
+
224
+ ~~~ruby
225
+ logging = ArchUnit::LoggingOptions.new(
226
+ level: :debug,
227
+ output_directory: 'tmp/archunit-logs'
228
+ )
229
+
230
+ options = ArchUnit::CheckOptions.new(
231
+ allow_empty_tests: false,
232
+ clear_cache: false,
233
+ load_paths: ['components/billing/lib'],
234
+ dependency_analysis: :imports,
235
+ logging: logging
236
+ )
237
+
238
+ violations = rule.check(options)
239
+ ~~~
240
+
241
+ Zero selected subjects produce `EmptyTestViolation` by default. Set `allow_empty_tests: true` only
242
+ when an empty scope is intentionally valid. Set `clear_cache: true` after modifying the analyzed
243
+ project during one process. `load_paths` adds project-contained source roots without evaluating
244
+ gemspecs or running application code.
245
+
246
+ `dependency_analysis` defaults to `:imports`. Imports accept literal arguments and preceding local
247
+ literal-string assignments in supported straight-line scopes. Aliases, mutation/escape, closures,
248
+ reflection, and unsupported control flow invalidate inferred values; runtime paths are not guessed.
249
+
250
+ For autoloaded projects, opt into known project constant references alongside imports:
251
+
252
+ ~~~ruby
253
+ options = ArchUnit::CheckOptions.new(dependency_analysis: :imports_and_constants)
254
+ violations = rule.check(options)
255
+ json = ArchUnit.project_graph.with_check_options(options).to_json
256
+ values = ArchUnit.metrics.distance.instability.measure(options)
257
+ ~~~
258
+
259
+ Expanded analysis resolves supported lexical, compact, and root-qualified references to their
260
+ definition files. Explicit superclass and mixin references also contribute edges. Missing or
261
+ ambiguous definitions, inherited-member lookup, and dynamic metaprogramming stay unresolved under
262
+ the bounded contract in the main guide. Namespaces that fail to stabilize within eight index passes
263
+ fall back to import-only results. Unknown third-party constants do not create architecture violations.
264
+
265
+ Declarations include ordinary `=` and conditional `||=`, with lexical/qualified/root-qualified
266
+ ownership and definition evidence. This does not predict runtime execution or load order. Multiple
267
+ definition files remain ambiguous; update-only compound and multiple-assignment forms are not
268
+ inferred as new declaration owners.
269
+
270
+ Expanded edges include `:constant_reference` in `import_kinds` and retain `DependencyEvidence` with
271
+ names and source lines in `edge.evidence` and JSON reports. An import and a constant use supporting
272
+ the same file pair merge into one edge. Import-only edge evidence stays empty for compatibility;
273
+ `ArchUnit::Extraction.extract_imports` retains import call locations in both modes. The analysis
274
+ mode participates in graph-cache identity, and `clear_cache: true` rebuilds the symbol index.
275
+
276
+ ## Errors and support
277
+
278
+ Invalid API input raises `ArchUnit::UserError` or `ArgumentError`; library or environment failures
279
+ raise `ArchUnit::TechnicalError`. Rule failures remain violation data.
280
+
281
+ Malformed included Ruby raises `ArchUnit::SourceParseError`, a `TechnicalError`, during graph or
282
+ AST-metric extraction. No partial graph is returned or cached. `error.diagnostics` is an immutable
283
+ array of `ArchUnit::ParseDiagnostic` values with `path`, one-based `line`/`column`, and `message`;
284
+ each value supports `to_h`. RSpec/Minitest assertions propagate this technical failure instead of
285
+ reporting a pass or an ordinary architecture violation. Excluded files are not parsed. The profile's
286
+ `malformed_files` counter records a rejected file; existing cache-clearing rules still apply.
287
+
288
+ See the [main guide](index.html) for installation and complete examples, [SUPPORT.md](https://github.com/LukasNiessen/ArchUnitRuby/blob/main/SUPPORT.md)
289
+ for help, and [SECURITY.md](https://github.com/LukasNiessen/ArchUnitRuby/blob/main/SECURITY.md) for
290
+ responsible vulnerability reporting.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0 - 2026-10-26
4
+
5
+ First stable API baseline. The previously unreleased 0.0.2 development work is included here;
6
+ 0.0.1 remains the preceding published version. See the README's migration notes before upgrading.
7
+
8
+ - Fail closed on malformed included Ruby in dependency graphs and AST metrics, with structured
9
+ source diagnostics and profile counters instead of silently treating incomplete analysis as clean.
10
+
11
+ - Add development-only locked dependency scanning with advisory refresh, fail-closed health checks,
12
+ Windows path normalization, and retained CI reports.
13
+
14
+ - Avoid stack overflows in strongly-connected-component detection, elementary-cycle traversal,
15
+ and blocked-vertex propagation on deep dependency graphs.
16
+ - Skip symlinked source entries and enforce canonical project boundaries for direct file metadata.
17
+ - Reuse source-entry file status instead of repeating separate directory and file probes.
18
+ - Validate version/changelog/lock consistency in CI and require prepared release metadata before
19
+ RubyGems publication.
20
+ - Enforce the unchanged coverage thresholds on runtime library source only, with a real-report
21
+ regression check that excludes loaded specs, benchmarks, and scripts from the denominator.
22
+
23
+ - Apply bounded project-root `.archignore` exclusions consistently to dependency graphs and metrics,
24
+ with cache invalidation, cross-platform glob handling, and configuration safety checks.
25
+
26
+ - Add immutable `.because(...)` explanations to every executable rule and shared assertion output.
27
+
28
+ - Add opt-in debug inspection of project roots, graphs, and selected files on cold and cached checks.
29
+ - Log passing metric values without repeating custom calculations.
30
+ - Document verbosity, CI log artifacts, and existing colored violation reports.
31
+
32
+ - Add opt-in project constant-reference dependencies with lexical lookup and conservative ambiguity handling.
33
+ - Index lexical, qualified, and root-qualified `||=` declarations in expanded analysis without
34
+ executing source or changing import-only mode.
35
+ - Infer import paths from safely known local literal strings without evaluating application code.
36
+ - Preserve dependency source evidence in expanded mode through graph merging and JSON reporting,
37
+ while keeping import-only edge values compatible with existing consumers.
38
+ - Support check options on metric measurements and add installed-gem extraction regression checks.
39
+ - Make trailing folder globs such as `app/api/**` include the named folder itself.
40
+ - Validate the README quickstart as a passing and failing end-to-end architecture test.
41
+ - Add a curated API guide, reliable Markdown rendering, responsive documentation navigation, and
42
+ documentation quality checks.
43
+ - Add the ArchUnitRuby family logo, social preview, and repository community health files.
44
+ - Discover adjacent `lib` directories in multi-gemspec repositories without evaluating gemspecs.
45
+ - Support validated, project-local custom load paths through `CheckOptions`.
46
+ - Add cold/warm extraction profiling, a reproducible benchmark, and resolution caching.
47
+
3
48
  ## 0.0.1 - 2026-08-11
4
49
 
5
50
  Initial executable prototype.