rigortype 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/docs/manual/02-cli-reference.md +2 -1
- data/docs/manual/08-skills.md +21 -0
- data/lib/rigor/cli/coverage_command.rb +42 -10
- data/lib/rigor/cli/skill_command.rb +52 -1
- data/lib/rigor/environment/rbs_loader.rb +28 -0
- data/lib/rigor/inference/statement_evaluator.rb +0 -4
- data/lib/rigor/sig_gen/generator.rb +25 -0
- data/lib/rigor/sig_gen/method_candidate.rb +7 -2
- data/lib/rigor/sig_gen/writer.rb +60 -13
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +63 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +2 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +14 -24
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +10 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +33 -76
- data/skills/rigor-ask/SKILL.md +21 -1
- data/skills/rigor-baseline-reduce/SKILL.md +16 -0
- data/skills/rigor-ci-setup/SKILL.md +96 -249
- data/skills/rigor-doctor/SKILL.md +39 -49
- data/skills/rigor-doctor/references/01-checks.md +52 -0
- data/skills/rigor-editor-setup/SKILL.md +14 -0
- data/skills/rigor-mcp-setup/SKILL.md +14 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +15 -0
- data/skills/rigor-plugin-author/SKILL.md +16 -0
- data/skills/rigor-plugin-review/SKILL.md +174 -0
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +214 -0
- data/skills/rigor-plugin-tune/SKILL.md +21 -2
- data/skills/rigor-project-init/SKILL.md +16 -0
- data/skills/rigor-protection-uplift/SKILL.md +15 -0
- data/skills/rigor-rbs-setup/SKILL.md +15 -0
- data/skills/rigor-upgrade/SKILL.md +16 -0
- metadata +7 -4
|
@@ -22,17 +22,20 @@ module Rigor
|
|
|
22
22
|
@contracts = contracts
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
# Per-`ClassNode` check over the engine-owned walk (ADR-37):
|
|
26
|
+
# called once per class node the `node_rule` in `hanami.rb`
|
|
27
|
+
# dispatches, against every contract whose `path_glob` matches
|
|
28
|
+
# the file. No cross-class-node state is needed, so the checker
|
|
29
|
+
# ships no `class_nodes` traversal of its own.
|
|
30
|
+
def check_class(class_node, path:)
|
|
31
|
+
@contracts.filter_map do |contract|
|
|
32
|
+
next unless path_matches?(contract.path_glob, path)
|
|
33
|
+
|
|
34
|
+
handle_def = find_handle(class_node, contract)
|
|
35
|
+
if handle_def.nil?
|
|
36
|
+
missing_handle_diagnostic(contract, path, class_node)
|
|
37
|
+
else
|
|
38
|
+
handle_arity_mismatch_diagnostic(contract, path, class_node, handle_def)
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
41
|
end
|
|
@@ -49,12 +52,6 @@ module Rigor
|
|
|
49
52
|
File.fnmatch?(File.join("**", glob), path, FNMATCH_FLAGS)
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
def class_nodes(root)
|
|
53
|
-
found = []
|
|
54
|
-
walk(root) { |node| found << node if node.is_a?(Prism::ClassNode) }
|
|
55
|
-
found
|
|
56
|
-
end
|
|
57
|
-
|
|
58
55
|
def find_handle(class_node, contract)
|
|
59
56
|
direct_defs(class_node).find do |def_node|
|
|
60
57
|
def_node.name == contract.method_name &&
|
|
@@ -107,13 +104,6 @@ module Rigor
|
|
|
107
104
|
path = class_node.constant_path
|
|
108
105
|
path.respond_to?(:slice) ? path.slice : class_node.name.to_s
|
|
109
106
|
end
|
|
110
|
-
|
|
111
|
-
def walk(node, &)
|
|
112
|
-
return if node.nil?
|
|
113
|
-
|
|
114
|
-
yield node
|
|
115
|
-
node.compact_child_nodes.each { |child| walk(child, &) }
|
|
116
|
-
end
|
|
117
107
|
end
|
|
118
108
|
end
|
|
119
109
|
end
|
|
@@ -98,11 +98,18 @@ module Rigor
|
|
|
98
98
|
@protocol_contracts || manifest.protocol_contracts
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
# ADR-37 — per-class-node validation over the engine-owned walk.
|
|
102
|
+
# Each `Prism::ClassNode` is checked against every contract
|
|
103
|
+
# independently, so the plugin no longer ships its own `class_nodes`
|
|
104
|
+
# traversal; `ActionChecker#check_class` keeps the per-class
|
|
105
|
+
# contract logic. (A per-class contract check is exactly what
|
|
106
|
+
# `node_rule` is for — the return type is void, so no `scope`
|
|
107
|
+
# query is needed.)
|
|
108
|
+
node_rule Prism::ClassNode do |node, _scope, path|
|
|
102
109
|
contracts = protocol_contracts
|
|
103
|
-
|
|
110
|
+
next [] if contracts.empty?
|
|
104
111
|
|
|
105
|
-
ActionChecker.new(contracts: contracts).
|
|
112
|
+
ActionChecker.new(contracts: contracts).check_class(node, path: path)
|
|
106
113
|
end
|
|
107
114
|
end
|
|
108
115
|
|
|
@@ -147,11 +147,7 @@ module Rigor
|
|
|
147
147
|
# symlink-bearing form here. Look up under both so the
|
|
148
148
|
# match is symlink-agnostic.
|
|
149
149
|
errors = @parse_errors_by_path[path] || @parse_errors_by_path[canonicalize(path)] || []
|
|
150
|
-
|
|
151
|
-
diagnostics.concat(absurd_reachable_diagnostics(path, root))
|
|
152
|
-
diagnostics.concat(reveal_type_diagnostics(path, root))
|
|
153
|
-
diagnostics.concat(assert_type_mismatch_diagnostics(path, root))
|
|
154
|
-
diagnostics
|
|
150
|
+
errors.map { |error| parse_error_diagnostic(path, error) }
|
|
155
151
|
end
|
|
156
152
|
|
|
157
153
|
# ADR-52 slice 4 — per-call return-type path via the
|
|
@@ -179,6 +175,35 @@ module Rigor
|
|
|
179
175
|
bind_post_return_facts(call_node, scope)
|
|
180
176
|
end
|
|
181
177
|
|
|
178
|
+
# ADR-37 — the three per-call diagnostics ride the engine-owned
|
|
179
|
+
# walk instead of three hand-rolled `walk_for_*` recursions. Each
|
|
180
|
+
# candidate `T.` call is *recorded by object identity* during the
|
|
181
|
+
# inference pass (the `dynamic_return` / `narrowing_facts` rules
|
|
182
|
+
# above call `record_*`), so by the time these node rules fire in
|
|
183
|
+
# the diagnostics phase the sets are populated; the membership
|
|
184
|
+
# `delete` both gates the emission and pops the entry so a re-run
|
|
185
|
+
# cannot double-fire. The recorded set is the gate — no per-node
|
|
186
|
+
# `AbsurdRecognizer` / name check is needed here.
|
|
187
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
188
|
+
next [] unless @reachable_absurd_nodes.delete(node)
|
|
189
|
+
|
|
190
|
+
[absurd_diagnostic(path, node)]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
194
|
+
display = @reveal_type_calls.delete(node)
|
|
195
|
+
next [] if display.nil?
|
|
196
|
+
|
|
197
|
+
[reveal_type_diagnostic(path, node, display)]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
201
|
+
recorded = @assert_type_mismatches.delete(node)
|
|
202
|
+
next [] if recorded.nil?
|
|
203
|
+
|
|
204
|
+
[assert_type_mismatch_diagnostic(path, node, *recorded)]
|
|
205
|
+
end
|
|
206
|
+
|
|
182
207
|
private
|
|
183
208
|
|
|
184
209
|
# Run-time method-name gate for the `dynamic_return` rule
|
|
@@ -536,31 +561,9 @@ module Rigor
|
|
|
536
561
|
nil
|
|
537
562
|
end
|
|
538
563
|
|
|
539
|
-
#
|
|
540
|
-
#
|
|
541
|
-
#
|
|
542
|
-
# `@reachable_absurd_nodes` (populated during the engine's
|
|
543
|
-
# earlier pass through the `dynamic_return` rule). Pops
|
|
544
|
-
# matched entries so a duplicate run doesn't double-emit.
|
|
545
|
-
def absurd_reachable_diagnostics(path, root)
|
|
546
|
-
return [] if @reachable_absurd_nodes.empty?
|
|
547
|
-
|
|
548
|
-
diagnostics = []
|
|
549
|
-
walk_for_absurd(root) do |call_node|
|
|
550
|
-
next unless @reachable_absurd_nodes.delete(call_node)
|
|
551
|
-
|
|
552
|
-
diagnostics << absurd_diagnostic(path, call_node)
|
|
553
|
-
end
|
|
554
|
-
diagnostics
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
def walk_for_absurd(node, &)
|
|
558
|
-
return unless node.is_a?(Prism::Node)
|
|
559
|
-
|
|
560
|
-
yield node if node.is_a?(Prism::CallNode) && AbsurdRecognizer.absurd_call?(node)
|
|
561
|
-
node.compact_child_nodes.each { |child| walk_for_absurd(child, &) }
|
|
562
|
-
end
|
|
563
|
-
|
|
564
|
+
# Emits a `plugin.sorbet.absurd-reachable` warning for the
|
|
565
|
+
# `T.absurd(x)` call recorded in `@reachable_absurd_nodes` during
|
|
566
|
+
# inference; the node rule above does the identity match and pop.
|
|
564
567
|
def absurd_diagnostic(path, call_node)
|
|
565
568
|
Rigor::Analysis::Diagnostic.from_node(
|
|
566
569
|
call_node,
|
|
@@ -591,29 +594,6 @@ module Rigor
|
|
|
591
594
|
type.respond_to?(:describe) ? type.describe : type.inspect
|
|
592
595
|
end
|
|
593
596
|
|
|
594
|
-
def reveal_type_diagnostics(path, root)
|
|
595
|
-
return [] if @reveal_type_calls.empty?
|
|
596
|
-
|
|
597
|
-
diagnostics = []
|
|
598
|
-
walk_for_reveal_type(root) do |call_node|
|
|
599
|
-
display = @reveal_type_calls.delete(call_node)
|
|
600
|
-
next if display.nil?
|
|
601
|
-
|
|
602
|
-
diagnostics << reveal_type_diagnostic(path, call_node, display)
|
|
603
|
-
end
|
|
604
|
-
diagnostics
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
def walk_for_reveal_type(node, &)
|
|
608
|
-
return unless node.is_a?(Prism::Node)
|
|
609
|
-
|
|
610
|
-
if node.is_a?(Prism::CallNode) && node.name == :reveal_type &&
|
|
611
|
-
TypeTranslator.sorbet_t_namespaced?(node.receiver)
|
|
612
|
-
yield node
|
|
613
|
-
end
|
|
614
|
-
node.compact_child_nodes.each { |child| walk_for_reveal_type(child, &) }
|
|
615
|
-
end
|
|
616
|
-
|
|
617
597
|
def reveal_type_diagnostic(path, call_node, display)
|
|
618
598
|
Rigor::Analysis::Diagnostic.from_node(
|
|
619
599
|
call_node,
|
|
@@ -647,29 +627,6 @@ module Rigor
|
|
|
647
627
|
@assert_type_mismatches[call_node] = [display_for_type(inferred), display_for_type(asserted)]
|
|
648
628
|
end
|
|
649
629
|
|
|
650
|
-
def assert_type_mismatch_diagnostics(path, root)
|
|
651
|
-
return [] if @assert_type_mismatches.empty?
|
|
652
|
-
|
|
653
|
-
diagnostics = []
|
|
654
|
-
walk_for_assert_type(root) do |call_node|
|
|
655
|
-
recorded = @assert_type_mismatches.delete(call_node)
|
|
656
|
-
next if recorded.nil?
|
|
657
|
-
|
|
658
|
-
diagnostics << assert_type_mismatch_diagnostic(path, call_node, *recorded)
|
|
659
|
-
end
|
|
660
|
-
diagnostics
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
def walk_for_assert_type(node, &)
|
|
664
|
-
return unless node.is_a?(Prism::Node)
|
|
665
|
-
|
|
666
|
-
if node.is_a?(Prism::CallNode) && node.name == :assert_type! &&
|
|
667
|
-
TypeTranslator.sorbet_t_namespaced?(node.receiver)
|
|
668
|
-
yield node
|
|
669
|
-
end
|
|
670
|
-
node.compact_child_nodes.each { |child| walk_for_assert_type(child, &) }
|
|
671
|
-
end
|
|
672
|
-
|
|
673
630
|
def assert_type_mismatch_diagnostic(path, call_node, inferred_display, asserted_display)
|
|
674
631
|
Rigor::Analysis::Diagnostic.from_node(
|
|
675
632
|
call_node,
|
data/skills/rigor-ask/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rigor-ask
|
|
3
3
|
description: |
|
|
4
|
-
|
|
4
|
+
Answer any question about Rigor by investigating, not from memory — Rigor is niche and version-specific, so run its tools and read its bundled docs, then answer from what you saw. Use `rigor docs` (handbook + manual, offline and version-matched) and `rigor explain <rule>`; for the user's own code, `rigor check` / `annotate` / `type-of`. Covers: why a line is flagged or whether it's a false positive; the type model (narrowing, refinements, `Dynamic`, RBS); config keys, flags, baselines; comparisons to Sorbet, Steep, mypy, PHPStan; whether Rigor handles Rails, RSpec, or a given gem; how to type a method; "what is Rigor / why use it / is it right for us?". Trigger on any Rigor question seeking understanding — even casual, comparative, or grumbling. Skip only when it's purely "set it up / fix / reduce it for me" (use rigor-next-steps).
|
|
5
5
|
license: MPL-2.0
|
|
6
6
|
metadata:
|
|
7
7
|
version: 0.2.0
|
|
@@ -36,6 +36,26 @@ This is the user's shortcut: they only ever need to remember two skills —
|
|
|
36
36
|
into the right lookup or analysis so they never have to remember the
|
|
37
37
|
command.
|
|
38
38
|
|
|
39
|
+
## Prefer the live map over this skill's tables
|
|
40
|
+
|
|
41
|
+
The doc-page names and command flags below are a snapshot; Rigor's pages
|
|
42
|
+
and CLI move release to release, and a vendored copy of this skill (e.g.
|
|
43
|
+
added via `npx skills`) lags further still. Treat the tables as
|
|
44
|
+
orientation, then confirm against the **installed** Rigor, which is always
|
|
45
|
+
current:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
rigor docs --list # the live doc map — what pages exist in THIS version
|
|
49
|
+
rigor --help # the live command + flag list
|
|
50
|
+
rigor skill --full rigor-ask # the current version of this skill itself
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Never cite a page, flag, or rule id you have not just seen in one of these
|
|
54
|
+
— that is this skill's whole discipline. If `rigor` is not installed, you
|
|
55
|
+
can still answer high-level "what is Rigor?" questions from the web
|
|
56
|
+
<https://rigor.typedduck.fail/llms.txt>, but for anything version-specific
|
|
57
|
+
install it first (see `rigor-next-steps`).
|
|
58
|
+
|
|
39
59
|
## The toolbox
|
|
40
60
|
|
|
41
61
|
Everything here is read-only and needs no network.
|
|
@@ -19,6 +19,22 @@ This skill is for **users improving their own project**. It uses the
|
|
|
19
19
|
published `rigor` executable on `PATH` and references only public CLI
|
|
20
20
|
flags and config keys.
|
|
21
21
|
|
|
22
|
+
## First: load the version-current copy
|
|
23
|
+
|
|
24
|
+
The step-by-step commands live in this skill's `references/` files, and
|
|
25
|
+
their exact flags drift between Rigor releases — so follow the copy that
|
|
26
|
+
ships with the **installed** Rigor rather than any vendored or frozen copy
|
|
27
|
+
of this file. Get the complete current procedure in one call:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
rigor skill --full rigor-baseline-reduce # this body + all its references/, inline
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If you already loaded this skill *via* `rigor skill` you have the current
|
|
34
|
+
copy — just proceed (read any `references/NN-*.md` from the directory the
|
|
35
|
+
header names). If `rigor` is not on `PATH`, this task needs it: run
|
|
36
|
+
**`rigor-next-steps`** to install Rigor first, then come back.
|
|
37
|
+
|
|
22
38
|
## Phase 0 — When to use this skill
|
|
23
39
|
|
|
24
40
|
Trigger when the user says "reduce the rigor baseline", "fix some
|