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
@@ -0,0 +1,105 @@
1
+ # Public launch checklist
2
+
3
+ RippleEffect v0.1 is a **private build**. Work through this before making the
4
+ repository public or pushing to RubyGems.
5
+
6
+ ## 1. Name verification: do this first
7
+
8
+ Everything else is wasted if the name is taken.
9
+
10
+ - [ ] **RubyGems:** `gem owner ripple_effect` / check
11
+ `https://rubygems.org/gems/ripple_effect` returns 404. A registry lookup is
12
+ authoritative; a web search is not.
13
+ *(As of this build, `gem search -r ripple_effect` returns no results: but
14
+ re-verify immediately before reserving, and reserve the name promptly.)*
15
+ - [ ] **GitHub:** the `ripple-effect` org or repo name is available.
16
+ - [ ] **Existing tools:** search for other developer tools called "Ripple Effect"
17
+ or similar, in Ruby and beyond, to avoid a confusing collision.
18
+ - [ ] **Trademark:** a basic search for conflicting marks in the software class.
19
+ - [ ] **Domain and social handles**, if you want them.
20
+ - [ ] Reserve the gem name with a `0.0.1` placeholder push once verified.
21
+
22
+ Update every reference if the name changes: gemspec, `lib/ripple_effect/`,
23
+ `exe/ripple-effect`, `.ripple-effect.yml`, module name, docs, and the
24
+ `tmp/ripple_effect/` cache directory.
25
+
26
+ ## 2. Correctness and quality
27
+
28
+ Verified locally on macOS (arm64) at the time of writing:
29
+
30
+ - [x] `bundle exec rake` green. 435 examples, 0 failures.
31
+ - [x] `bundle exec rake smoke` green. 9 checks.
32
+ - [x] Suite green on Ruby 3.2.8, 3.3.10 and 3.4.7.
33
+ - [x] Rails matrix green with Rails **actually installed**: 7.0.10, 7.1.6,
34
+ 7.2.3.2, 8.0.5.1, 8.1.3.1.
35
+ - [x] `gem build` emits zero warnings.
36
+ - [x] Coverage above the bar. 95.9% line, 78.7% branch.
37
+ - [x] No `TODO`, `FIXME` or stub for anything v0.1 promises.
38
+ - [x] Run against **real, large** Rails applications. Six were checked: lobsters,
39
+ rubygems.org, Solidus, Mastodon, and two private production apps. None
40
+ crashed; none reported an analysis error. `script/ground_truth.rb` found
41
+ **zero false positives across 18 heavily-referenced symbols**.
42
+
43
+ Still to do on real CI hardware:
44
+
45
+ - [ ] Confirm the same matrix is green on GitHub Actions (it has never run
46
+ there: the repository has no remote yet).
47
+ - [ ] Check behaviour on Linux and Windows. Only macOS has been exercised, and
48
+ the case-insensitivity bug found during validation was platform-specific.
49
+
50
+ ## 3. Documentation
51
+
52
+ - [ ] Every command in `README.md` and `docs/CLI.md` produces the output shown.
53
+ - [ ] Limitations are stated plainly and prominently.
54
+ - [ ] Comparisons to Packwerk, Enola and test-selection tools are accurate,
55
+ respectful, and make no unverified superiority claim.
56
+ - [ ] `CHANGELOG.md` has a dated `0.1.0` entry.
57
+ - [ ] The JSON contract is documented with its schema version.
58
+ - [ ] Rubydex and Prism are credited, with their licenses acknowledged.
59
+ - [ ] No private paths, usernames, internal hostnames or secrets anywhere in the
60
+ repository, including fixtures and spec output.
61
+
62
+ ## 4. Repository setup
63
+
64
+ - [ ] `LICENSE.txt` (MIT) present.
65
+ - [ ] `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `SECURITY.md` present.
66
+ - [ ] Issue templates and a PR template present.
67
+ - [ ] Branch protection on `main`; required status checks.
68
+ - [ ] **GitHub Actions permissions minimised**, `permissions: contents: read` at
69
+ the workflow level, raised only where a job needs more.
70
+ - [ ] No secrets configured that are not yet needed. In particular,
71
+ `release.yml.example` stays disabled until publishing is decided.
72
+ - [ ] Private vulnerability reporting **enabled**, and the placeholder URL in
73
+ `SECURITY.md` replaced with the real advisory link.
74
+ - [ ] Repository description and topics set (`ruby`, `rails`, `static-analysis`,
75
+ `impact-analysis`, `developer-tools`).
76
+
77
+ ## 5. Release mechanics
78
+
79
+ - [ ] RubyGems account has **MFA enabled** (the gemspec requires it).
80
+ - [ ] Decided: manual push, or trusted publishing via OIDC. Prefer OIDC over a
81
+ long-lived API key.
82
+ - [ ] A signed tag for the release.
83
+ - [ ] Release notes drafted from the changelog.
84
+
85
+ ## 6. Claims to double-check
86
+
87
+ RippleEffect's value is that people can trust what it says. The same standard
88
+ applies to how it is described.
89
+
90
+ - [ ] **No benchmark number is quoted anywhere unless it was measured**, with the
91
+ hardware and project size stated.
92
+ - [ ] No claim that test selection is safe to use for skipping CI.
93
+ - [ ] No probability or percentage anywhere in the risk output or the marketing.
94
+ - [ ] The privacy claims: no network, no telemetry, no boot, no `eval`, are all
95
+ still literally true of the shipped code. Re-verify; do not assume.
96
+
97
+ ## 7. Announcement
98
+
99
+ - [ ] README opening lines work as a standalone description.
100
+ - [ ] A short terminal capture of `inspect` and `diff` on a real app.
101
+ - [ ] A post that leads with the problem (Rails hides its dependencies) rather
102
+ than the feature list, and that is honest about the limitations.
103
+ - [ ] Prepared answers for the obvious questions: how it differs from Packwerk and
104
+ Enola, whether it is safe to skip tests with it (no), and how it handles
105
+ dynamic dispatch (it does not, and says so).
data/docs/RELEASING.md ADDED
@@ -0,0 +1,94 @@
1
+ # Releasing
2
+
3
+ > **RippleEffect 0.1.0 has not been released.** Before the first public release,
4
+ > work through [PUBLIC_LAUNCH_CHECKLIST.md](PUBLIC_LAUNCH_CHECKLIST.md), in
5
+ > particular, the name must be verified as available on RubyGems.
6
+
7
+ ## Versioning
8
+
9
+ SemVer. The public API and what falls outside it are defined at the top of
10
+ [CHANGELOG.md](../CHANGELOG.md).
11
+
12
+ Two version numbers move independently:
13
+
14
+ - `RippleEffect::VERSION`, the gem.
15
+ - `RippleEffect::SCHEMA_VERSION`, the JSON contract. A breaking change here
16
+ increments both it and the gem's major version.
17
+ - `RippleEffect::CACHE_SCHEMA_VERSION`, the on-disk cache. Bumping it invalidates
18
+ every existing cache, which is the intended effect; it is not part of SemVer.
19
+
20
+ ## Before you release
21
+
22
+ Everything below must pass on a clean checkout.
23
+
24
+ ```console
25
+ $ bundle install
26
+ $ bundle exec rake # specs + RuboCop
27
+ $ bundle exec rake smoke # build, isolated install, installed CLI
28
+ $ gem build ripple_effect.gemspec # must emit zero warnings
29
+ ```
30
+
31
+ Then check by hand:
32
+
33
+ - [ ] CI is green on every supported Ruby.
34
+ - [ ] `CHANGELOG.md` has a dated entry for this version, and `## [Unreleased]` is
35
+ empty.
36
+ - [ ] `lib/ripple_effect/version.rb` is bumped.
37
+ - [ ] Every command shown in `README.md` and `docs/CLI.md` actually produces the
38
+ output shown. Run them against `spec/fixtures/rails_app`.
39
+ - [ ] Fixtures, docs and specs contain no private paths, usernames or secrets.
40
+ - [ ] `SCHEMA_VERSION` was incremented if the JSON contract changed.
41
+ - [ ] Any benchmark number quoted anywhere was actually measured.
42
+
43
+ ## The release
44
+
45
+ ```console
46
+ $ git switch main && git pull
47
+ $ $EDITOR lib/ripple_effect/version.rb # bump
48
+ $ $EDITOR CHANGELOG.md # date the entry, empty Unreleased
49
+ $ bundle install # refresh Gemfile.lock
50
+ $ bundle exec rake && bundle exec rake smoke
51
+
52
+ $ git add -A
53
+ $ git commit -m "Release v0.1.0"
54
+ $ git tag -s v0.1.0 -m "RippleEffect v0.1.0"
55
+ $ git push origin main --follow-tags
56
+ ```
57
+
58
+ Then publish:
59
+
60
+ ```console
61
+ $ gem build ripple_effect.gemspec
62
+ $ gem push ripple_effect-0.1.0.gem
63
+ ```
64
+
65
+ Finally, cut a GitHub release from the tag, pasting the changelog entry.
66
+
67
+ ## RubyGems account requirements
68
+
69
+ - **MFA is required** to own or push this gem. The gemspec sets
70
+ `rubygems_mfa_required`, so RubyGems will reject a push from an account without
71
+ it.
72
+ - Decide before the first release whether pushes are manual or via a trusted
73
+ publishing workflow. `.github/workflows/release.yml.example` sketches the
74
+ automated route and is **not enabled**, do not activate it, or add
75
+ a `RUBYGEMS_API_KEY` secret, until that decision is made. Trusted publishing
76
+ (OIDC) is preferred over a long-lived API key.
77
+
78
+ ## After releasing
79
+
80
+ - [ ] `gem install ripple_effect` from a clean machine and run `ripple-effect
81
+ doctor` against a real Rails app.
82
+ - [ ] Confirm the RubyGems page renders the README and links correctly.
83
+ - [ ] Open `## [Unreleased]` in `CHANGELOG.md` again.
84
+
85
+ ## Yanking
86
+
87
+ Yank only for a security problem or a broken package. A yank breaks
88
+ anyone who pinned the version.
89
+
90
+ ```console
91
+ $ gem yank ripple_effect -v 0.1.0
92
+ ```
93
+
94
+ Prefer releasing a fixed patch version.
data/docs/TESTING.md ADDED
@@ -0,0 +1,179 @@
1
+ # Testing
2
+
3
+ ```console
4
+ $ bundle exec rake # specs + RuboCop: the gate
5
+ $ bundle exec rspec # specs only
6
+ $ bundle exec rake smoke # build, isolated install, installed-CLI checks
7
+ $ bundle exec rake benchmark
8
+ ```
9
+
10
+ ## Layout
11
+
12
+ ```
13
+ spec/
14
+ ├── spec_helper.rb
15
+ ├── support/
16
+ │ ├── project_helpers.rb # throwaway projects, the shared fixture analyzer
17
+ │ └── git_helpers.rb # throwaway Git repositories
18
+ ├── unit/ # one file per class or extractor
19
+ ├── integration/ # fixture app, diff engine, JSON contract, CLI, docs
20
+ └── fixtures/
21
+ └── rails_app/ # a static, Rails-shaped application
22
+ ```
23
+
24
+ ### The fixture application
25
+
26
+ `spec/fixtures/rails_app/` is a small but realistic Rails-shaped app. It **never
27
+ boots**, it is analysis input.
28
+
29
+ It contains every relationship the product claims to understand:
30
+
31
+ ```
32
+ route POST /checkout -> OrdersController#create
33
+ OrdersController#create -> CheckoutService#call
34
+ CheckoutService#call -> BillingService#charge
35
+ CheckoutService#call -> InvoiceJob.perform_later
36
+ Order -> belongs_to :account
37
+ Order -> after_commit :send_receipt, if: :receiptable?
38
+ Order#send_receipt -> OrderMailer.receipt(...).deliver_later
39
+ Subscription#renew! -> RenewSubscriptionJob.perform_later
40
+ Order -> include Auditable
41
+ User -> delegate :display_name, to: :account
42
+ ```
43
+
44
+ > **Its `*_spec.rb` files are fixtures, not tests.** They exist so the
45
+ > test-relevance extractor has something to index. Both `.rspec` and the Rake task
46
+ > exclude them; do not remove those exclusions.
47
+
48
+ Indexing the fixture is the expensive part of the suite, so it is built once per
49
+ run and shared via `ProjectHelpers.fixture_analyzer`.
50
+
51
+ ## Writing specs
52
+
53
+ ### Against throwaway files
54
+
55
+ ```ruby
56
+ graph_for(
57
+ "app/models/order.rb" => "class Order\n belongs_to :account\nend\n",
58
+ "app/models/account.rb" => "class Account\nend\n"
59
+ ) do |graph|
60
+ expect(edges_between(graph, "Order", "Account").map(&:type)).to include(:association)
61
+ end
62
+ ```
63
+
64
+ `with_project`, `with_analyzer` and `graph_for` all accept either a braced hash or
65
+ brace-less `"path" => "source"` pairs, and clean up after themselves.
66
+
67
+ ### Against a real Git repository
68
+
69
+ Diff specs use real, temporary repositories rather than stubs: a stub would let
70
+ us pass against a Git that behaves differently from the user's.
71
+
72
+ ```ruby
73
+ with_git_project("app/a.rb" => "class A\n def call; end\nend\n") do |root|
74
+ write_files(root, "app/a.rb" => "class A\n def call\n 1\n end\nend\n")
75
+
76
+ expect(analyzer_for(root).diff(base: "main").changed_nodes.map(&:name)).to eq(["A#call"])
77
+ end
78
+ ```
79
+
80
+ Helpers: `write_files`, `delete_file`, `git`, `analyzer_for`.
81
+
82
+ ### Against the CLI
83
+
84
+ Two layers, on purpose:
85
+
86
+ - `spec/unit/cli_application_spec.rb` drives `Application#run` in-process with
87
+ `StringIO`. Fast, and coverage can see it.
88
+ - `spec/integration/cli_spec.rb` runs the **real executable** in a subprocess, so
89
+ argument parsing, exit codes and the stdout/stderr split are checked as a user
90
+ experiences them.
91
+
92
+ ## What must stay covered
93
+
94
+ The plan's bar, and the reason for each:
95
+
96
+ - **Determinism**, identical input, byte-identical output. Without it the JSON
97
+ contract cannot be snapshot-tested.
98
+ - **The JSON contract**, key order, schema version, no symbols, relative paths.
99
+ - **Every CLI command**, including exit codes and which stream output goes to.
100
+ - **Every extractor**, positive, negative, unresolvable, and config-off cases.
101
+ - **Diff mapping**, modified, added, deleted, class-body, renamed, staged,
102
+ unstaged, untracked, invalid ref, and a path containing spaces.
103
+ - **Safety**, that a malicious Git ref cannot reach a shell, that a symlink
104
+ pointing outside the root is refused, that YAML cannot construct objects, and
105
+ that indexing never executes the source.
106
+ - **Documentation consistency**, `spec/integration/documentation_spec.rb` asserts
107
+ that every edge type, node kind and diagnostic code is documented, that the
108
+ config example matches the built-in defaults, and that the README lists every
109
+ command.
110
+
111
+ ## Coverage
112
+
113
+ SimpleCov, with branch coverage, reporting to `coverage/`.
114
+
115
+ The release expectation is **≥ 90% line coverage** of our own code. Current:
116
+ **96%+ line**, **79%+ branch**, across 380+ examples.
117
+
118
+ Coverage is a floor, not a goal: a covered line that asserts nothing is worth
119
+ nothing.
120
+
121
+ ## The smoke test
122
+
123
+ `bundle exec rake smoke` catches what specs structurally cannot:
124
+
125
+ 1. `gem build`, must produce zero warnings.
126
+ 2. Install into a throwaway `GEM_HOME`, with every Bundler variable stripped, so
127
+ the *installed* gem is what runs.
128
+ 3. `require "ripple_effect"` and check the version.
129
+ 4. Run the installed `ripple-effect` binary: `--help`, `version`, `doctor`,
130
+ `inspect`, `graph`.
131
+ 5. Check the installed CLI's JSON output parses and carries a schema version.
132
+
133
+ This is how you find a file missing from the gemspec's file list, or a require
134
+ that only resolves from the source tree. **It must pass before any release.**
135
+
136
+ ## Ground truth against real applications
137
+
138
+ Fixtures prove the code does what it was written to do. They cannot tell you
139
+ whether the answers are *true* of a real codebase.
140
+
141
+ ```console
142
+ $ ruby script/ground_truth.rb ~/code/some-rails-app ~/code/another
143
+ ```
144
+
145
+ For each application it picks the most-referenced uniquely-named methods and
146
+ compares RippleEffect's reported dependents against every file that textually
147
+ mentions that method name.
148
+
149
+ The text search is a crude oracle on purpose. It effectively has no false
150
+ negatives: if a file uses the method, the name appears in it: so **anything
151
+ RippleEffect reports that grep did not find is a genuine false positive**, which
152
+ is the failure mode this project cares about most. The script exits non-zero if
153
+ it finds any.
154
+
155
+ It is not symmetric: grep also matches comments, same-named methods on unrelated
156
+ objects, and strings, so `unmatched` is a ceiling on what was missed rather than
157
+ a defect count. Conventional test files are counted separately, since a spec is
158
+ relevant because of where it lives, not because it names the method.
159
+
160
+ v0.1 was checked this way against six applications: lobsters, rubygems.org,
161
+ Solidus, Mastodon and two private production apps: covering Rails 7.0 to 8.1,
162
+ classic ERB, API-only, Haml, and an engine monorepo. **18 symbols, zero false
163
+ positives.**
164
+
165
+ ## CI
166
+
167
+ `.github/workflows/ci.yml` runs:
168
+
169
+ - **test**, Ruby 3.2, 3.3 and 3.4
170
+ - **rails-matrix**, representative Ruby/Rails pairs (7.0, 7.1, 7.2, 8.0, 8.1)
171
+ via per-version Gemfiles. This job installs the real Rails version and runs
172
+ `spec/integration/rails_dsl_compatibility_spec.rb`, which pins the DSL forms
173
+ each version uses: otherwise the matrix would prove nothing, since the rest
174
+ of the suite never loads Rails.
175
+ - **lint**, RuboCop on the current Ruby
176
+ - **package**, `gem build` plus the installed-gem smoke test
177
+
178
+ Benchmarks run locally, never as CI assertions: wall-clock numbers on shared CI
179
+ runners are noise, and a flaky performance test trains people to ignore failures.
data/exe/ripple-effect ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "ripple_effect"
5
+ require "ripple_effect/cli/application"
6
+
7
+ exit RippleEffect::CLI::Application.new.run(ARGV)