ripple_effect 0.1.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.ripple-effect.yml.example +56 -0
  3. data/ARCHITECTURE.md +222 -0
  4. data/CHANGELOG.md +115 -0
  5. data/CODE_OF_CONDUCT.md +64 -0
  6. data/CONTRIBUTING.md +112 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +305 -0
  9. data/SECURITY.md +73 -0
  10. data/docs/ANALYSIS_MODEL.md +275 -0
  11. data/docs/CLI.md +276 -0
  12. data/docs/CONFIGURATION.md +178 -0
  13. data/docs/DECISIONS.md +210 -0
  14. data/docs/PUBLIC_LAUNCH_CHECKLIST.md +105 -0
  15. data/docs/RELEASING.md +94 -0
  16. data/docs/TESTING.md +179 -0
  17. data/exe/ripple-effect +7 -0
  18. data/lib/ripple_effect/analyzer.rb +379 -0
  19. data/lib/ripple_effect/cache_store.rb +207 -0
  20. data/lib/ripple_effect/cli/application.rb +126 -0
  21. data/lib/ripple_effect/cli/command.rb +165 -0
  22. data/lib/ripple_effect/cli/diff_command.rb +76 -0
  23. data/lib/ripple_effect/cli/doctor_command.rb +106 -0
  24. data/lib/ripple_effect/cli/graph_command.rb +61 -0
  25. data/lib/ripple_effect/cli/inspect_command.rb +66 -0
  26. data/lib/ripple_effect/cli/tests_command.rb +109 -0
  27. data/lib/ripple_effect/cli/version_command.rb +46 -0
  28. data/lib/ripple_effect/confidence.rb +61 -0
  29. data/lib/ripple_effect/configuration.rb +264 -0
  30. data/lib/ripple_effect/diagnostic.rb +90 -0
  31. data/lib/ripple_effect/diff/changed_symbol_resolver.rb +292 -0
  32. data/lib/ripple_effect/diff/git.rb +175 -0
  33. data/lib/ripple_effect/diff/hunk.rb +80 -0
  34. data/lib/ripple_effect/edge.rb +114 -0
  35. data/lib/ripple_effect/error.rb +23 -0
  36. data/lib/ripple_effect/extractors/base.rb +292 -0
  37. data/lib/ripple_effect/extractors/rails_associations.rb +102 -0
  38. data/lib/ripple_effect/extractors/rails_callbacks.rb +144 -0
  39. data/lib/ripple_effect/extractors/rails_delegation.rb +121 -0
  40. data/lib/ripple_effect/extractors/rails_jobs.rb +131 -0
  41. data/lib/ripple_effect/extractors/rails_mailers.rb +120 -0
  42. data/lib/ripple_effect/extractors/rails_routes.rb +256 -0
  43. data/lib/ripple_effect/extractors/rails_views.rb +299 -0
  44. data/lib/ripple_effect/extractors/ruby_structure.rb +221 -0
  45. data/lib/ripple_effect/extractors/test_conventions.rb +135 -0
  46. data/lib/ripple_effect/formatters/dot.rb +69 -0
  47. data/lib/ripple_effect/formatters/json.rb +43 -0
  48. data/lib/ripple_effect/formatters/text.rb +197 -0
  49. data/lib/ripple_effect/graph.rb +199 -0
  50. data/lib/ripple_effect/node.rb +153 -0
  51. data/lib/ripple_effect/project.rb +264 -0
  52. data/lib/ripple_effect/result.rb +147 -0
  53. data/lib/ripple_effect/risk.rb +167 -0
  54. data/lib/ripple_effect/static_index/adapter.rb +84 -0
  55. data/lib/ripple_effect/static_index/rubydex_adapter.rb +356 -0
  56. data/lib/ripple_effect/traversal/impact_walker.rb +153 -0
  57. data/lib/ripple_effect/version.rb +11 -0
  58. data/lib/ripple_effect.rb +89 -0
  59. metadata +155 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e21f6c7b049337fe428c43688fc03649e6d0cba1900ffb4d0c201f21dbca43ae
4
+ data.tar.gz: 78f8ffcfb821ddbaeb8095c270896d75b8487052f453300d5df725fc8aceadca
5
+ SHA512:
6
+ metadata.gz: 6cf0d03cf06db2aaf3994b801c0f34c84db7e02fd4310f5a89fb3dfe6d6216592f79f25f5b8e08c489b9974086ba25730b1271b0eda28b2dffa4f57e0851e768
7
+ data.tar.gz: f940b74752a7a87816d52e12ec6bfcb09ebad291cf0f0073f0a3e98288ea899839d33e59bf93c712112c2ffe29122f1f912a34f3292d9d50be4752cd8c57bbc2
@@ -0,0 +1,56 @@
1
+ version: 1
2
+
3
+ paths:
4
+ include:
5
+ - app/**/*.rb
6
+ - lib/**/*.rb
7
+ - config/routes.rb
8
+ - config/routes/**/*.rb
9
+ - spec/**/*.rb
10
+ - test/**/*.rb
11
+ views:
12
+ - app/views/**/*.erb
13
+ discover_engines: true
14
+ exclude:
15
+ - vendor/**
16
+ - node_modules/**
17
+ - tmp/**
18
+ - log/**
19
+ - coverage/**
20
+ - public/assets/**
21
+ - storage/**
22
+ - .bundle/**
23
+ - .git/**
24
+
25
+ analysis:
26
+ min_confidence: medium
27
+ default_depth: all
28
+ include_low_confidence: false
29
+
30
+ rails:
31
+ routes: true
32
+ associations: true
33
+ callbacks: true
34
+ jobs: true
35
+ mailers: true
36
+ delegation: true
37
+ views: true
38
+
39
+ tests:
40
+ framework: auto
41
+ unsafe_global_files:
42
+ - Gemfile
43
+ - Gemfile.lock
44
+ - config/application.rb
45
+ - config/environment.rb
46
+ - config/boot.rb
47
+ - config/environments/**
48
+ - config/initializers/**
49
+ - config/routes.rb
50
+ - spec/spec_helper.rb
51
+ - spec/rails_helper.rb
52
+ - test/test_helper.rb
53
+
54
+ cache:
55
+ enabled: true
56
+ directory: tmp/ripple_effect
data/ARCHITECTURE.md ADDED
@@ -0,0 +1,222 @@
1
+ # Architecture
2
+
3
+ RippleEffect is a pipeline. Each stage has one job, produces plain value objects,
4
+ and can be tested on its own.
5
+
6
+ ```
7
+ project files
8
+ |
9
+ v
10
+ [ Project ] selects files, keeps every path inside the root
11
+ |
12
+ v
13
+ [ StaticIndex ] Rubydex, behind an adapter -> Declarations, References
14
+ |
15
+ v
16
+ [ Extractors ] Ruby structure, then Rails semantics -> Nodes, Edges
17
+ |
18
+ v
19
+ [ Graph ] typed, evidenced, deterministic; forward + reverse indexes
20
+ | ^
21
+ | |
22
+ | [ CacheStore ] JSON on disk, content-keyed
23
+ v
24
+ [ Query | Diff ] a symbol, or Git-changed symbols
25
+ |
26
+ v
27
+ [ ImpactWalker ] breadth-first reverse traversal -> Impacts with evidence
28
+ |
29
+ v
30
+ [ Risk | Tests ] explainable band; ranked test files
31
+ |
32
+ v
33
+ [ Result ] -> [ Formatters ] text | json | dot
34
+ ```
35
+
36
+ ## The graph is the product
37
+
38
+ Everything else exists to build it or to ask questions of it.
39
+
40
+ A **node** is an addressable thing: a file, class, module, method, route, callback,
41
+ job, or mailer action. Node IDs are deterministic strings that include the path,
42
+ so a class reopened in two files does not collide:
43
+
44
+ ```
45
+ method:app/services/billing_service.rb:BillingService#charge
46
+ class:app/models/order.rb:Order
47
+ route:config/routes.rb:5:route:POST:/checkout
48
+ ```
49
+
50
+ An **edge** is a directed, evidenced relationship. The direction never varies:
51
+
52
+ > `from_id` **depends on / invokes / references** `into_id`
53
+
54
+ That single rule is what makes the whole thing work: walking edges *backwards*
55
+ from a changed node finds everything that could break.
56
+
57
+ Every edge carries an `evidence` code (`rails.after_commit`,
58
+ `rubydex.constant_reference`, `convention.rspec_path`) and a `confidence` band.
59
+ No extractor may add an edge it cannot evidence.
60
+
61
+ ## The Rubydex adapter boundary
62
+
63
+ Ruby declarations and references come from [Rubydex](https://github.com/Shopify/rubydex).
64
+ Rubydex is fast, Ruby-aware, and young: its API is still moving. So it is sealed
65
+ behind `StaticIndex::Adapter`, and `RubydexAdapter` converts everything into our
66
+ own `Declaration`, `MethodReference` and `ConstantReference` structs *immediately*.
67
+
68
+ No Rubydex object is ever visible outside `lib/ripple_effect/static_index/`. There
69
+ is a spec that asserts exactly this.
70
+
71
+ The adapter also absorbs two things nobody else should have to know:
72
+
73
+ - Rubydex reports **zero-indexed** lines. Git, editors and humans count from one,
74
+ so the adapter shifts them at the boundary.
75
+ - Rubydex names singleton methods `Order::<Order>#recent`. The adapter normalises
76
+ that to the documented canonical form `Order.recent`.
77
+
78
+ Replacing the indexer means rewriting one file.
79
+
80
+ ## Extractors
81
+
82
+ Extractors add nodes and edges. They run in a fixed order because they build on
83
+ each other:
84
+
85
+ 1. **RubyStructure**, files, classes, modules, methods, inheritance, mixins,
86
+ constant references, method calls. Creates the nodes everything else attaches
87
+ to, so it is never optional.
88
+ 2. **RailsAssociations**, `belongs_to`, `has_one`, `has_many`, `has_and_belongs_to_many`.
89
+ 3. **RailsCallbacks**, the lifecycle macros, including conditions and blocks.
90
+ 4. **RailsJobs**, job classes and `perform_later` / `perform_now` sites.
91
+ 5. **RailsMailers**, mailer actions and `deliver_later` / `deliver_now` sites.
92
+ 6. **RailsRoutes**, `config/routes.rb`.
93
+ 7. **RailsDelegation**, `delegate ..., to:`, which resolves targets *through*
94
+ associations, so it must run after them.
95
+ 8. **TestConventions**, links test files to what they cover. Runs last, so it can
96
+ see every node.
97
+
98
+ The declaration index tells us *that* `belongs_to` was called; only an AST tells us
99
+ what it was called with. The Rails extractors therefore parse with **Prism**,
100
+ sharing one `SourceCache` so each file is parsed at most once.
101
+
102
+ Each Rails extractor can be switched off in configuration.
103
+
104
+ ## Method call resolution
105
+
106
+ Ruby dispatch is dynamic, so this is where honesty matters most.
107
+
108
+ | Situation | What we do | Confidence |
109
+ | --- | --- | --- |
110
+ | Rubydex resolved the receiver | Link to that exact method | `high` |
111
+ | Receiver unresolved, one method in the project has that name | Link to it | `medium` |
112
+ | Receiver unresolved, several methods share the name | **No edge.** Record a diagnostic |, |
113
+ | `send`, `public_send`, `method_missing`, generated methods | **No edge** |, |
114
+
115
+ The third row is the important one. Picking one of several candidates would
116
+ produce a confident falsehood, and one of those costs more trust than ten honest
117
+ "I don't know"s.
118
+
119
+ ## Diff mapping
120
+
121
+ Marking every declaration in a touched file as changed would give a one-line fix
122
+ an enormous, useless blast radius. Instead:
123
+
124
+ 1. `git diff --unified=0` gives hunks with no context, so every reported line
125
+ really changed.
126
+ 2. Added and modified lines are mapped through the live index to the **smallest
127
+ enclosing declaration**, a method, not its class.
128
+ 3. Deleted lines exist only in the base revision, so that content is fetched with
129
+ `git show BASE:path` and parsed separately with Prism. If the deleted symbol is
130
+ gone entirely, the change is attributed to the class that owned it: which is
131
+ what its former dependents actually referenced.
132
+ 4. Lines inside a file but outside any declaration mean the *file* changed.
133
+ 5. Renames, deletions and untracked files are each recorded as diagnostics.
134
+
135
+ Git is always invoked with argument arrays, so a ref is only ever a ref.
136
+
137
+ With only a `BASE`, the comparison is against the working tree and includes
138
+ staged, unstaged **and untracked** files. `git diff` alone cannot see the last of
139
+ those, so they are collected separately.
140
+
141
+ ## Traversal
142
+
143
+ `ImpactWalker` does a breadth-first reverse walk from the changed nodes.
144
+
145
+ Breadth-first is not an implementation detail. It means the evidence path we
146
+ report is the *shortest* chain of reasoning, which is the one a human can check
147
+ fastest. Where two paths are equally short, the more confident one wins.
148
+
149
+ A path's confidence is its **weakest link**, a chain of reasoning is only as good
150
+ as its worst step.
151
+
152
+ Cycles are handled with a visited set. Depth is unlimited by default, with
153
+ `--depth N` to narrow.
154
+
155
+ ## Risk
156
+
157
+ `Risk` turns graph facts into a band (`low` / `medium` / `high` / `critical`) plus
158
+ the reasons for it. The formula lives in one class, is fully documented, and is
159
+ tested against its thresholds.
160
+
161
+ It counts dependents (capped, so one huge fan-out cannot dominate), adds points
162
+ for reachability from an entry point (route, job, callback, mailer), for touching
163
+ a whole class body rather than a method body, for changing a boot-impact file, and
164
+ for spanning several architectural layers.
165
+
166
+ File nodes are excluded from the counts: they exist so a file-level change can
167
+ reach the code it declares, and counting them would inflate every score.
168
+
169
+ Risk is a ranking aid for reviewers. It is not a prediction of defects, and there
170
+ are no percentages.
171
+
172
+ ## Test relevance
173
+
174
+ Test files reached by the walk are ranked by *why* they were reached:
175
+
176
+ 1. `reference`, the test actually mentions the changed code
177
+ 2. `rails_semantic`, reached through a Rails relationship
178
+ 3. `convention`, it sits at the conventional path
179
+ 4. `heuristic`, a looser path match, such as a request spec
180
+
181
+ A changed test file is always worth running, so it ranks first.
182
+
183
+ When a global or boot-impact file changes, no narrow answer is trustworthy, and
184
+ `tests` refuses to print one unless `--allow-unsafe-focus` is given.
185
+
186
+ ## Cache
187
+
188
+ A built graph is written to `tmp/ripple_effect/` as JSON: never Marshal, because a
189
+ cache file is ordinary data that any process can write, and Marshal would turn a
190
+ corrupted cache into arbitrary object construction.
191
+
192
+ The cache key is a SHA-256 over the tool version, cache schema version, indexer
193
+ version, Ruby version, config digest, and the content digest of every indexed
194
+ file. Correctness beats clever invalidation.
195
+
196
+ A cache that cannot be read is discarded and rebuilt, with a diagnostic. It is
197
+ never fatal.
198
+
199
+ ## Determinism
200
+
201
+ Identical input must produce byte-identical output, or the JSON contract cannot be
202
+ snapshot-tested and CI diffs become noise.
203
+
204
+ - Files are indexed in sorted order.
205
+ - Node and edge IDs are derived, never random.
206
+ - Duplicate edges are suppressed by `(from, into, type, evidence)`.
207
+ - Every traversal sorts its frontier by node ID.
208
+ - Results sort by `(depth, node id)`; diagnostics by `(path, line, code)`.
209
+ - Serialised key order is fixed.
210
+
211
+ ## Security
212
+
213
+ The threat model is simple: RippleEffect reads files and runs `git`.
214
+
215
+ - No network, no telemetry.
216
+ - The application is never booted; its source is never `eval`ed.
217
+ - Git runs through `Open3.capture3` with argument arrays.
218
+ - YAML is loaded with `safe_load_file`, no aliases, no permitted classes.
219
+ - Paths are normalised, symlinks resolved, and anything resolving outside the
220
+ project root is refused.
221
+
222
+ See [SECURITY.md](SECURITY.md).
data/CHANGELOG.md ADDED
@@ -0,0 +1,115 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## Versioning policy
9
+
10
+ The public API covered by SemVer is:
11
+
12
+ - The `ripple-effect` command line: its commands, flags, exit codes, and the
13
+ stdout/stderr contract.
14
+ - The JSON document, identified by `schema_version`. A breaking change to it
15
+ increments `schema_version` **and** the major version.
16
+ - The documented Ruby API: `RippleEffect.analyze`, `.diff`, `.tests_for`, and the
17
+ public classes `Configuration`, `Project`, `Analyzer`, `Result`, `Node`, `Edge`,
18
+ `Error`.
19
+
20
+ Not covered: extractor internals, the static-index adapter, exact risk scores,
21
+ diagnostic message wording (the `code` is stable, the message is not), and the
22
+ on-disk cache format.
23
+
24
+ While the version is `0.x`, minor versions may make breaking changes; they will be
25
+ listed here.
26
+
27
+ ## [Unreleased]
28
+
29
+ Nothing yet.
30
+
31
+ ## [0.1.0] - 2026-09-10
32
+
33
+ First release.
34
+
35
+ ### Added
36
+
37
+ - **Change-impact graph.** Typed, evidenced, deterministic. Every edge carries an
38
+ evidence code and a confidence band (`high` / `medium` / `low`).
39
+ - **Ruby structure analysis** via [Rubydex](https://github.com/Shopify/rubydex),
40
+ behind an adapter: classes, modules, instance and singleton methods,
41
+ inheritance, `include` / `prepend` / `extend`, constant references, and method
42
+ calls.
43
+ - **Rails semantics** via [Prism](https://github.com/ruby/prism): associations,
44
+ lifecycle callbacks (including conditions and blocks), Active Job enqueues,
45
+ Action Mailer deliveries, routes, and delegation.
46
+ - **`ripple-effect inspect SYMBOL`**, what may be affected by a change, and why.
47
+ - **`ripple-effect diff BASE [HEAD]`**, blast radius of a Git range, mapping
48
+ changed lines to the smallest enclosing declaration rather than to whole files.
49
+ Handles added, modified, deleted, renamed, staged, unstaged and untracked files.
50
+ - **`ripple-effect tests BASE [HEAD]`**, ranked relevant test files, printed as
51
+ bare paths so they compose in a shell. Refuses to narrow the suite when a
52
+ boot-impact file changes, unless `--allow-unsafe-focus` is given.
53
+ - **`ripple-effect graph SYMBOL`**, the impacted subgraph as DOT or JSON.
54
+ - **`ripple-effect doctor`**, project, environment and index health, plus a
55
+ restatement of the privacy guarantees.
56
+ - **`ripple-effect version`.**
57
+ - **Explainable risk scoring**, discrete bands with stated reasons, never a
58
+ probability.
59
+ - **JSON output** with `schema_version: 1`, deterministic ordering, and a JSON
60
+ error envelope on stderr.
61
+ - **Embeddable Ruby API**, `RippleEffect.analyze`, `.diff`, `.tests_for`.
62
+ - **Content-keyed JSON cache** that self-heals when corrupt.
63
+ - **View layer analysis.** ERB templates and partials are indexed: a controller
64
+ action links to its conventional template, templates link to the partials they
65
+ render, and template helper calls and constant references are resolved under
66
+ the same rules as Ruby. `helper_method` is modelled, including when the method
67
+ comes from an included concern. Templates are never rendered or executed.
68
+ - **Configuration** via `.ripple-effect.yml`, safely loaded, with warnings for
69
+ unknown keys and hard errors for invalid values.
70
+
71
+ ### Added during validation against real applications
72
+
73
+ - **Engine monorepo support.** Directories that are Rails engines or gems in
74
+ their own right: an `app/` plus a `*.gemspec` or `lib/**/engine.rb`, are
75
+ discovered and indexed. Solidus went from 2 indexed files to 1,810. Disable
76
+ with `paths.discover_engines: false`.
77
+ - **`doctor` reports templates indexed and engines discovered.**
78
+ - **`script/ground_truth.rb`**, which checks RippleEffect's answers against an
79
+ exhaustive text search of a real application.
80
+ - **Rails DSL compatibility specs** that pin the routing, model, job, mailer and
81
+ template forms each supported Rails version uses. The CI Rails matrix now
82
+ actually exercises them; previously it ran a suite that never loaded Rails.
83
+
84
+ ### Fixed during validation against real applications
85
+
86
+ - **A root whose casing differed from the directory on disk produced an empty
87
+ analysis with no error.** On case-insensitive filesystems (macOS, Windows)
88
+ `--root ~/code/appease` would find every file in `~/code/AppEase` and then
89
+ discard every declaration. The project root is now anchored on the filesystem's
90
+ own spelling, and symlinked roots resolve the same way.
91
+ - **Indexer lint findings were reported as parse failures.** "assigned but unused
92
+ variable" and similar notes about perfectly valid files were surfaced as
93
+ `unparsed_file` warnings. They are now `index_note` diagnostics at `info`.
94
+ - **A cached run reported no diagnostics at all**, so running twice made the tool
95
+ look more confident the second time. Diagnostics are now stored with the graph.
96
+ - **Indexing files and finding no declarations is now a loud error**
97
+ (`empty_index`) rather than a confident empty answer.
98
+ - **`doctor` created a directory inside the analysed project** while checking
99
+ cache writability, even when run with `--no-cache`. It now inspects the nearest
100
+ existing ancestor instead and creates nothing.
101
+ - **The gem claimed support for Ruby 4.0, which does not exist.** Supported and
102
+ tested versions are Ruby 3.2, 3.3 and 3.4; the gemspec bound is now `< 5.0`.
103
+ - Analysis of a 5,000-file project went from 7.1s to 1.5s by removing quadratic
104
+ path and ancestry lookups.
105
+
106
+ ### Security
107
+
108
+ - No network access, no telemetry.
109
+ - The analysed application is never booted and its source is never `eval`ed.
110
+ - Git is invoked with argument arrays, never a shell string.
111
+ - YAML is loaded with `safe_load_file`; the cache is JSON, never Marshal.
112
+ - Paths are confined to the project root, with symlinks resolved before the check.
113
+
114
+ [Unreleased]: https://github.com/iamzayn19/ripple-effect/compare/v0.1.0...HEAD
115
+ [0.1.0]: https://github.com/iamzayn19/ripple-effect/releases/tag/v0.1.0
@@ -0,0 +1,64 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body size,
7
+ visible or invisible disability, ethnicity, sex characteristics, gender identity
8
+ and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment:
18
+
19
+ - Demonstrating empathy and kindness toward other people
20
+ - Being respectful of differing opinions, viewpoints, and experiences
21
+ - Giving and gracefully accepting constructive feedback
22
+ - Accepting responsibility and apologizing to those affected by our mistakes,
23
+ and learning from the experience
24
+ - Focusing on what is best for the overall community
25
+
26
+ Examples of unacceptable behavior:
27
+
28
+ - The use of sexualized language or imagery, and sexual attention or advances
29
+ - Trolling, insulting or derogatory comments, and personal or political attacks
30
+ - Public or private harassment
31
+ - Publishing others' private information, such as a physical or email address,
32
+ without their explicit permission
33
+ - Other conduct which could reasonably be considered inappropriate in a
34
+ professional setting
35
+
36
+ ## Enforcement Responsibilities
37
+
38
+ Community leaders are responsible for clarifying and enforcing our standards of
39
+ acceptable behavior and will take appropriate and fair corrective action in
40
+ response to any behavior that they deem inappropriate, threatening, offensive, or
41
+ harmful.
42
+
43
+ ## Scope
44
+
45
+ This Code of Conduct applies within all community spaces, and also applies when an
46
+ individual is officially representing the community in public spaces.
47
+
48
+ ## Enforcement
49
+
50
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
51
+ reported to the community leaders responsible for enforcement through the
52
+ repository's private contact channels. All complaints will be reviewed and
53
+ investigated promptly and fairly.
54
+
55
+ All community leaders are obligated to respect the privacy and security of the
56
+ reporter of any incident.
57
+
58
+ ## Attribution
59
+
60
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
61
+ version 2.1, available at
62
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
63
+
64
+ [homepage]: https://www.contributor-covenant.org
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,112 @@
1
+ # Contributing
2
+
3
+ Thanks for considering a contribution.
4
+
5
+ ## Setup
6
+
7
+ RippleEffect targets Ruby 3.2–4.0. The repository pins 3.4.7 in `.tool-versions`.
8
+
9
+ ```console
10
+ $ bin/setup # or: bundle install
11
+ $ bundle exec rake # specs + RuboCop; this must be green
12
+ ```
13
+
14
+ ## The commands you need
15
+
16
+ ```console
17
+ $ bundle exec rake # specs + RuboCop (the default task)
18
+ $ bundle exec rspec # specs only
19
+ $ bundle exec rspec spec/unit # fast unit specs
20
+ $ bundle exec rubocop -a # autocorrect
21
+ $ bundle exec rake smoke # build, install into a temp GEM_HOME, run the CLI
22
+ $ bundle exec rake benchmark # indexing benchmarks
23
+ ```
24
+
25
+ Try it against the fixture application:
26
+
27
+ ```console
28
+ $ bundle exec ruby -Ilib exe/ripple-effect --root spec/fixtures/rails_app inspect 'BillingService#charge'
29
+ ```
30
+
31
+ ## The principles this project is built on
32
+
33
+ Please read [ARCHITECTURE.md](ARCHITECTURE.md) first. A few rules are not
34
+ negotiable, because the product is trust:
35
+
36
+ 1. **Never invent an edge.** Every edge carries an evidence code and a confidence
37
+ band. If you cannot evidence a relationship, do not add it.
38
+ 2. **Prefer a false negative to a confident false positive.** When a target is
39
+ ambiguous, record a diagnostic and add nothing. One confident falsehood costs
40
+ more than ten honest "I don't know"s.
41
+ 3. **Never execute the analysed application.** No booting, no `eval`, no database,
42
+ no network. Ever.
43
+ 4. **Output must be deterministic.** Identical input, byte-identical output.
44
+ 5. **Keep Rubydex behind the adapter.** Nothing outside
45
+ `lib/ripple_effect/static_index/` may see a Rubydex object.
46
+ 6. **Keep the layers apart.** No CLI parsing in domain objects, no formatting in
47
+ analysis objects, no Rails dependency in core code, no global state.
48
+
49
+ ## Adding a Rails extractor
50
+
51
+ Rails extractors are the most likely contribution. To add one:
52
+
53
+ 1. Create `lib/ripple_effect/extractors/rails_<thing>.rb`, subclassing
54
+ `Extractors::Base`.
55
+ 2. Define `self.feature` to return the config key that switches it off, and add
56
+ that key to `Configuration::RAILS_EXTRACTORS`.
57
+ 3. Read only **literal** DSL forms. Never execute a block, scope, or proc.
58
+ 4. Give every edge an evidence code (`rails.<macro>`) and an honest confidence
59
+ band. Emit a diagnostic when a target cannot be resolved.
60
+ 5. Register it in `Analyzer::EXTRACTORS`, in dependency order: associations must
61
+ run before delegation, and `TestConventions` runs last.
62
+ 6. Add `spec/unit/rails_<thing>_spec.rb` covering positive cases, negative cases,
63
+ the unresolvable case, and the config-off case.
64
+ 7. Document the edge types, evidence codes and diagnostics in
65
+ [docs/ANALYSIS_MODEL.md](docs/ANALYSIS_MODEL.md), a spec checks that every one
66
+ is documented.
67
+
68
+ Use `Extractors::Base` helpers rather than re-parsing: `each_call_in_class`,
69
+ `literal_keywords`, `literal_positional_names`, `constant_path_name`, `classify`.
70
+
71
+ ## Tests
72
+
73
+ Every behaviour change needs a spec. See [docs/TESTING.md](docs/TESTING.md).
74
+
75
+ - `spec/unit/`, one file per class or extractor.
76
+ - `spec/integration/`, the fixture app, the Git diff engine, the JSON contract,
77
+ the CLI, and documentation consistency.
78
+ - `spec/fixtures/rails_app/`, a static, Rails-shaped app. It never boots. Its
79
+ `*_spec.rb` files are **analysis input, not tests**, and are excluded from runs.
80
+
81
+ Diff behaviour is tested against real, temporary Git repositories, because a stub
82
+ would let us pass against a Git that behaves differently from the user's.
83
+
84
+ ## Style
85
+
86
+ RuboCop is the arbiter; `bundle exec rake` must be green.
87
+
88
+ Beyond that: boring, conventional Ruby. Small classes with one job. Keyword
89
+ arguments for multi-parameter constructors. Value objects with readers. Explicit
90
+ constants for kinds and types. No metaprogramming, no monkey patches.
91
+
92
+ Comments should explain **why**, not restate the code. The interesting comments in
93
+ this codebase are the ones explaining a judgement call: why an edge is `medium`
94
+ and not `high`, why a fallback exists.
95
+
96
+ ## Pull requests
97
+
98
+ - One logical change per PR.
99
+ - Explain the reasoning, not just the diff.
100
+ - Include specs, and update the docs when behaviour changes.
101
+ - Add a `CHANGELOG.md` entry under `## [Unreleased]`.
102
+ - Confirm `bundle exec rake` and `bundle exec rake smoke` both pass.
103
+
104
+ ## Issues
105
+
106
+ For a bug, the most useful report includes: your Ruby and Rails versions, the
107
+ command you ran, what you expected, what happened, and: if you can share it: a
108
+ minimal snippet that reproduces it. `ripple-effect doctor --format json` output is
109
+ very helpful.
110
+
111
+ For a wrong or missing edge, please say what relationship you expected and where
112
+ in your source it comes from. That is usually enough to write the failing spec.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iamzayn19
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.