rigortype 0.1.12 → 0.1.13

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.
@@ -0,0 +1,168 @@
1
+ # 03 — Triage, baseline, and surfacing real bugs
2
+
3
+ Covers **Phase 6** (triage), **Phase 7** (baseline — acknowledge mode
4
+ only), and **Phase 8** (real bugs + escalation).
5
+
6
+ ## Phase 6 — Triage the diagnostic stream
7
+
8
+ Do **not** read the raw `rigor check` output to decide what to do. A
9
+ mature codebase's raw stream is hundreds of lines and reads as
10
+ hundreds of unrelated problems — it is the wrong first artefact. Run
11
+ the triage command instead:
12
+
13
+ ```sh
14
+ rigor triage --format json
15
+ ```
16
+
17
+ `rigor triage` runs the same analysis as `rigor check`, then returns
18
+ a structured summary instead of the per-line dump. It is read-only
19
+ and advisory — it never edits config and never writes a baseline.
20
+ The JSON shape:
21
+
22
+ ```json
23
+ {
24
+ "summary": { "total": 489, "error": 480, "warning": 9, "info": 0 },
25
+ "distribution": [ { "rule": "call.undefined-method", "count": 437 } ],
26
+ "hotspots": [ { "file": "app/models/status.rb", "count": 42,
27
+ "by_rule": { "call.undefined-method": 40 } } ],
28
+ "hints": [
29
+ { "id": "activesupport-core-ext", "confidence": "likely",
30
+ "diagnostic_count": 365, "summary": "...", "action": "..." }
31
+ ]
32
+ }
33
+ ```
34
+
35
+ Use the three sections like this:
36
+
37
+ - **`summary` / `distribution`** — the scale, and which rules
38
+ dominate. Decides nothing on its own; feeds the mode sanity-check
39
+ (>100 errors → acknowledge mode is the right default).
40
+ - **`hotspots`** — files carrying the most diagnostics. A single hot
41
+ file is often one structural cause, not many bugs.
42
+ - **`hints`** — the heuristic catalogue. Each hint names a *likely
43
+ cause* and a *suggested action*. They are signal, not verdicts —
44
+ the `confidence` field is `likely` or `possible`; verify before
45
+ acting.
46
+
47
+ ### Hint catalogue → what to do
48
+
49
+ | Hint `id` | Cause | Where this skill handles it |
50
+ | --- | --- | --- |
51
+ | `activesupport-core-ext` | ActiveSupport core-class monkey-patches not loaded. | Go back to Phase 3/4: add `rigor-activesupport-core-ext` to `plugins:` (it is an RBS-bundle plugin), re-run triage. This is a config gap, not a bug. |
52
+ | `gem-without-rbs` | A dependency ships no RBS. | Phase 7 escalation — `rbs collection install`, or `dependencies.source_inference:`, or open a Rigor issue. |
53
+ | `project-monkey-patch` | An in-project monkey-patch / refinement Rigor did not see. | Phase 7 escalation — register the defining file via `pre_eval:`, or (if it is a DSL) write a project plugin. |
54
+ | `activerecord-relation-misinference` | An ActiveRecord relation inferred as `Array`. | Ensure `rigor-activerecord` is enabled (Phase 3). If it persists, it is an engine gap — open a Rigor issue. |
55
+ | `systemic-file-cluster` | One file × one rule, large count. | Acknowledge mode: a clean baseline bucket. Strict mode: a single fix may clear many — review that file first. |
56
+ | `genuine-bugs` | Low-count rules scattered across files. | **Phase 7** — these are the localised bugs Rigor caught. Review first, in both modes. Note: the hint groups all low-count rules regardless of severity — filter for `error` severity when prioritising actionable items. |
57
+
58
+ If triage flags `activesupport-core-ext` (or any config gap),
59
+ **fix the config and re-run `rigor triage` before continuing**. The
60
+ baseline and the real-bug review should both run against the
61
+ post-config diagnostic set, not the inflated one.
62
+
63
+ ## Phase 7 — Generate the baseline (acknowledge mode only)
64
+
65
+ **Strict mode skips this phase entirely.** A strict project has no
66
+ baseline; every diagnostic stays live.
67
+
68
+ In acknowledge mode, snapshot today's diagnostics:
69
+
70
+ ```sh
71
+ rigor baseline generate
72
+ ```
73
+
74
+ This writes `.rigor-baseline.yml` at the project root — one
75
+ `(file, rule, count)` bucket per cluster. The command refuses to
76
+ overwrite an existing baseline without `--force`.
77
+
78
+ Then **wire it into the config**. Per Rigor's no-magic rule, the
79
+ baseline file does nothing until `.rigor.dist.yml` names it. Add (or
80
+ uncomment) the line written in Phase 4:
81
+
82
+ ```yaml
83
+ baseline: .rigor-baseline.yml
84
+ ```
85
+
86
+ `rigor baseline generate` prints a note reminding you of this if the
87
+ config does not yet declare `baseline:`. Do both edits — generate and
88
+ wire — in one step so the user never has a generated baseline that
89
+ silently does nothing.
90
+
91
+ How the baseline behaves afterwards (acknowledge mode's whole point):
92
+
93
+ - A `(file, rule)` bucket is silenced while its live count stays at
94
+ or below the recorded number.
95
+ - If a commit pushes a bucket *over* its recorded count, **every**
96
+ diagnostic in that bucket surfaces — the bucket is now a regression
97
+ to review.
98
+ - New `(file, rule)` pairs that were not in the baseline surface
99
+ immediately.
100
+
101
+ So ordinary coding cannot quietly grow the diagnostic count: the
102
+ baseline is a ceiling, not a blanket. Reducing it later is the
103
+ `rigor-baseline-reduce` skill's job.
104
+
105
+ Commit `.rigor-baseline.yml` — it documents project state.
106
+
107
+ Print the suppression summary for the user: "N diagnostics recorded
108
+ as baseline; M will surface on subsequent runs."
109
+
110
+ ## Phase 8 — Surface real bugs & offer escalation
111
+
112
+ The triage `genuine-bugs` hint (and any low-count, scattered rule in
113
+ `distribution`) points at the diagnostics most likely to be **actual
114
+ bugs** — a `nil`-receiver crash on a rarely-exercised line, a typo'd
115
+ method. In **both modes**, surface 2–3 of these to the user and offer
116
+ to walk them: a small, scattered rule is rarely systemic.
117
+
118
+ In acknowledge mode these still went into the baseline — that is
119
+ fine; the baseline is a starting envelope, not a verdict that the
120
+ bug is acceptable. Recommend the user run the `rigor-baseline-reduce`
121
+ skill next to work them down.
122
+
123
+ ### Escalation path A — application-specific metaprogramming
124
+
125
+ If triage reports `project-monkey-patch`, or a `call.undefined-method`
126
+ cluster lands on the project's own DSL / `define_method` factory /
127
+ in-house macro, Rigor cannot follow it by default. Two answers,
128
+ cheapest first:
129
+
130
+ 1. **A plain monkey-patch in a known file** (e.g.
131
+ `lib/core_ext/string_extensions.rb`) — register it via `pre_eval:`
132
+ in `.rigor.dist.yml`. Rigor walks those files before per-file
133
+ inference, so the added methods become visible.
134
+ 2. **A genuine project DSL** — the durable fix is a **project-private
135
+ Rigor plugin** that teaches Rigor the DSL's shape. Offer to hand
136
+ off to the `rigor-plugin-author` skill. The plugin can live under
137
+ the project's own `lib/` (loaded without a gemspec) or as a
138
+ separate `rigor-<name>` gem.
139
+
140
+ ### Escalation path B — an unsupported external gem
141
+
142
+ If triage reports `gem-without-rbs`, a dependency ships no type
143
+ information and Rigor has no built-in coverage. In order:
144
+
145
+ 1. `rbs collection install` — pulls community RBS for the gem if it
146
+ exists. Re-run triage afterwards.
147
+ 2. `dependencies.source_inference:` in `.rigor.dist.yml` — opt the
148
+ gem into Rigor inferring `Dynamic`-typed returns from its source.
149
+ 3. If the gem is widely used and genuinely warrants first-class
150
+ support, **open an issue on the Rigor project** so the maintainers
151
+ can ship a plugin or RBS bundle:
152
+ <https://github.com/rigortype/rigor/issues>. Include the gem name,
153
+ version, and a sample of the diagnostics.
154
+
155
+ Neither escalation is mandatory — offer them when triage points at
156
+ the cause; the user decides whether to act now or defer.
157
+
158
+ ## Output of this module — onboarding complete
159
+
160
+ - A committed `.rigor.dist.yml`.
161
+ - Acknowledge mode: a committed `.rigor-baseline.yml` + an active
162
+ `baseline:` line. Strict mode: neither.
163
+ - The user has seen the likely real bugs and knows the two escalation
164
+ paths.
165
+
166
+ Next sessions: `rigor-baseline-reduce` to work the baseline down
167
+ (acknowledge mode), or `rigor-plugin-author` if escalation path A
168
+ applies.
@@ -0,0 +1,171 @@
1
+ # 04 — Generate initial RBS sigs and uplift precision
2
+
3
+ Covers **Phase 5**. Inputs: the configured `.rigor.dist.yml` from
4
+ Phase 4 and the detected source paths.
5
+
6
+ ## Why generate sigs before triaging
7
+
8
+ Rigor's inference engine uses RBS signatures from `sig/` to sharpen
9
+ its type flow. Without them, whole chains of methods fall back to
10
+ `untyped`. Running `rigor triage` against an uninitialized `sig/`
11
+ inflates the diagnostic count with cascade noise that sigs would
12
+ eliminate. Generating sigs first means Phase 6's `rigor triage`
13
+ report is as signal-rich as possible — the `genuine-bugs` hint
14
+ counts bugs, not sig gaps.
15
+
16
+ This phase is **optional if the project already has a `sig/`
17
+ directory** with handwritten RBS annotations. In that case skip
18
+ straight to Phase 6.
19
+
20
+ ## Step 5-a — Dry-run sig-gen
21
+
22
+ Inspect what sig-gen would produce without writing anything:
23
+
24
+ ```sh
25
+ rigor sig-gen lib # adjust path to match the paths: key
26
+ ```
27
+
28
+ Typical output at this point: most `new_method` candidates have
29
+ literal or concrete return types (`"hello"`, `42`, `:done`, `nil`).
30
+ Methods whose return type cannot be inferred show up with
31
+ `skip_reason: :untyped_return` — these are the sig precision targets.
32
+
33
+ To get a breakdown in JSON:
34
+
35
+ ```sh
36
+ rigor sig-gen --format json lib | ruby -e '
37
+ require "json"
38
+ data = JSON.parse($stdin.read)["candidates"]
39
+ puts "=== classification breakdown ==="
40
+ data.group_by { |c| c["classification"] }
41
+ .sort_by { |k, _| k }
42
+ .each { |k, v| puts " #{k}: #{v.size}" }
43
+ skipped = data.select { |c| c["classification"] == "skipped" }
44
+ puts "\n=== skip reasons ==="
45
+ skipped.group_by { |c| c["skip_reason"] }
46
+ .sort_by { |_, v| -v.size }
47
+ .each { |r, v| puts " #{r}: #{v.size}" }
48
+ '
49
+ ```
50
+
51
+ ## Step 5-b — Write the baseline sigs
52
+
53
+ ```sh
54
+ rigor sig-gen --write lib
55
+ ```
56
+
57
+ This creates `sig/**/*.rbs` with one method signature per inferred
58
+ method. Check in a few files:
59
+
60
+ ```sh
61
+ head -40 sig/lib/your_class.rbs
62
+ ```
63
+
64
+ At this point, `attr_reader` and `attr_accessor` methods that rely on
65
+ ivar types set from `initialize` parameters will likely still be
66
+ absent (classified as `:untyped_return`). Step 5-c fixes that.
67
+
68
+ ## Step 5-c — Precision uplift with --params=observed
69
+
70
+ `--params=observed` tells sig-gen to collect observed argument types
71
+ from every call site it processes during the analysis pass. The most
72
+ important use case: **`attr_reader` / `attr_writer` / `attr_accessor`
73
+ methods whose `@ivar` is assigned from an `initialize` parameter**.
74
+
75
+ ### Example
76
+
77
+ ```ruby
78
+ class Person
79
+ attr_reader :name, :age
80
+
81
+ def initialize(name, age)
82
+ @name = name
83
+ @age = age
84
+ end
85
+ end
86
+
87
+ Person.new("Alice", 30)
88
+ Person.new("Bob", 25)
89
+ ```
90
+
91
+ Without observations: `attr_reader :name` → skipped as `:untyped_return`
92
+ (the ivar's type is unknown because the blank inference scope never
93
+ sees the parameter values).
94
+
95
+ With `--params=observed`: sig-gen accumulates `name → "Alice" | "Bob"`,
96
+ `age → 25 | 30` from the `Person.new(...)` call sites, then propagates:
97
+ `@name: ("Alice" | "Bob")` → `def name: () -> ("Alice" | "Bob")`.
98
+
99
+ Run:
100
+
101
+ ```sh
102
+ rigor sig-gen --params=observed --write lib
103
+ ```
104
+
105
+ The cascade effect is significant: a single resolved `attr_reader`
106
+ can unlock dozens of downstream methods whose precision depends on it.
107
+
108
+ ### Measuring the uplift
109
+
110
+ ```sh
111
+ rigor sig-gen --format json lib | ruby -e '
112
+ require "json"
113
+ data = JSON.parse($stdin.read)["candidates"]
114
+ new_m = data.select { |c| c["classification"] == "new_method" }
115
+ untyped = new_m.count { |c| (c["inferred_return"] || "").include?("untyped") }
116
+ concrete = new_m.count { |c| !(c["rbs"] || "").include?("untyped") }
117
+ puts "new_method: #{new_m.size} | still-untyped return: #{untyped} | concrete: #{concrete}"
118
+ '
119
+ ```
120
+
121
+ Run this before and after `--params=observed` to see how many methods
122
+ resolved.
123
+
124
+ ## Step 5-d — Handle remaining untyped methods
125
+
126
+ For methods still showing `untyped` return after `--params=observed`,
127
+ there are a few options depending on the cause:
128
+
129
+ | Pattern | Cause | Fix |
130
+ |---|---|---|
131
+ | `attr_reader :x` with `@x` never set in `initialize` | ivar set from a DB query, config read, or side effect | Add a hand-written sig: create (or edit) `sig/your_class.rbs` with `attr_reader x: String` |
132
+ | Deep method chains on untyped receivers | Cascade from a gem with no RBS | `rbs collection install`; Phase 7 escalation path B |
133
+ | Recursive or mutually recursive methods | Return type not inferrable without a base case | Add a `# @rbs return: YourType` inline annotation, or a hand-written sig |
134
+ | Dynamic methods (`define_method`, DSL) | Metaprogramming Rigor cannot follow | Phase 7 escalation path A (project plugin) |
135
+
136
+ Do not spend long on residual `untyped` methods at this stage — a
137
+ handful of `untyped` returns in `sig/` does not block adoption. The
138
+ objective is to reduce the false-positive count before triage, not to
139
+ reach perfect sig coverage.
140
+
141
+ ## Step 5-e — Commit the sig/ directory
142
+
143
+ Once you are satisfied with the initial sig quality:
144
+
145
+ ```sh
146
+ git add sig/
147
+ git commit -m "Add initial RBS sigs from rigor sig-gen (--params=observed)"
148
+ ```
149
+
150
+ A committed `sig/` is a first-class project artefact: it improves
151
+ inference quality on every subsequent run and is maintained alongside
152
+ the source (add new sig files when adding classes; update sigs with
153
+ `rigor sig-gen --write lib` when method signatures change).
154
+
155
+ ## Quick reference — sig-gen flags used in this phase
156
+
157
+ | Flag | Effect |
158
+ |---|---|
159
+ | *(no flags)* | Print candidates to stdout; nothing written |
160
+ | `--write` | Write `sig/**/*.rbs` files |
161
+ | `--params=observed` | Collect observed argument types from call sites; used to resolve attr_reader / attr_accessor / attr_writer ivar types from initialize observations |
162
+ | `--format json` | Output structured JSON (for scripted analysis) |
163
+ | `--diff` | Show what would change vs. existing sigs (useful for incremental updates) |
164
+
165
+ ## Output of this module
166
+
167
+ A committed `sig/` directory with RBS skeletons for all statically
168
+ inferrable methods. Remaining `:untyped_return` methods are noted for
169
+ potential manual annotation; they do not block Phase 6.
170
+
171
+ Proceed to Phase 6 ([`03-baseline-and-bugs.md`](03-baseline-and-bugs.md)).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -295,6 +295,7 @@ files:
295
295
  - lib/rigor/cli/plugins_renderer.rb
296
296
  - lib/rigor/cli/prism_colorizer.rb
297
297
  - lib/rigor/cli/sig_gen_command.rb
298
+ - lib/rigor/cli/skill_command.rb
298
299
  - lib/rigor/cli/triage_command.rb
299
300
  - lib/rigor/cli/triage_renderer.rb
300
301
  - lib/rigor/cli/type_of_command.rb
@@ -640,6 +641,18 @@ files:
640
641
  - sig/rigor/testing.rbs
641
642
  - sig/rigor/trinary.rbs
642
643
  - sig/rigor/type.rbs
644
+ - skills/rigor-baseline-reduce/SKILL.md
645
+ - skills/rigor-baseline-reduce/references/01-classify.md
646
+ - skills/rigor-baseline-reduce/references/02-fix-or-suppress.md
647
+ - skills/rigor-plugin-author/SKILL.md
648
+ - skills/rigor-plugin-author/references/01-plan-and-scaffold.md
649
+ - skills/rigor-plugin-author/references/02-walker-and-types.md
650
+ - skills/rigor-plugin-author/references/03-test-and-ship.md
651
+ - skills/rigor-project-init/SKILL.md
652
+ - skills/rigor-project-init/references/01-detect.md
653
+ - skills/rigor-project-init/references/02-configure.md
654
+ - skills/rigor-project-init/references/03-baseline-and-bugs.md
655
+ - skills/rigor-project-init/references/04-sig-uplift.md
643
656
  homepage: https://github.com/rigortype/rigor
644
657
  licenses:
645
658
  - MPL-2.0