rails-doctor 0.1.0 → 0.3
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/CHANGELOG.md +13 -0
- data/README.md +4 -0
- data/docs/output-schema.md +15 -1
- data/examples/github-actions/rails-doctor.yml +7 -0
- data/examples/report.html +138 -20
- data/examples/report.json +313 -93
- data/examples/report.md +13 -5
- data/lib/rails_doctor/checks/rails_checks.rb +260 -22
- data/lib/rails_doctor/init/runner.rb +42 -1
- data/lib/rails_doctor/models.rb +11 -1
- data/lib/rails_doctor/reporters/html.rb +26 -1
- data/lib/rails_doctor/reporters/markdown.rb +14 -0
- data/lib/rails_doctor/reporters/terminal.rb +13 -0
- data/lib/rails_doctor/scanner.rb +41 -2
- data/lib/rails_doctor/version.rb +1 -1
- data/site/assets/rails-doctor-favicon.png +0 -0
- data/site/assets/rails-doctor-logo.png +0 -0
- data/site/index.html +27 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed3ab8bad2609f892947c536cb38286fd793a80954c3d319fa420a5fbe60363d
|
|
4
|
+
data.tar.gz: 5c56138a1015010e73aef14fee3dd8711db17fa65ad859d1278de14fe2770bcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a27e13091f164d1cf8381f2ff0a812425bc10aae66929f91e1c582f08381c247ae48348a98a265d4c25a9a9d746d6d63cf17913c070048362883c6d060e9d494
|
|
7
|
+
data.tar.gz: 0c5f55e894a9da56d9c13ceb31026c73034b44228d7cde6a167b4b58307d2ead1c72880b6bbd44d4d4a1d06d1c332a4efa3022fbfa545664dc420f8ac118eab5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3
|
|
4
|
+
|
|
5
|
+
- Reduced Rails route-check false positives for `resources` `only:`/`except:` options, namespace and `scope module:` blocks, inherited Devise controller actions, and private controller helper methods.
|
|
6
|
+
- Made `rails-doctor init --install` run Bundler through the active Ruby executable so rbenv/asdf/shimmed environments use the same Ruby context as Rails Doctor.
|
|
7
|
+
- Added npm install steps to generated GitHub Actions workflows when a `package-lock.json` is present, improving Rails app support for npm-managed frontend assets.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
- Improved Rails schema parsing for inline indexes, single-column indexes, scoped uniqueness validations, partial unique indexes, and string-backed foreign keys.
|
|
12
|
+
- Added normalized tool-run statuses and report notes so nonzero advisory tool exits are easier to interpret.
|
|
13
|
+
- Added deeper `rails-doctor init --profile deep` setup guidance with exact companion-tool install commands.
|
|
14
|
+
- Bumped the JSON output schema to `1.2` for tool-run status metadata.
|
|
15
|
+
|
|
3
16
|
## 0.1.0
|
|
4
17
|
|
|
5
18
|
- Initial Rails Doctor CLI and gem scaffold.
|
data/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="site/assets/rails-doctor-logo.png" alt="" width="220">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# Rails Doctor
|
|
2
6
|
|
|
3
7
|
[](https://github.com/joshsaintjacque/rails-doctor/actions/workflows/ci.yml)
|
data/docs/output-schema.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Output Schema
|
|
2
2
|
|
|
3
|
-
`rails-doctor --format json` emits schema version `1.
|
|
3
|
+
`rails-doctor --format json` emits schema version `1.2`.
|
|
4
4
|
|
|
5
5
|
Top-level fields:
|
|
6
6
|
|
|
@@ -54,4 +54,18 @@ Each finding contains:
|
|
|
54
54
|
|
|
55
55
|
Agents should treat `agent_instruction` and `suggested_commands` as guidance, not permission to mutate code. Mutation only occurs through an explicit agent workflow outside JSON report generation.
|
|
56
56
|
|
|
57
|
+
Each tool run contains:
|
|
58
|
+
|
|
59
|
+
- `name`
|
|
60
|
+
- `available`
|
|
61
|
+
- `skipped`
|
|
62
|
+
- `command`
|
|
63
|
+
- `exit_status`
|
|
64
|
+
- `duration_ms`
|
|
65
|
+
- `skip_reason`
|
|
66
|
+
- `status`
|
|
67
|
+
- `metadata`
|
|
68
|
+
|
|
69
|
+
`status` is one of `completed`, `completed_with_findings`, `completed_with_filtered_findings`, `adapter_failed`, `skipped`, or `nonzero_exit`. Raw tool exit codes are preserved in `exit_status`; `status` describes Rails Doctor's normalized interpretation for the current report. Nonzero exits may still be `completed` when the adapter parsed no actionable findings. `metadata.status_explanation` explains nonzero exits that need interpretation, and `metadata.parsed_finding_count` records how many findings the adapter produced before report filtering or deduplication.
|
|
70
|
+
|
|
57
71
|
`metadata` includes detected Ruby and Rails versions, current branch, optional base ref, and changed files used for changed-file scoring.
|
|
@@ -23,6 +23,13 @@ jobs:
|
|
|
23
23
|
with:
|
|
24
24
|
ruby-version: ${{ matrix.ruby }}
|
|
25
25
|
bundler-cache: true
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
if: ${{ hashFiles('package-lock.json') != '' }}
|
|
28
|
+
with:
|
|
29
|
+
node-version: "22"
|
|
30
|
+
cache: npm
|
|
31
|
+
- run: npm ci
|
|
32
|
+
if: ${{ hashFiles('package-lock.json') != '' }}
|
|
26
33
|
- run: bundle exec rails-doctor --profile ci --base origin/${{ github.base_ref || 'main' }} --format markdown --output tmp/rails-doctor/summary.md
|
|
27
34
|
- run: bundle exec rails-doctor --profile ci --base origin/${{ github.base_ref || 'main' }} --format json --output tmp/rails-doctor/report.json
|
|
28
35
|
- run: bundle exec rails-doctor --profile ci --base origin/${{ github.base_ref || 'main' }} --format html --output tmp/rails-doctor/report.html
|
data/examples/report.html
CHANGED
|
@@ -103,6 +103,12 @@
|
|
|
103
103
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
104
104
|
font-size: 13px;
|
|
105
105
|
}
|
|
106
|
+
.tool-note {
|
|
107
|
+
padding: 14px 0;
|
|
108
|
+
border-top: 1px solid var(--line);
|
|
109
|
+
}
|
|
110
|
+
.tool-note strong { display: block; margin-bottom: 4px; }
|
|
111
|
+
.tool-meta { color: var(--muted); font-size: 13px; }
|
|
106
112
|
details { border-top: 1px solid var(--line); padding: 14px 0; }
|
|
107
113
|
summary { cursor: pointer; font-weight: 700; }
|
|
108
114
|
pre { white-space: pre-wrap; overflow: auto; background: #171717; color: #f7f5f0; padding: 16px; }
|
|
@@ -141,7 +147,7 @@
|
|
|
141
147
|
<div class="score-ring">
|
|
142
148
|
<div class="score-label">Overall health</div>
|
|
143
149
|
<div class="score-number">0</div>
|
|
144
|
-
<div>Changed files: <strong>
|
|
150
|
+
<div>Changed files: <strong>86</strong> · Confidence: <strong>100%</strong></div>
|
|
145
151
|
</div>
|
|
146
152
|
</div>
|
|
147
153
|
</header>
|
|
@@ -152,7 +158,7 @@
|
|
|
152
158
|
<div class="metric"><span>Medium</span><strong>10</strong></div>
|
|
153
159
|
<div class="metric"><span>Coverage</span><strong>48.00%</strong></div>
|
|
154
160
|
<div class="metric"><span>Skipped</span><strong>0</strong></div>
|
|
155
|
-
<div class="metric"><span>Duration</span><strong>
|
|
161
|
+
<div class="metric"><span>Duration</span><strong>1497ms</strong></div>
|
|
156
162
|
</section>
|
|
157
163
|
|
|
158
164
|
<section class="section">
|
|
@@ -326,6 +332,43 @@
|
|
|
326
332
|
- medium: Update the deprecated API usage and add a regression test when behavior could change.</pre>
|
|
327
333
|
</section>
|
|
328
334
|
|
|
335
|
+
<section class="section">
|
|
336
|
+
<h2>Tool Run Notes</h2>
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
<div class="tool-note">
|
|
340
|
+
<strong>rubocop</strong>
|
|
341
|
+
<div class="tool-meta">Status: completed_with_findings · Exit: 1</div>
|
|
342
|
+
<p>The tool exited nonzero and Rails Doctor normalized actionable findings from its output.</p>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div class="tool-note">
|
|
346
|
+
<strong>brakeman</strong>
|
|
347
|
+
<div class="tool-meta">Status: completed_with_findings · Exit: 3</div>
|
|
348
|
+
<p>The tool exited nonzero and Rails Doctor normalized actionable findings from its output.</p>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<div class="tool-note">
|
|
352
|
+
<strong>bundler_audit</strong>
|
|
353
|
+
<div class="tool-meta">Status: completed_with_findings · Exit: 1</div>
|
|
354
|
+
<p>The tool exited nonzero and Rails Doctor normalized actionable findings from its output.</p>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<div class="tool-note">
|
|
358
|
+
<strong>reek</strong>
|
|
359
|
+
<div class="tool-meta">Status: completed_with_findings · Exit: 2</div>
|
|
360
|
+
<p>The tool exited nonzero and Rails Doctor normalized actionable findings from its output.</p>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div class="tool-note">
|
|
364
|
+
<strong>dependency_freshness</strong>
|
|
365
|
+
<div class="tool-meta">Status: completed_with_findings · Exit: 1</div>
|
|
366
|
+
<p>The tool exited nonzero and Rails Doctor normalized actionable findings from its output.</p>
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
</section>
|
|
371
|
+
|
|
329
372
|
<section class="section">
|
|
330
373
|
<h2>Findings</h2>
|
|
331
374
|
<div class="filters" role="toolbar" aria-label="Finding filters">
|
|
@@ -536,7 +579,71 @@
|
|
|
536
579
|
<td>dependency_freshness</td>
|
|
537
580
|
<td>dependency-freshness</td>
|
|
538
581
|
<td>Gemfile.lock</td>
|
|
539
|
-
<td><strong>
|
|
582
|
+
<td><strong>brakeman appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
583
|
+
</tr>
|
|
584
|
+
|
|
585
|
+
<tr data-severity="low">
|
|
586
|
+
<td><span class="chip low">low</span></td>
|
|
587
|
+
<td>dependency_freshness</td>
|
|
588
|
+
<td>dependency-freshness</td>
|
|
589
|
+
<td>Gemfile.lock</td>
|
|
590
|
+
<td><strong>bundler-audit appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
591
|
+
</tr>
|
|
592
|
+
|
|
593
|
+
<tr data-severity="low">
|
|
594
|
+
<td><span class="chip low">low</span></td>
|
|
595
|
+
<td>dependency_freshness</td>
|
|
596
|
+
<td>dependency-freshness</td>
|
|
597
|
+
<td>Gemfile.lock</td>
|
|
598
|
+
<td><strong>flay appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
599
|
+
</tr>
|
|
600
|
+
|
|
601
|
+
<tr data-severity="low">
|
|
602
|
+
<td><span class="chip low">low</span></td>
|
|
603
|
+
<td>dependency_freshness</td>
|
|
604
|
+
<td>dependency-freshness</td>
|
|
605
|
+
<td>Gemfile.lock</td>
|
|
606
|
+
<td><strong>flog appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
607
|
+
</tr>
|
|
608
|
+
|
|
609
|
+
<tr data-severity="low">
|
|
610
|
+
<td><span class="chip low">low</span></td>
|
|
611
|
+
<td>dependency_freshness</td>
|
|
612
|
+
<td>dependency-freshness</td>
|
|
613
|
+
<td>Gemfile.lock</td>
|
|
614
|
+
<td><strong>prosopite appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
615
|
+
</tr>
|
|
616
|
+
|
|
617
|
+
<tr data-severity="low">
|
|
618
|
+
<td><span class="chip low">low</span></td>
|
|
619
|
+
<td>dependency_freshness</td>
|
|
620
|
+
<td>dependency-freshness</td>
|
|
621
|
+
<td>Gemfile.lock</td>
|
|
622
|
+
<td><strong>rails appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
623
|
+
</tr>
|
|
624
|
+
|
|
625
|
+
<tr data-severity="low">
|
|
626
|
+
<td><span class="chip low">low</span></td>
|
|
627
|
+
<td>dependency_freshness</td>
|
|
628
|
+
<td>dependency-freshness</td>
|
|
629
|
+
<td>Gemfile.lock</td>
|
|
630
|
+
<td><strong>rubocop appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
631
|
+
</tr>
|
|
632
|
+
|
|
633
|
+
<tr data-severity="low">
|
|
634
|
+
<td><span class="chip low">low</span></td>
|
|
635
|
+
<td>dependency_freshness</td>
|
|
636
|
+
<td>dependency-freshness</td>
|
|
637
|
+
<td>Gemfile.lock</td>
|
|
638
|
+
<td><strong>rubocop-rails appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
639
|
+
</tr>
|
|
640
|
+
|
|
641
|
+
<tr data-severity="low">
|
|
642
|
+
<td><span class="chip low">low</span></td>
|
|
643
|
+
<td>dependency_freshness</td>
|
|
644
|
+
<td>dependency-freshness</td>
|
|
645
|
+
<td>Gemfile.lock</td>
|
|
646
|
+
<td><strong>strong_migrations appears to be outdated</strong><br>Review the update risk and update in a separate dependency-focused change.</td>
|
|
540
647
|
</tr>
|
|
541
648
|
|
|
542
649
|
</tbody>
|
|
@@ -569,11 +676,11 @@
|
|
|
569
676
|
|
|
570
677
|
<tr>
|
|
571
678
|
<td>Gemfile.lock</td>
|
|
572
|
-
<td>
|
|
573
|
-
<td>
|
|
574
|
-
<td>
|
|
575
|
-
<td>
|
|
576
|
-
<td>
|
|
679
|
+
<td>29</td>
|
|
680
|
+
<td>10</td>
|
|
681
|
+
<td>3</td>
|
|
682
|
+
<td>true</td>
|
|
683
|
+
<td>Changed file with 10 findings across dependency-security, dependency-freshness.</td>
|
|
577
684
|
</tr>
|
|
578
685
|
|
|
579
686
|
<tr>
|
|
@@ -628,51 +735,51 @@
|
|
|
628
735
|
|
|
629
736
|
|
|
630
737
|
<details>
|
|
631
|
-
<summary>rubocop</summary>
|
|
738
|
+
<summary>rubocop · completed_with_findings · exit 1</summary>
|
|
632
739
|
<pre>{"files":[{"path":"app/models/post.rb","offenses":[{"severity":"warning","message":"Use Time.current instead of Time.now.","cop_name":"Rails/TimeZone","location":{"line":6}}]}]}
|
|
633
740
|
\n</pre>
|
|
634
741
|
</details>
|
|
635
742
|
|
|
636
743
|
<details>
|
|
637
|
-
<summary>brakeman</summary>
|
|
744
|
+
<summary>brakeman · completed_with_findings · exit 3</summary>
|
|
638
745
|
<pre>{"warnings":[{"warning_type":"SQL Injection","message":"Possible SQL injection","file":"app/models/post.rb","line":8,"confidence":"High","fingerprint":"abc123","link":"https://brakemanscanner.org/docs/warning_types/sql_injection/"}]}
|
|
639
746
|
\n</pre>
|
|
640
747
|
</details>
|
|
641
748
|
|
|
642
749
|
<details>
|
|
643
|
-
<summary>bundler_audit</summary>
|
|
750
|
+
<summary>bundler_audit · completed_with_findings · exit 1</summary>
|
|
644
751
|
<pre>{"results":[{"gem":{"name":"rack"},"advisory":{"id":"CVE-2099-0001","title":"Example vulnerability","criticality":"high","url":"https://example.test/advisory"}}]}
|
|
645
752
|
\n</pre>
|
|
646
753
|
</details>
|
|
647
754
|
|
|
648
755
|
<details>
|
|
649
|
-
<summary>zeitwerk</summary>
|
|
756
|
+
<summary>zeitwerk · completed · exit 0</summary>
|
|
650
757
|
<pre>Hold on, I am eager loading the application.
|
|
651
758
|
All is good!
|
|
652
759
|
\n</pre>
|
|
653
760
|
</details>
|
|
654
761
|
|
|
655
762
|
<details>
|
|
656
|
-
<summary>reek</summary>
|
|
763
|
+
<summary>reek · completed_with_findings · exit 2</summary>
|
|
657
764
|
<pre>[{"context":"Post#publish!","lines":[4],"message":"has the smell of too many statements","smell_type":"TooManyStatements","source":"app/models/post.rb"}]
|
|
658
765
|
\n</pre>
|
|
659
766
|
</details>
|
|
660
767
|
|
|
661
768
|
<details>
|
|
662
|
-
<summary>test_runner</summary>
|
|
769
|
+
<summary>test_runner · completed_with_findings · exit 0</summary>
|
|
663
770
|
<pre>DEPRECATION WARNING: old API is deprecated
|
|
664
771
|
Prosopite: N+1 queries detected for Post => [:user]
|
|
665
772
|
\n</pre>
|
|
666
773
|
</details>
|
|
667
774
|
|
|
668
775
|
<details>
|
|
669
|
-
<summary>flog</summary>
|
|
776
|
+
<summary>flog · completed_with_findings · exit 0</summary>
|
|
670
777
|
<pre> 32.5 Post#publish! app/models/post.rb:4
|
|
671
778
|
\n</pre>
|
|
672
779
|
</details>
|
|
673
780
|
|
|
674
781
|
<details>
|
|
675
|
-
<summary>flay</summary>
|
|
782
|
+
<summary>flay · completed_with_findings · exit 0</summary>
|
|
676
783
|
<pre>Similar code found in :iter (mass = 42)
|
|
677
784
|
app/models/post.rb:4
|
|
678
785
|
app/models/user.rb:2
|
|
@@ -680,14 +787,25 @@ Prosopite: N+1 queries detected for Post => [:user]
|
|
|
680
787
|
</details>
|
|
681
788
|
|
|
682
789
|
<details>
|
|
683
|
-
<summary>dependency_freshness</summary>
|
|
684
|
-
<pre>
|
|
685
|
-
|
|
790
|
+
<summary>dependency_freshness · completed_with_findings · exit 1</summary>
|
|
791
|
+
<pre>
|
|
792
|
+
brakeman (newest 8.0.4, installed 7.0.0)
|
|
793
|
+
bundler-audit (newest 0.9.3, installed 0.9.0)
|
|
794
|
+
flay (newest 2.14.4, installed 2.13.3)
|
|
795
|
+
flog (newest 4.9.4, installed 4.8.0)
|
|
796
|
+
prosopite (newest 2.2.0, installed 1.4.0)
|
|
797
|
+
rails (newest 8.1.3, installed 8.0.0, requested ~> 8.0)
|
|
798
|
+
rubocop (newest 1.87.0, installed 1.70.0)
|
|
799
|
+
rubocop-rails (newest 2.35.3, installed 2.28.0)
|
|
800
|
+
strong_migrations (newest 2.8.0, installed 2.0.0)
|
|
801
|
+
\nFetching gem metadata from https://rubygems.org/..........
|
|
802
|
+
Resolving dependencies...
|
|
803
|
+
</pre>
|
|
686
804
|
</details>
|
|
687
805
|
|
|
688
806
|
</section>
|
|
689
807
|
</main>
|
|
690
|
-
<script type="application/json" id="rails-doctor-data">{"schema_version":"1.1","generated_at":"2026-05-30T20:00:00Z","project_root":"/path/to/app","profile":"deep","metadata":{"rails_app":true,"ruby_version":"3.4.1","rails_version":"8.0.0","changed_files":[]},"summary":{"profile":"deep","duration_ms":600,"finding_count":24,"severity_counts":{"medium":10,"critical":1,"high":10,"low":3},"skipped_tools":[],"score":{"overall":0,"changed_files":100,"confidence":100,"penalties":[{"id":"rd-gfnxqbg1qkup","severity":"medium","file":"app/models/post.rb","message":"Rails/TimeZone: Use Time.current instead of Time.now.","penalty":3.0},{"id":"rd-op6mq8hfnm99","severity":"critical","file":"app/models/post.rb","message":"SQL Injection: Possible SQL injection","penalty":15.0},{"id":"rd-habnfsz1zgdj","severity":"high","file":"Gemfile.lock","message":"rack: Example vulnerability","penalty":7.0},{"id":"rd-2er9zgsavtk6","severity":"medium","file":"app/models/post.rb","message":"TooManyStatements: has the smell of too many statements","penalty":3.0},{"id":"rd-ci7ub1199rvp","severity":"low","file":"config/initializers/strong_migrations.rb","message":"strong_migrations is installed but no initializer was found","penalty":0.75},{"id":"rd-iad8kr5ch7mh","severity":"high","file":"db/schema.rb","message":"posts.user_id has no index","penalty":7.0},{"id":"rd-nd2oek4kujwu","severity":"high","file":"app/models/user.rb","message":"users.email has a Rails uniqueness validation without a unique database index","penalty":5.25},{"id":"rd-p9vqylkxcybn","severity":"high","file":"config/routes.rb","message":"Routes reference missing ghosts_controller.rb","penalty":7.0},{"id":"rd-stmr7t8qmjti","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#create","penalty":7.0},{"id":"rd-c9eld5z4i9rf","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#destroy","penalty":7.0},{"id":"rd-hdn01soot3gf","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#edit","penalty":7.0},{"id":"rd-ygjxjocmpubf","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#new","penalty":7.0},{"id":"rd-q2wm1ehy5qn","severity":"medium","file":"app/controllers/posts_controller.rb","message":"posts#show has no matching template or explicit response","penalty":2.25},{"id":"rd-w4g0okt214t6","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#update","penalty":7.0},{"id":"rd-elkkq3siut9a","severity":"low","file":"app/controllers/posts_controller.rb","message":"posts#archive is not referenced by simple route analysis","penalty":0.4},{"id":"rd-4f2vjquwx6t4","severity":"medium","file":"app/models/post.rb","message":"1 TODO/FIXME/HACK marker in 10 lines","penalty":2.25},{"id":"rd-4dacnomu05u7","severity":"medium","file":null,"message":"DEPRECATION WARNING: old API is deprecated","penalty":2.25},{"id":"rd-bg982jjw6xt3","severity":"high","file":null,"message":"Prosopite: N+1 queries detected for Post =\u003e [:user]","penalty":5.25},{"id":"rd-qx8han58wk0o","severity":"medium","file":null,"message":"Line coverage 48.00% is below the 90.00% threshold","penalty":3.0},{"id":"rd-9ikgvif63uif","severity":"medium","file":"app/controllers/posts_controller.rb","message":"app/controllers/posts_controller.rb line coverage 22.22% is below the 80.00% per-file threshold","penalty":3.0},{"id":"rd-s83lai6gkb0r","severity":"medium","file":"app/models/post.rb","message":"app/models/post.rb line coverage 44.44% is below the 80.00% per-file threshold","penalty":3.0},{"id":"rd-l380zgdih1jq","severity":"medium","file":"app/models/post.rb","message":"High complexity score 32.5 for Post#publish!","penalty":2.25},{"id":"rd-xq812p6rik5z","severity":"medium","file":"app/models/post.rb","message":"Similar code group 1 across app/models/post.rb:4, app/models/user.rb:2","penalty":2.25},{"id":"rd-7wpu6grg34b2","severity":"low","file":"Gemfile.lock","message":"rack appears to be outdated","penalty":0.75}],"top_score_movers":[{"id":"rd-op6mq8hfnm99","severity":"critical","file":"app/models/post.rb","message":"SQL Injection: Possible SQL injection","penalty":15.0},{"id":"rd-habnfsz1zgdj","severity":"high","file":"Gemfile.lock","message":"rack: Example vulnerability","penalty":7.0},{"id":"rd-w4g0okt214t6","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#update","penalty":7.0},{"id":"rd-ygjxjocmpubf","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#new","penalty":7.0},{"id":"rd-iad8kr5ch7mh","severity":"high","file":"db/schema.rb","message":"posts.user_id has no index","penalty":7.0}]},"coverage":{"available":true,"status":"below_threshold","source":"simplecov","line_percent":48.0,"branch_percent":50.0,"thresholds":{"line":90.0,"file_line":80.0,"branch":null},"low_file_count":2,"changed_file_low_count":0}},"coverage":{"available":true,"status":"below_threshold","source":"simplecov","report_path":"coverage/.resultset.json","line_percent":48.0,"branch_percent":50.0,"covered_lines":12,"missed_lines":13,"total_lines":25,"covered_branches":1,"missed_branches":1,"total_branches":2,"thresholds":{"line":90.0,"file_line":80.0,"branch":null},"top_files":[{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true},{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true},{"file":"app/models/user.rb","line_percent":80.0,"covered_lines":4,"missed_lines":1,"total_lines":5,"below_threshold":false},{"file":"app/controllers/health_controller.rb","line_percent":100.0,"covered_lines":2,"missed_lines":0,"total_lines":2,"below_threshold":false}],"low_file_count":2,"changed_files_below_threshold":[],"metadata":{}},"findings":[{"id":"rd-gfnxqbg1qkup","severity":"medium","category":"lint","tool":"rubocop","file":"app/models/post.rb","line":6,"confidence":"high","message":"Rails/TimeZone: Use Time.current instead of Time.now.","recommendation":"Fix the RuboCop offense or document why this cop should be configured differently.","agent_instruction":"Apply a minimal change that satisfies Rails/TimeZone. Preserve behavior and run the relevant tests.","suggested_commands":["bundle exec rubocop app/models/post.rb"],"metadata":{}},{"id":"rd-op6mq8hfnm99","severity":"critical","category":"security","tool":"brakeman","file":"app/models/post.rb","line":8,"confidence":"high","message":"SQL Injection: Possible SQL injection","recommendation":"Review Brakeman guidance: https://brakemanscanner.org/docs/warning_types/sql_injection/","agent_instruction":"Fix this security finding with the smallest behavior-preserving change. Prefer framework-safe APIs and add regression tests.","suggested_commands":[],"metadata":{"fingerprint":"abc123"}},{"id":"rd-habnfsz1zgdj","severity":"high","category":"dependency-security","tool":"bundler_audit","file":"Gemfile.lock","confidence":"high","message":"rack: Example vulnerability","recommendation":"Update rack to a patched version and rerun Bundler Audit.","agent_instruction":"Update the vulnerable gem conservatively, refresh the lockfile, and run the test suite.","suggested_commands":["bundle update rack","bundle exec bundle-audit check"],"metadata":{"advisory":"CVE-2099-0001","url":"https://example.test/advisory"}},{"id":"rd-2er9zgsavtk6","severity":"medium","category":"code-smell","tool":"reek","file":"app/models/post.rb","line":4,"confidence":"high","message":"TooManyStatements: has the smell of too many statements","recommendation":"Refactor the local smell without broad behavior changes.","agent_instruction":"Refactor only the affected method/class. Preserve public behavior and add or run tests around the changed code.","suggested_commands":[],"metadata":{"context":"Post#publish!","smell_type":"TooManyStatements"}},{"id":"rd-ci7ub1199rvp","severity":"low","category":"migration-safety","tool":"strong_migrations","file":"config/initializers/strong_migrations.rb","confidence":"medium","message":"strong_migrations is installed but no initializer was found","recommendation":"Generate or review the Strong Migrations initializer so project-specific safety settings are explicit.","agent_instruction":"Add the standard Strong Migrations initializer only after checking project database adapter and deployment practices.","suggested_commands":[],"metadata":{}},{"id":"rd-iad8kr5ch7mh","severity":"high","category":"database-integrity","tool":"rails_checks","file":"db/schema.rb","confidence":"high","message":"posts.user_id has no index","recommendation":"Add an index for the foreign key column to avoid slow association lookups.","agent_instruction":"Create a migration that adds an index on posts.user_id. For PostgreSQL production apps, prefer a concurrent index path compatible with strong_migrations.","suggested_commands":["bin/rails generate migration AddIndexToPostsUserId"],"metadata":{}},{"id":"rd-nd2oek4kujwu","severity":"high","category":"database-integrity","tool":"rails_checks","file":"app/models/user.rb","confidence":"medium","message":"users.email has a Rails uniqueness validation without a unique database index","recommendation":"Back uniqueness validations with a unique index to prevent race-condition duplicates.","agent_instruction":"Add a unique index migration for users.email, handle existing duplicate data if necessary, and rerun tests.","suggested_commands":["bin/rails generate migration AddUniqueIndexToUsersEmail"],"metadata":{}},{"id":"rd-p9vqylkxcybn","severity":"high","category":"routing","tool":"rails_checks","file":"config/routes.rb","confidence":"high","message":"Routes reference missing ghosts_controller.rb","recommendation":"Create the controller or remove/rename the route.","agent_instruction":"Align routes with real controller names. Prefer removing stale routes over creating empty controllers.","suggested_commands":[],"metadata":{}},{"id":"rd-stmr7t8qmjti","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#create","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-c9eld5z4i9rf","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#destroy","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-hdn01soot3gf","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#edit","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-ygjxjocmpubf","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#new","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-q2wm1ehy5qn","severity":"medium","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"medium","message":"posts#show has no matching template or explicit response","recommendation":"Add a template or explicit render/redirect/head response.","agent_instruction":"Inspect the action intent. Add the missing view or explicit response and cover the route with a request/controller test.","suggested_commands":[],"metadata":{}},{"id":"rd-w4g0okt214t6","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#update","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-elkkq3siut9a","severity":"low","category":"dead-code","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"low","message":"posts#archive is not referenced by simple route analysis","recommendation":"Review whether this action is reached by custom routing or can be removed.","agent_instruction":"Do not remove this action automatically. First search routes, tests, links, and callers for dynamic usage.","suggested_commands":[],"metadata":{}},{"id":"rd-4f2vjquwx6t4","severity":"medium","category":"technical-debt","tool":"rails_checks","file":"app/models/post.rb","confidence":"medium","message":"1 TODO/FIXME/HACK marker in 10 lines","recommendation":"Convert stale markers into tracked work or resolve them while the context is fresh.","agent_instruction":"Do not delete markers without addressing or preserving the underlying work item. Prefer resolving changed-file markers.","suggested_commands":[],"metadata":{}},{"id":"rd-4dacnomu05u7","severity":"medium","category":"deprecation","tool":"test_runner","line":1,"confidence":"medium","message":"DEPRECATION WARNING: old API is deprecated","recommendation":"Resolve deprecation warnings before framework or gem upgrades make them failures.","agent_instruction":"Update the deprecated API usage and add a regression test when behavior could change.","suggested_commands":[],"metadata":{}},{"id":"rd-bg982jjw6xt3","severity":"high","category":"runtime-n-plus-one","tool":"test_runner","line":2,"confidence":"medium","message":"Prosopite: N+1 queries detected for Post =\u003e [:user]","recommendation":"Fix the N+1 query by eager loading or adjusting the query path exercised by tests.","agent_instruction":"Use includes/preload/eager_load or query restructuring. Verify with the same test command.","suggested_commands":["../../fake_bin/passing_tests"],"metadata":{}},{"id":"rd-qx8han58wk0o","severity":"medium","category":"test-coverage","tool":"test_coverage","confidence":"high","message":"Line coverage 48.00% is below the 90.00% threshold","recommendation":"Add tests for uncovered application code, starting with the lowest-coverage files.","agent_instruction":"Prioritize behavior tests for uncovered app/lib code. Use the coverage metadata to start with files below the configured threshold.","suggested_commands":[],"metadata":{"line_percent":48.0,"threshold":90.0,"low_files":[{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true},{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true}]}},{"id":"rd-9ikgvif63uif","severity":"medium","category":"test-coverage","tool":"test_coverage","file":"app/controllers/posts_controller.rb","confidence":"high","message":"app/controllers/posts_controller.rb line coverage 22.22% is below the 80.00% per-file threshold","recommendation":"Add focused tests that exercise the uncovered behavior in this file.","agent_instruction":"Add or update tests for this file before expanding the implementation. Prefer behavior-level tests that cover the missing branches or lines.","suggested_commands":[],"metadata":{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true,"threshold":80.0}},{"id":"rd-s83lai6gkb0r","severity":"medium","category":"test-coverage","tool":"test_coverage","file":"app/models/post.rb","confidence":"high","message":"app/models/post.rb line coverage 44.44% is below the 80.00% per-file threshold","recommendation":"Add focused tests that exercise the uncovered behavior in this file.","agent_instruction":"Add or update tests for this file before expanding the implementation. Prefer behavior-level tests that cover the missing branches or lines.","suggested_commands":[],"metadata":{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true,"threshold":80.0}},{"id":"rd-l380zgdih1jq","severity":"medium","category":"complexity","tool":"flog","file":"app/models/post.rb","line":4,"confidence":"medium","message":"High complexity score 32.5 for Post#publish!","recommendation":"Extract simpler methods or objects around the complex branch.","agent_instruction":"Reduce complexity with behavior-preserving extraction. Do not combine this with unrelated cleanup.","suggested_commands":[],"metadata":{"flog_score":32.5}},{"id":"rd-xq812p6rik5z","severity":"medium","category":"duplication","tool":"flay","file":"app/models/post.rb","line":4,"confidence":"medium","message":"Similar code group 1 across app/models/post.rb:4, app/models/user.rb:2","recommendation":"Review whether this duplication is intentional. Extract shared behavior only if the abstraction is clear.","agent_instruction":"Do not blindly abstract. Compare the duplicated code paths, preserve semantics, and add tests if extracting shared code.","suggested_commands":[],"metadata":{"locations":["app/models/post.rb:4","app/models/user.rb:2"]}},{"id":"rd-7wpu6grg34b2","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"rack appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update rack conservatively and run the full test suite.","suggested_commands":["bundle update rack"],"metadata":{}}],"hotspots":[{"file":"app/controllers/posts_controller.rb","score":42,"finding_count":8,"churn":0,"changed":false,"categories":["dead-code","routing","test-coverage"],"summary":"Inherited file with 8 findings across routing, dead-code, test-coverage."},{"file":"app/models/post.rb","score":33,"finding_count":7,"churn":0,"changed":false,"categories":["code-smell","complexity","duplication","lint","security","technical-debt","test-coverage"],"summary":"Inherited file with 7 findings across lint, security, code-smell, technical-debt, test-coverage, complexity, duplication."},{"file":"Gemfile.lock","score":8,"finding_count":2,"churn":0,"changed":false,"categories":["dependency-freshness","dependency-security"],"summary":"Inherited file with 2 findings across dependency-security, dependency-freshness."},{"file":"db/schema.rb","score":7,"finding_count":1,"churn":0,"changed":false,"categories":["database-integrity"],"summary":"Inherited file with 1 finding across database-integrity."},{"file":"app/models/user.rb","score":7,"finding_count":1,"churn":0,"changed":false,"categories":["database-integrity"],"summary":"Inherited file with 1 finding across database-integrity."},{"file":"config/routes.rb","score":7,"finding_count":1,"churn":0,"changed":false,"categories":["routing"],"summary":"Inherited file with 1 finding across routing."},{"file":"config/initializers/strong_migrations.rb","score":1,"finding_count":1,"churn":0,"changed":false,"categories":["migration-safety"],"summary":"Inherited file with 1 finding across migration-safety."}],"tool_runs":[{"name":"rubocop","available":true,"skipped":false,"command":"../../fake_bin/rubocop","exit_status":1,"duration_ms":50,"metadata":{}},{"name":"brakeman","available":true,"skipped":false,"command":"../../fake_bin/brakeman","exit_status":3,"duration_ms":50,"metadata":{}},{"name":"bundler_audit","available":true,"skipped":false,"command":"../../fake_bin/bundle-audit","exit_status":1,"duration_ms":50,"metadata":{}},{"name":"zeitwerk","available":true,"skipped":false,"command":"../../fake_bin/rails zeitwerk:check","exit_status":0,"duration_ms":50,"metadata":{}},{"name":"reek","available":true,"skipped":false,"command":"../../fake_bin/reek","exit_status":2,"duration_ms":50,"metadata":{}},{"name":"strong_migrations","available":true,"skipped":false,"exit_status":0,"duration_ms":50,"metadata":{"coverage":"strong_migrations gem detected","initializer_present":false}},{"name":"rails_checks","available":true,"skipped":false,"exit_status":0,"duration_ms":50,"metadata":{"checks":["indexes","uniqueness","routes","views","size","todos","tests","coverage-gaps"]}},{"name":"test_runner","available":true,"skipped":false,"command":"../../fake_bin/passing_tests","exit_status":0,"duration_ms":50,"metadata":{}},{"name":"test_coverage","available":true,"skipped":false,"exit_status":0,"duration_ms":1,"metadata":{"coverage":{"available":true,"status":"below_threshold","source":"simplecov","report_path":"coverage/.resultset.json","line_percent":48.0,"branch_percent":50.0,"covered_lines":12,"missed_lines":13,"total_lines":25,"covered_branches":1,"missed_branches":1,"total_branches":2,"thresholds":{"line":90.0,"file_line":80.0,"branch":null},"top_files":[{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true},{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true},{"file":"app/models/user.rb","line_percent":80.0,"covered_lines":4,"missed_lines":1,"total_lines":5,"below_threshold":false},{"file":"app/controllers/health_controller.rb","line_percent":100.0,"covered_lines":2,"missed_lines":0,"total_lines":2,"below_threshold":false}],"low_file_count":2,"changed_files_below_threshold":[],"metadata":{}}}},{"name":"flog","available":true,"skipped":false,"command":"../../fake_bin/flog","exit_status":0,"duration_ms":50,"metadata":{}},{"name":"flay","available":true,"skipped":false,"command":"../../fake_bin/flay","exit_status":0,"duration_ms":50,"metadata":{}},{"name":"dependency_freshness","available":true,"skipped":false,"command":"bundle outdated --parseable","exit_status":1,"duration_ms":50,"metadata":{}}]}</script>
|
|
808
|
+
<script type="application/json" id="rails-doctor-data">{"schema_version":"1.2","generated_at":"2026-06-02T00:44:54-07:00","project_root":"/Users/joshs/.codex/worktrees/86c9/rails-doctor/test/fixtures/rails_apps/sample_app","profile":"deep","metadata":{"rails_app":true,"ruby_version":"3.4.1","rails_version":"8.0.0","branch":"codex/rails-doctor-feedback","changed_files":["CHANGELOG.md","Gemfile.lock","examples/report.html","examples/report.json","examples/report.md","lib/rails_doctor/version.rb"]},"summary":{"profile":"deep","duration_ms":1497,"finding_count":32,"severity_counts":{"medium":10,"critical":1,"high":10,"low":11},"skipped_tools":[],"score":{"overall":0,"changed_files":86,"confidence":100,"penalties":[{"id":"rd-umvcmo2jx3ej","severity":"medium","file":"app/models/post.rb","message":"Rails/TimeZone: Use Time.current instead of Time.now.","penalty":3.0},{"id":"rd-1012b90h4skt","severity":"critical","file":"app/models/post.rb","message":"SQL Injection: Possible SQL injection","penalty":15.0},{"id":"rd-euhneannuy8g","severity":"high","file":"Gemfile.lock","message":"rack: Example vulnerability","penalty":7.0},{"id":"rd-m3d20263f351","severity":"medium","file":"app/models/post.rb","message":"TooManyStatements: has the smell of too many statements","penalty":3.0},{"id":"rd-jqcjizfjy1bj","severity":"low","file":"config/initializers/strong_migrations.rb","message":"strong_migrations is installed but no initializer was found","penalty":0.75},{"id":"rd-vxswmqgnex1","severity":"high","file":"db/schema.rb","message":"posts.user_id has no index","penalty":7.0},{"id":"rd-eeg09srhctrg","severity":"high","file":"app/models/user.rb","message":"users.email has a Rails uniqueness validation without a unique database index","penalty":5.25},{"id":"rd-viv39xpaou4q","severity":"high","file":"config/routes.rb","message":"Routes reference missing ghosts_controller.rb","penalty":7.0},{"id":"rd-35hpmof55rrc","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#create","penalty":7.0},{"id":"rd-43ws5mpdqpau","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#destroy","penalty":7.0},{"id":"rd-jq42qh002niq","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#edit","penalty":7.0},{"id":"rd-3zze50gr5bhz","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#new","penalty":7.0},{"id":"rd-83ah00od1xdk","severity":"medium","file":"app/controllers/posts_controller.rb","message":"posts#show has no matching template or explicit response","penalty":2.25},{"id":"rd-21pwdm41rqyx","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#update","penalty":7.0},{"id":"rd-dnpx28helhlp","severity":"low","file":"app/controllers/posts_controller.rb","message":"posts#archive is not referenced by simple route analysis","penalty":0.4},{"id":"rd-4q2ytrqtdk55","severity":"medium","file":"app/models/post.rb","message":"1 TODO/FIXME/HACK marker in 10 lines","penalty":2.25},{"id":"rd-fdpj1pz9wcjs","severity":"medium","file":null,"message":"DEPRECATION WARNING: old API is deprecated","penalty":2.25},{"id":"rd-sniv3674shv","severity":"high","file":null,"message":"Prosopite: N+1 queries detected for Post =\u003e [:user]","penalty":5.25},{"id":"rd-b8krrpfcahfo","severity":"medium","file":null,"message":"Line coverage 48.00% is below the 90.00% threshold","penalty":3.0},{"id":"rd-yjazqvzca79b","severity":"medium","file":"app/controllers/posts_controller.rb","message":"app/controllers/posts_controller.rb line coverage 22.22% is below the 80.00% per-file threshold","penalty":3.0},{"id":"rd-mqmakuk5efa3","severity":"medium","file":"app/models/post.rb","message":"app/models/post.rb line coverage 44.44% is below the 80.00% per-file threshold","penalty":3.0},{"id":"rd-5j6a8yb13paz","severity":"medium","file":"app/models/post.rb","message":"High complexity score 32.5 for Post#publish!","penalty":2.25},{"id":"rd-2h34p5sc8thu","severity":"medium","file":"app/models/post.rb","message":"Similar code group 1 across app/models/post.rb:4, app/models/user.rb:2","penalty":2.25},{"id":"rd-m1ly6yvtaw5i","severity":"low","file":"Gemfile.lock","message":"brakeman appears to be outdated","penalty":0.75},{"id":"rd-5c65cl20d67s","severity":"low","file":"Gemfile.lock","message":"bundler-audit appears to be outdated","penalty":0.75},{"id":"rd-rcxs7mw0ony6","severity":"low","file":"Gemfile.lock","message":"flay appears to be outdated","penalty":0.75},{"id":"rd-molifhqxqz88","severity":"low","file":"Gemfile.lock","message":"flog appears to be outdated","penalty":0.75},{"id":"rd-6hx5xwejxac7","severity":"low","file":"Gemfile.lock","message":"prosopite appears to be outdated","penalty":0.75},{"id":"rd-196gusw92scx","severity":"low","file":"Gemfile.lock","message":"rails appears to be outdated","penalty":0.75},{"id":"rd-3wa8qni8z72s","severity":"low","file":"Gemfile.lock","message":"rubocop appears to be outdated","penalty":0.75},{"id":"rd-q5605256tdqy","severity":"low","file":"Gemfile.lock","message":"rubocop-rails appears to be outdated","penalty":0.75},{"id":"rd-qslk943qk2yb","severity":"low","file":"Gemfile.lock","message":"strong_migrations appears to be outdated","penalty":0.75}],"top_score_movers":[{"id":"rd-1012b90h4skt","severity":"critical","file":"app/models/post.rb","message":"SQL Injection: Possible SQL injection","penalty":15.0},{"id":"rd-euhneannuy8g","severity":"high","file":"Gemfile.lock","message":"rack: Example vulnerability","penalty":7.0},{"id":"rd-21pwdm41rqyx","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#update","penalty":7.0},{"id":"rd-3zze50gr5bhz","severity":"high","file":"app/controllers/posts_controller.rb","message":"Route points to missing posts#new","penalty":7.0},{"id":"rd-vxswmqgnex1","severity":"high","file":"db/schema.rb","message":"posts.user_id has no index","penalty":7.0}]},"coverage":{"available":true,"status":"below_threshold","source":"simplecov","line_percent":48.0,"branch_percent":50.0,"thresholds":{"line":90.0,"file_line":80.0,"branch":null},"low_file_count":2,"changed_file_low_count":0}},"coverage":{"available":true,"status":"below_threshold","source":"simplecov","report_path":"coverage/.resultset.json","line_percent":48.0,"branch_percent":50.0,"covered_lines":12,"missed_lines":13,"total_lines":25,"covered_branches":1,"missed_branches":1,"total_branches":2,"thresholds":{"line":90.0,"file_line":80.0,"branch":null},"top_files":[{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true},{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true},{"file":"app/models/user.rb","line_percent":80.0,"covered_lines":4,"missed_lines":1,"total_lines":5,"below_threshold":false},{"file":"app/controllers/health_controller.rb","line_percent":100.0,"covered_lines":2,"missed_lines":0,"total_lines":2,"below_threshold":false}],"low_file_count":2,"changed_files_below_threshold":[],"metadata":{}},"findings":[{"id":"rd-umvcmo2jx3ej","severity":"medium","category":"lint","tool":"rubocop","file":"app/models/post.rb","line":6,"confidence":"high","message":"Rails/TimeZone: Use Time.current instead of Time.now.","recommendation":"Fix the RuboCop offense or document why this cop should be configured differently.","agent_instruction":"Apply a minimal change that satisfies Rails/TimeZone. Preserve behavior and run the relevant tests.","suggested_commands":["bundle exec rubocop app/models/post.rb"],"metadata":{}},{"id":"rd-1012b90h4skt","severity":"critical","category":"security","tool":"brakeman","file":"app/models/post.rb","line":8,"confidence":"high","message":"SQL Injection: Possible SQL injection","recommendation":"Review Brakeman guidance: https://brakemanscanner.org/docs/warning_types/sql_injection/","agent_instruction":"Fix this security finding with the smallest behavior-preserving change. Prefer framework-safe APIs and add regression tests.","suggested_commands":[],"metadata":{"fingerprint":"abc123"}},{"id":"rd-euhneannuy8g","severity":"high","category":"dependency-security","tool":"bundler_audit","file":"Gemfile.lock","confidence":"high","message":"rack: Example vulnerability","recommendation":"Update rack to a patched version and rerun Bundler Audit.","agent_instruction":"Update the vulnerable gem conservatively, refresh the lockfile, and run the test suite.","suggested_commands":["bundle update rack","bundle exec bundle-audit check"],"metadata":{"advisory":"CVE-2099-0001","url":"https://example.test/advisory"}},{"id":"rd-m3d20263f351","severity":"medium","category":"code-smell","tool":"reek","file":"app/models/post.rb","line":4,"confidence":"high","message":"TooManyStatements: has the smell of too many statements","recommendation":"Refactor the local smell without broad behavior changes.","agent_instruction":"Refactor only the affected method/class. Preserve public behavior and add or run tests around the changed code.","suggested_commands":[],"metadata":{"context":"Post#publish!","smell_type":"TooManyStatements"}},{"id":"rd-jqcjizfjy1bj","severity":"low","category":"migration-safety","tool":"strong_migrations","file":"config/initializers/strong_migrations.rb","confidence":"medium","message":"strong_migrations is installed but no initializer was found","recommendation":"Generate or review the Strong Migrations initializer so project-specific safety settings are explicit.","agent_instruction":"Add the standard Strong Migrations initializer only after checking project database adapter and deployment practices.","suggested_commands":[],"metadata":{}},{"id":"rd-vxswmqgnex1","severity":"high","category":"database-integrity","tool":"rails_checks","file":"db/schema.rb","confidence":"high","message":"posts.user_id has no index","recommendation":"Add an index for the foreign key column to avoid slow association lookups.","agent_instruction":"Create a migration that adds an index on posts.user_id. For PostgreSQL production apps, prefer a concurrent index path compatible with strong_migrations.","suggested_commands":["bin/rails generate migration AddIndexToPostsUserId"],"metadata":{}},{"id":"rd-eeg09srhctrg","severity":"high","category":"database-integrity","tool":"rails_checks","file":"app/models/user.rb","confidence":"medium","message":"users.email has a Rails uniqueness validation without a unique database index","recommendation":"Back uniqueness validations with a unique index to prevent race-condition duplicates.","agent_instruction":"Add a unique index migration for users.email, handle existing duplicate data if necessary, and rerun tests.","suggested_commands":["bin/rails generate migration AddUniqueIndexToUsersEmail"],"metadata":{}},{"id":"rd-viv39xpaou4q","severity":"high","category":"routing","tool":"rails_checks","file":"config/routes.rb","confidence":"high","message":"Routes reference missing ghosts_controller.rb","recommendation":"Create the controller or remove/rename the route.","agent_instruction":"Align routes with real controller names. Prefer removing stale routes over creating empty controllers.","suggested_commands":[],"metadata":{}},{"id":"rd-35hpmof55rrc","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#create","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-43ws5mpdqpau","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#destroy","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-jq42qh002niq","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#edit","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-3zze50gr5bhz","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#new","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-83ah00od1xdk","severity":"medium","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"medium","message":"posts#show has no matching template or explicit response","recommendation":"Add a template or explicit render/redirect/head response.","agent_instruction":"Inspect the action intent. Add the missing view or explicit response and cover the route with a request/controller test.","suggested_commands":[],"metadata":{}},{"id":"rd-21pwdm41rqyx","severity":"high","category":"routing","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"high","message":"Route points to missing posts#update","recommendation":"Implement the action or update/remove the route.","agent_instruction":"Do not add an empty action. Determine the intended route behavior, then implement or remove the stale route.","suggested_commands":[],"metadata":{}},{"id":"rd-dnpx28helhlp","severity":"low","category":"dead-code","tool":"rails_checks","file":"app/controllers/posts_controller.rb","confidence":"low","message":"posts#archive is not referenced by simple route analysis","recommendation":"Review whether this action is reached by custom routing or can be removed.","agent_instruction":"Do not remove this action automatically. First search routes, tests, links, and callers for dynamic usage.","suggested_commands":[],"metadata":{}},{"id":"rd-4q2ytrqtdk55","severity":"medium","category":"technical-debt","tool":"rails_checks","file":"app/models/post.rb","confidence":"medium","message":"1 TODO/FIXME/HACK marker in 10 lines","recommendation":"Convert stale markers into tracked work or resolve them while the context is fresh.","agent_instruction":"Do not delete markers without addressing or preserving the underlying work item. Prefer resolving changed-file markers.","suggested_commands":[],"metadata":{}},{"id":"rd-fdpj1pz9wcjs","severity":"medium","category":"deprecation","tool":"test_runner","line":1,"confidence":"medium","message":"DEPRECATION WARNING: old API is deprecated","recommendation":"Resolve deprecation warnings before framework or gem upgrades make them failures.","agent_instruction":"Update the deprecated API usage and add a regression test when behavior could change.","suggested_commands":[],"metadata":{}},{"id":"rd-sniv3674shv","severity":"high","category":"runtime-n-plus-one","tool":"test_runner","line":2,"confidence":"medium","message":"Prosopite: N+1 queries detected for Post =\u003e [:user]","recommendation":"Fix the N+1 query by eager loading or adjusting the query path exercised by tests.","agent_instruction":"Use includes/preload/eager_load or query restructuring. Verify with the same test command.","suggested_commands":["../../fake_bin/passing_tests"],"metadata":{}},{"id":"rd-b8krrpfcahfo","severity":"medium","category":"test-coverage","tool":"test_coverage","confidence":"high","message":"Line coverage 48.00% is below the 90.00% threshold","recommendation":"Add tests for uncovered application code, starting with the lowest-coverage files.","agent_instruction":"Prioritize behavior tests for uncovered app/lib code. Use the coverage metadata to start with files below the configured threshold.","suggested_commands":[],"metadata":{"line_percent":48.0,"threshold":90.0,"low_files":[{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true},{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true}]}},{"id":"rd-yjazqvzca79b","severity":"medium","category":"test-coverage","tool":"test_coverage","file":"app/controllers/posts_controller.rb","confidence":"high","message":"app/controllers/posts_controller.rb line coverage 22.22% is below the 80.00% per-file threshold","recommendation":"Add focused tests that exercise the uncovered behavior in this file.","agent_instruction":"Add or update tests for this file before expanding the implementation. Prefer behavior-level tests that cover the missing branches or lines.","suggested_commands":[],"metadata":{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true,"threshold":80.0}},{"id":"rd-mqmakuk5efa3","severity":"medium","category":"test-coverage","tool":"test_coverage","file":"app/models/post.rb","confidence":"high","message":"app/models/post.rb line coverage 44.44% is below the 80.00% per-file threshold","recommendation":"Add focused tests that exercise the uncovered behavior in this file.","agent_instruction":"Add or update tests for this file before expanding the implementation. Prefer behavior-level tests that cover the missing branches or lines.","suggested_commands":[],"metadata":{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true,"threshold":80.0}},{"id":"rd-5j6a8yb13paz","severity":"medium","category":"complexity","tool":"flog","file":"app/models/post.rb","line":4,"confidence":"medium","message":"High complexity score 32.5 for Post#publish!","recommendation":"Extract simpler methods or objects around the complex branch.","agent_instruction":"Reduce complexity with behavior-preserving extraction. Do not combine this with unrelated cleanup.","suggested_commands":[],"metadata":{"flog_score":32.5}},{"id":"rd-2h34p5sc8thu","severity":"medium","category":"duplication","tool":"flay","file":"app/models/post.rb","line":4,"confidence":"medium","message":"Similar code group 1 across app/models/post.rb:4, app/models/user.rb:2","recommendation":"Review whether this duplication is intentional. Extract shared behavior only if the abstraction is clear.","agent_instruction":"Do not blindly abstract. Compare the duplicated code paths, preserve semantics, and add tests if extracting shared code.","suggested_commands":[],"metadata":{"locations":["app/models/post.rb:4","app/models/user.rb:2"]}},{"id":"rd-m1ly6yvtaw5i","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"brakeman appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update brakeman conservatively and run the full test suite.","suggested_commands":["bundle update brakeman"],"metadata":{}},{"id":"rd-5c65cl20d67s","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"bundler-audit appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update bundler-audit conservatively and run the full test suite.","suggested_commands":["bundle update bundler-audit"],"metadata":{}},{"id":"rd-rcxs7mw0ony6","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"flay appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update flay conservatively and run the full test suite.","suggested_commands":["bundle update flay"],"metadata":{}},{"id":"rd-molifhqxqz88","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"flog appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update flog conservatively and run the full test suite.","suggested_commands":["bundle update flog"],"metadata":{}},{"id":"rd-6hx5xwejxac7","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"prosopite appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update prosopite conservatively and run the full test suite.","suggested_commands":["bundle update prosopite"],"metadata":{}},{"id":"rd-196gusw92scx","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"rails appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update rails conservatively and run the full test suite.","suggested_commands":["bundle update rails"],"metadata":{}},{"id":"rd-3wa8qni8z72s","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"rubocop appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update rubocop conservatively and run the full test suite.","suggested_commands":["bundle update rubocop"],"metadata":{}},{"id":"rd-q5605256tdqy","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"rubocop-rails appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update rubocop-rails conservatively and run the full test suite.","suggested_commands":["bundle update rubocop-rails"],"metadata":{}},{"id":"rd-qslk943qk2yb","severity":"low","category":"dependency-freshness","tool":"dependency_freshness","file":"Gemfile.lock","confidence":"medium","message":"strong_migrations appears to be outdated","recommendation":"Review the update risk and update in a separate dependency-focused change.","agent_instruction":"Do not batch this with feature work. Update strong_migrations conservatively and run the full test suite.","suggested_commands":["bundle update strong_migrations"],"metadata":{}}],"hotspots":[{"file":"app/controllers/posts_controller.rb","score":42,"finding_count":8,"churn":0,"changed":false,"categories":["dead-code","routing","test-coverage"],"summary":"Inherited file with 8 findings across routing, dead-code, test-coverage."},{"file":"app/models/post.rb","score":33,"finding_count":7,"churn":0,"changed":false,"categories":["code-smell","complexity","duplication","lint","security","technical-debt","test-coverage"],"summary":"Inherited file with 7 findings across lint, security, code-smell, technical-debt, test-coverage, complexity, duplication."},{"file":"Gemfile.lock","score":29,"finding_count":10,"churn":3,"changed":true,"categories":["dependency-freshness","dependency-security"],"summary":"Changed file with 10 findings across dependency-security, dependency-freshness."},{"file":"db/schema.rb","score":7,"finding_count":1,"churn":0,"changed":false,"categories":["database-integrity"],"summary":"Inherited file with 1 finding across database-integrity."},{"file":"app/models/user.rb","score":7,"finding_count":1,"churn":0,"changed":false,"categories":["database-integrity"],"summary":"Inherited file with 1 finding across database-integrity."},{"file":"config/routes.rb","score":7,"finding_count":1,"churn":0,"changed":false,"categories":["routing"],"summary":"Inherited file with 1 finding across routing."},{"file":"config/initializers/strong_migrations.rb","score":1,"finding_count":1,"churn":0,"changed":false,"categories":["migration-safety"],"summary":"Inherited file with 1 finding across migration-safety."}],"tool_runs":[{"name":"rubocop","available":true,"skipped":false,"command":"../../fake_bin/rubocop","exit_status":1,"duration_ms":35,"status":"completed_with_findings","metadata":{"parsed_finding_count":1,"status_explanation":"The tool exited nonzero and Rails Doctor normalized actionable findings from its output."}},{"name":"brakeman","available":true,"skipped":false,"command":"../../fake_bin/brakeman","exit_status":3,"duration_ms":33,"status":"completed_with_findings","metadata":{"parsed_finding_count":1,"status_explanation":"The tool exited nonzero and Rails Doctor normalized actionable findings from its output."}},{"name":"bundler_audit","available":true,"skipped":false,"command":"../../fake_bin/bundle-audit","exit_status":1,"duration_ms":34,"status":"completed_with_findings","metadata":{"parsed_finding_count":1,"status_explanation":"The tool exited nonzero and Rails Doctor normalized actionable findings from its output."}},{"name":"zeitwerk","available":true,"skipped":false,"command":"../../fake_bin/rails zeitwerk:check","exit_status":0,"duration_ms":30,"status":"completed","metadata":{"parsed_finding_count":0}},{"name":"reek","available":true,"skipped":false,"command":"../../fake_bin/reek","exit_status":2,"duration_ms":33,"status":"completed_with_findings","metadata":{"parsed_finding_count":1,"status_explanation":"The tool exited nonzero and Rails Doctor normalized actionable findings from its output."}},{"name":"strong_migrations","available":true,"skipped":false,"exit_status":0,"status":"completed_with_findings","metadata":{"coverage":"strong_migrations gem detected","initializer_present":false,"parsed_finding_count":1}},{"name":"rails_checks","available":true,"skipped":false,"exit_status":0,"status":"completed_with_findings","metadata":{"checks":["indexes","uniqueness","routes","views","size","todos","tests","coverage-gaps"],"parsed_finding_count":11}},{"name":"test_runner","available":true,"skipped":false,"command":"../../fake_bin/passing_tests","exit_status":0,"duration_ms":30,"status":"completed_with_findings","metadata":{"parsed_finding_count":2}},{"name":"test_coverage","available":true,"skipped":false,"exit_status":0,"duration_ms":0,"status":"completed_with_findings","metadata":{"coverage":{"available":true,"status":"below_threshold","source":"simplecov","report_path":"coverage/.resultset.json","line_percent":48.0,"branch_percent":50.0,"covered_lines":12,"missed_lines":13,"total_lines":25,"covered_branches":1,"missed_branches":1,"total_branches":2,"thresholds":{"line":90.0,"file_line":80.0,"branch":null},"top_files":[{"file":"app/controllers/posts_controller.rb","line_percent":22.22,"covered_lines":2,"missed_lines":7,"total_lines":9,"below_threshold":true},{"file":"app/models/post.rb","line_percent":44.44,"covered_lines":4,"missed_lines":5,"total_lines":9,"branch_percent":50.0,"covered_branches":1,"missed_branches":1,"total_branches":2,"below_threshold":true},{"file":"app/models/user.rb","line_percent":80.0,"covered_lines":4,"missed_lines":1,"total_lines":5,"below_threshold":false},{"file":"app/controllers/health_controller.rb","line_percent":100.0,"covered_lines":2,"missed_lines":0,"total_lines":2,"below_threshold":false}],"low_file_count":2,"changed_files_below_threshold":[],"metadata":{}},"parsed_finding_count":3}},{"name":"flog","available":true,"skipped":false,"command":"../../fake_bin/flog","exit_status":0,"duration_ms":30,"status":"completed_with_findings","metadata":{"parsed_finding_count":1}},{"name":"flay","available":true,"skipped":false,"command":"../../fake_bin/flay","exit_status":0,"duration_ms":30,"status":"completed_with_findings","metadata":{"parsed_finding_count":1}},{"name":"dependency_freshness","available":true,"skipped":false,"command":"bundle outdated --parseable","exit_status":1,"duration_ms":1222,"status":"completed_with_findings","metadata":{"parsed_finding_count":9,"status_explanation":"The tool exited nonzero and Rails Doctor normalized actionable findings from its output."}}]}</script>
|
|
691
809
|
<script>
|
|
692
810
|
document.querySelectorAll("[data-filter]").forEach((button) => {
|
|
693
811
|
button.addEventListener("click", () => {
|