rigortype 0.2.0 → 0.2.2
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 +82 -20
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/docs/handbook/01-getting-started.md +311 -0
- data/docs/handbook/02-everyday-types.md +337 -0
- data/docs/handbook/03-narrowing.md +359 -0
- data/docs/handbook/04-tuples-and-shapes.md +321 -0
- data/docs/handbook/05-methods-and-blocks.md +339 -0
- data/docs/handbook/06-classes.md +305 -0
- data/docs/handbook/07-rbs-and-extended.md +427 -0
- data/docs/handbook/08-understanding-errors.md +373 -0
- data/docs/handbook/09-plugins.md +241 -0
- data/docs/handbook/10-sorbet.md +347 -0
- data/docs/handbook/11-sig-gen.md +312 -0
- data/docs/handbook/12-lightweight-hkt.md +333 -0
- data/docs/handbook/README.md +275 -0
- data/docs/handbook/appendix-elixir.md +370 -0
- data/docs/handbook/appendix-go.md +399 -0
- data/docs/handbook/appendix-java-csharp.md +470 -0
- data/docs/handbook/appendix-liskov.md +580 -0
- data/docs/handbook/appendix-mypy.md +370 -0
- data/docs/handbook/appendix-phpstan.md +338 -0
- data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
- data/docs/handbook/appendix-rust.md +446 -0
- data/docs/handbook/appendix-steep.md +336 -0
- data/docs/handbook/appendix-type-theory.md +1662 -0
- data/docs/handbook/appendix-typeprof.md +416 -0
- data/docs/handbook/appendix-typescript.md +332 -0
- data/docs/install.md +189 -0
- data/docs/llms.txt +72 -0
- data/docs/manual/01-installation.md +342 -0
- data/docs/manual/02-cli-reference.md +557 -0
- data/docs/manual/03-configuration.md +152 -0
- data/docs/manual/04-diagnostics.md +206 -0
- data/docs/manual/05-inspecting-types.md +109 -0
- data/docs/manual/06-baseline.md +104 -0
- data/docs/manual/07-plugins.md +92 -0
- data/docs/manual/08-skills.md +143 -0
- data/docs/manual/09-editor-integration.md +245 -0
- data/docs/manual/10-mcp-server.md +532 -0
- data/docs/manual/11-ci.md +274 -0
- data/docs/manual/12-caching.md +116 -0
- data/docs/manual/13-troubleshooting.md +120 -0
- data/docs/manual/14-rails-quickstart.md +332 -0
- data/docs/manual/15-type-protection-coverage.md +204 -0
- data/docs/manual/16-rbs-extended-annotations.md +190 -0
- data/docs/manual/17-driving-improvement.md +160 -0
- data/docs/manual/README.md +87 -0
- data/docs/manual/ci-templates/README.md +58 -0
- data/docs/manual/plugins/README.md +86 -0
- data/docs/manual/plugins/rigor-actioncable.md +78 -0
- data/docs/manual/plugins/rigor-actionmailer.md +74 -0
- data/docs/manual/plugins/rigor-actionpack.md +80 -0
- data/docs/manual/plugins/rigor-activejob.md +58 -0
- data/docs/manual/plugins/rigor-activerecord.md +102 -0
- data/docs/manual/plugins/rigor-activestorage.md +74 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
- data/docs/manual/plugins/rigor-devise.md +70 -0
- data/docs/manual/plugins/rigor-dry-schema.md +56 -0
- data/docs/manual/plugins/rigor-dry-struct.md +60 -0
- data/docs/manual/plugins/rigor-dry-types.md +59 -0
- data/docs/manual/plugins/rigor-dry-validation.md +62 -0
- data/docs/manual/plugins/rigor-factorybot.md +76 -0
- data/docs/manual/plugins/rigor-graphql.md +89 -0
- data/docs/manual/plugins/rigor-hanami.md +83 -0
- data/docs/manual/plugins/rigor-mangrove.md +73 -0
- data/docs/manual/plugins/rigor-minitest.md +86 -0
- data/docs/manual/plugins/rigor-pundit.md +72 -0
- data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
- data/docs/manual/plugins/rigor-rails-routes.md +94 -0
- data/docs/manual/plugins/rigor-rails.md +44 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
- data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
- data/docs/manual/plugins/rigor-rspec.md +86 -0
- data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
- data/docs/manual/plugins/rigor-sidekiq.md +78 -0
- data/docs/manual/plugins/rigor-sinatra.md +61 -0
- data/docs/manual/plugins/rigor-sorbet.md +63 -0
- data/docs/manual/plugins/rigor-statesman.md +75 -0
- data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
- data/exe/rigor +1 -1
- data/lib/rigor/analysis/incremental_session.rb +4 -2
- data/lib/rigor/analysis/run_stats.rb +13 -1
- data/lib/rigor/analysis/runner.rb +54 -12
- data/lib/rigor/cli/check_command.rb +26 -3
- data/lib/rigor/cli/coverage_command.rb +67 -92
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration.rb +12 -0
- data/lib/rigor/environment/rbs_loader.rb +27 -0
- data/lib/rigor/environment.rb +49 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +140 -38
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/inference/statement_evaluator.rb +27 -0
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +98 -38
- data/lib/rigor/protection/mutator.rb +21 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/version.rb +1 -1
- data/skills/rigor-ask/SKILL.md +172 -0
- data/skills/rigor-doctor/SKILL.md +87 -0
- data/skills/rigor-editor-setup/SKILL.md +114 -0
- data/skills/rigor-mcp-setup/SKILL.md +117 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
- data/skills/rigor-next-steps/SKILL.md +113 -0
- data/skills/rigor-plugin-tune/SKILL.md +79 -0
- data/skills/rigor-protection-uplift/SKILL.md +133 -0
- data/skills/rigor-rbs-setup/SKILL.md +128 -0
- data/skills/rigor-upgrade/SKILL.md +79 -0
- metadata +120 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rigor-editor-setup
|
|
3
|
+
description: |
|
|
4
|
+
Wire Rigor's bundled language server (`rigor lsp`) into the developer's editor for live diagnostics, hover-to-type, outline, and type-aware completion. The per-editor config snippets live in the manual; this skill identifies the editor, applies the right one, and verifies the server attaches. Triggers: "set up Rigor in my editor", "rigor LSP / language server", "live Rigor diagnostics in VS Code / Neovim / Helix / Emacs", "hover types in my editor". NOT for CI integration (use rigor-ci-setup) or first-time project setup (use rigor-project-init).
|
|
5
|
+
license: MPL-2.0
|
|
6
|
+
metadata:
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
homepage: https://github.com/rigortype/rigor
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rigor Editor Setup
|
|
12
|
+
|
|
13
|
+
`rigor lsp` is the in-process Language Server bundled with the
|
|
14
|
+
`rigortype` gem. It speaks LSP over stdio and turns Rigor's analyzer
|
|
15
|
+
into a live editor experience: diagnostics as you type, hover-to-type,
|
|
16
|
+
an outline view, and type-aware completion. This skill wires it into the
|
|
17
|
+
developer's editor.
|
|
18
|
+
|
|
19
|
+
The authoritative, per-editor configuration lives in the manual. With
|
|
20
|
+
Rigor installed, read it **offline** with no network round-trip:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
rigor docs editor-integration
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
(Web fallback, only before Rigor is installed:
|
|
27
|
+
**[Rigor LSP — Editor Integration](https://github.com/rigortype/rigor/blob/master/docs/manual/09-editor-integration.md)**.)
|
|
28
|
+
This skill is the *workflow* around it (identify the editor → apply the
|
|
29
|
+
manual's snippet → verify), so it does not duplicate (and cannot
|
|
30
|
+
stale-out) the config details.
|
|
31
|
+
|
|
32
|
+
## When to use
|
|
33
|
+
|
|
34
|
+
- A developer wants Rigor feedback live in their editor, not just from
|
|
35
|
+
`rigor check` on the command line.
|
|
36
|
+
- A project commits a shared editor config (e.g. `.vscode/`) and you want
|
|
37
|
+
to add Rigor's LSP to it so the whole team gets it.
|
|
38
|
+
|
|
39
|
+
## When NOT to use
|
|
40
|
+
|
|
41
|
+
- Wiring Rigor into CI — that is `rigor-ci-setup`.
|
|
42
|
+
- The project has no Rigor config yet — run `rigor-project-init` first
|
|
43
|
+
(the LSP uses the same `.rigor.yml` discovery as `rigor check`).
|
|
44
|
+
|
|
45
|
+
## The one stable fact
|
|
46
|
+
|
|
47
|
+
Every editor snippet simply launches **`rigor lsp`** (stdio) and needs
|
|
48
|
+
**`rigor` on the editor's `PATH`** — the same executable `rigor check`
|
|
49
|
+
uses. For GUI editors that do not inherit your shell, the `mise` shim
|
|
50
|
+
path is the most reliable channel (see `rigor docs install`, or
|
|
51
|
+
[Installing Rigor](https://github.com/rigortype/rigor/blob/master/docs/install.md)
|
|
52
|
+
on the web). Do **not** add `rigortype` to the project's `Gemfile` — it
|
|
53
|
+
is a tool, not a library.
|
|
54
|
+
|
|
55
|
+
## Procedure
|
|
56
|
+
|
|
57
|
+
### Phase 1 — confirm the analyzer works from the CLI first
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
rigor check <a-file-or-dir>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The LSP shares `rigor check`'s config discovery and analysis. If `check`
|
|
64
|
+
works from the project root, the LSP will too; if it fails, fix that
|
|
65
|
+
first — the editor would only surface the same failure.
|
|
66
|
+
|
|
67
|
+
### Phase 2 — identify the editor
|
|
68
|
+
|
|
69
|
+
Ask the developer which editor they use, or detect it (a committed
|
|
70
|
+
`.vscode/`, a Neovim `init.lua`, `~/.config/helix/`, an Emacs config).
|
|
71
|
+
The manual chapter covers:
|
|
72
|
+
|
|
73
|
+
- **Neovim** (nvim-lspconfig)
|
|
74
|
+
- **VS Code** (generic LSP-client wrapper / minimal extension)
|
|
75
|
+
- **Helix** (`languages.toml`)
|
|
76
|
+
- **Emacs** (Eglot and lsp-mode)
|
|
77
|
+
|
|
78
|
+
### Phase 3 — apply the matching config
|
|
79
|
+
|
|
80
|
+
Open the manual's **Editor wiring** section and apply the snippet for
|
|
81
|
+
the developer's editor verbatim:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
rigor docs editor-integration
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
(or, pre-install, the web copy:
|
|
88
|
+
<https://github.com/rigortype/rigor/blob/master/docs/manual/09-editor-integration.md>)
|
|
89
|
+
|
|
90
|
+
All snippets invoke `rigor lsp` directly. (Only if the project still
|
|
91
|
+
uses a legacy bundler-local install do you swap in
|
|
92
|
+
`bundle exec rigor lsp` — the manual notes this per editor.)
|
|
93
|
+
|
|
94
|
+
If the project commits a shared editor config (e.g. `.vscode/`), add the
|
|
95
|
+
Rigor LSP wiring there and commit it, so every contributor gets the same
|
|
96
|
+
setup rather than configuring it individually.
|
|
97
|
+
|
|
98
|
+
### Phase 4 — verify
|
|
99
|
+
|
|
100
|
+
Open a Ruby file inside a Rigor-configured project and confirm:
|
|
101
|
+
|
|
102
|
+
- diagnostics appear (on save / as you type), and
|
|
103
|
+
- hover over a method or local shows its inferred type.
|
|
104
|
+
|
|
105
|
+
If the server starts but nothing appears, work the manual's
|
|
106
|
+
**Troubleshooting** section (most often: `rigor` not on the editor's
|
|
107
|
+
PATH, or no `.rigor.yml` at the project root). Capture the LSP log with
|
|
108
|
+
`rigor lsp --log=/tmp/rigor-lsp.log` when filing an issue.
|
|
109
|
+
|
|
110
|
+
## Next step
|
|
111
|
+
|
|
112
|
+
Re-run `rigor skill describe` for the next move — with live feedback in
|
|
113
|
+
the editor, raising protection (`rigor-protection-uplift`) or reducing a
|
|
114
|
+
baseline (`rigor-baseline-reduce`) is a tighter loop.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rigor-mcp-setup
|
|
3
|
+
description: |
|
|
4
|
+
Wire Rigor's bundled MCP server (`rigor mcp`) into an AI coding agent (Claude Code, Claude Desktop, Cursor, Cline) so the agent can call Rigor's read-only analysis tools — rigor_check, rigor_type_of, rigor_triage, rigor_coverage, and more — during a session. The per-client config lives in the manual; this skill identifies the client, applies the right one, and verifies the handshake. Triggers: "set up rigor mcp", "give my AI agent Rigor tools", "wire Rigor into Claude Code / Cursor / Cline", "rigor MCP server". NOT for editor LSP integration (use rigor-editor-setup) or CI (use rigor-ci-setup).
|
|
5
|
+
license: MPL-2.0
|
|
6
|
+
metadata:
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
homepage: https://github.com/rigortype/rigor
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rigor MCP Setup
|
|
12
|
+
|
|
13
|
+
`rigor mcp` is the Model Context Protocol server bundled with the
|
|
14
|
+
`rigortype` gem. It exposes Rigor's analysis as JSON-RPC tool calls over
|
|
15
|
+
stdio, so an AI coding agent can call Rigor directly during a session —
|
|
16
|
+
check types before a refactor, look up the type at a cursor, or triage a
|
|
17
|
+
project's diagnostics as review context. All tools are **read-only**
|
|
18
|
+
(write-side commands like `rigor init` / `sig-gen --write` are
|
|
19
|
+
deliberately excluded — modifying files stays the developer's call).
|
|
20
|
+
|
|
21
|
+
The authoritative, per-client configuration lives in the manual. With
|
|
22
|
+
Rigor installed, read it **offline** with no network round-trip:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
rigor docs mcp-server
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
(Web fallback, only before Rigor is installed:
|
|
29
|
+
**[Rigor MCP Server — AI Agent Integration](https://github.com/rigortype/rigor/blob/master/docs/manual/10-mcp-server.md)**.)
|
|
30
|
+
This skill is the *workflow* around it (identify the client → apply the
|
|
31
|
+
manual's snippet → verify the handshake), so it does not duplicate (and
|
|
32
|
+
cannot stale-out) the config details.
|
|
33
|
+
|
|
34
|
+
## When to use
|
|
35
|
+
|
|
36
|
+
- A developer wants their AI agent to call Rigor's tools mid-session.
|
|
37
|
+
- A project commits a shared MCP config (`.mcp.json`, `.cursor/mcp.json`)
|
|
38
|
+
and you want to add Rigor to it for the whole team.
|
|
39
|
+
|
|
40
|
+
## When NOT to use
|
|
41
|
+
|
|
42
|
+
- Editor (human) integration — that is `rigor-editor-setup` (`rigor lsp`).
|
|
43
|
+
- The project has no Rigor config yet — run `rigor-project-init` first
|
|
44
|
+
(the MCP tools use the same `.rigor.yml` discovery as `rigor check`).
|
|
45
|
+
|
|
46
|
+
## The tools you are wiring in
|
|
47
|
+
|
|
48
|
+
`rigor_check`, `rigor_type_of`, `rigor_triage`, `rigor_annotate`,
|
|
49
|
+
`rigor_sig_gen`, `rigor_explain`, `rigor_coverage` — each the JSON form
|
|
50
|
+
of the matching `rigor` CLI command. See the manual's **Tool reference**
|
|
51
|
+
for inputs and outputs.
|
|
52
|
+
|
|
53
|
+
## The one stable fact
|
|
54
|
+
|
|
55
|
+
Every client config simply launches **`rigor mcp`** (stdio) and needs
|
|
56
|
+
**`rigor` on the agent's `PATH`** — the same executable `rigor check`
|
|
57
|
+
uses. For agents that do not inherit your shell, the `mise` shim path is
|
|
58
|
+
the most reliable channel (see `rigor docs install`, or
|
|
59
|
+
[Installing Rigor](https://github.com/rigortype/rigor/blob/master/docs/install.md)
|
|
60
|
+
on the web). Do **not** add `rigortype` to the project's `Gemfile` — it
|
|
61
|
+
is a tool, not a library.
|
|
62
|
+
|
|
63
|
+
## Procedure
|
|
64
|
+
|
|
65
|
+
### Phase 1 — confirm the analyzer works from the CLI first
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
rigor check <a-file-or-dir>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The MCP tools share `rigor check`'s config discovery. If `check` works
|
|
72
|
+
from the project root, the tools will too.
|
|
73
|
+
|
|
74
|
+
### Phase 2 — identify the client and apply the config
|
|
75
|
+
|
|
76
|
+
Ask the developer which agent they use, or detect a committed config
|
|
77
|
+
(`.mcp.json`, `.cursor/mcp.json`, `.claude/settings.json`). Apply the
|
|
78
|
+
matching snippet from the manual's **Client wiring** section verbatim —
|
|
79
|
+
it covers Claude Desktop, Claude Code CLI, Cursor, Cline, and a generic
|
|
80
|
+
stdio client:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
rigor docs mcp-server
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
(or, pre-install, the web copy:
|
|
87
|
+
<https://github.com/rigortype/rigor/blob/master/docs/manual/10-mcp-server.md>)
|
|
88
|
+
|
|
89
|
+
Each snippet is the same shape — `{"command": "rigor", "args": ["mcp"]}`.
|
|
90
|
+
If the project commits a shared MCP config, add the Rigor entry there and
|
|
91
|
+
commit it so every contributor gets it.
|
|
92
|
+
|
|
93
|
+
**Working-directory gotcha (from the manual's Troubleshooting):** the
|
|
94
|
+
server discovers config from the directory it is launched in. If the
|
|
95
|
+
client starts `rigor mcp` from `$HOME` or a temp dir, no `.rigor.yml` is
|
|
96
|
+
found and tools return an empty set. Pin it with
|
|
97
|
+
`"args": ["mcp", "--config=/abs/path/.rigor.yml"]`, or pass absolute
|
|
98
|
+
`paths` in the tool call.
|
|
99
|
+
|
|
100
|
+
### Phase 3 — verify the handshake
|
|
101
|
+
|
|
102
|
+
Confirm `rigor mcp` answers the MCP `initialize` request:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
echo '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}' | rigor mcp
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A JSON `result` with `serverInfo.name: "rigor"` means it works; nothing
|
|
109
|
+
or a shell error means `rigor` is not on the PATH the client uses. Then
|
|
110
|
+
restart the client and confirm the `rigor_*` tools appear in its tool
|
|
111
|
+
palette.
|
|
112
|
+
|
|
113
|
+
## Next step
|
|
114
|
+
|
|
115
|
+
Re-run `rigor skill describe` for the next move — with the agent able to
|
|
116
|
+
call Rigor's tools, raising protection (`rigor-protection-uplift`) or
|
|
117
|
+
reducing a baseline (`rigor-baseline-reduce`) is a tighter loop.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rigor-monkeypatch-resolve
|
|
3
|
+
description: |
|
|
4
|
+
Resolve a cluster of `call.unresolved-toplevel` / `call.undefined-method` diagnostics that are really the project's own monkey-patches (core-extension files that add methods with literal `def`) by wiring them into `pre_eval:` so Rigor pre-evaluates them and learns the added methods. Triggers: "Rigor flags methods my core-ext adds", "undefined-method on my own monkey-patch", "set up pre_eval", "lots of unresolved-toplevel from my lib/core_ext". NOT for dynamically-generated methods (define_method / method_missing / class_eval heredocs) — those need a plugin (use rigor-plugin-author) — and NOT for an external gem with no RBS (use rigor-rbs-setup).
|
|
5
|
+
license: MPL-2.0
|
|
6
|
+
metadata:
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
homepage: https://github.com/rigortype/rigor
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rigor Monkeypatch Resolve
|
|
12
|
+
|
|
13
|
+
When a project adds methods to existing classes in its own files — the
|
|
14
|
+
classic `lib/core_ext/*.rb` `class String; def squish; …; end` pattern —
|
|
15
|
+
Rigor does not see them unless told to, so every call to the added method
|
|
16
|
+
fires `call.undefined-method` (or `call.unresolved-toplevel` for a
|
|
17
|
+
top-level helper). `pre_eval:` ([ADR-17](https://github.com/rigortype/rigor/blob/master/docs/adr/17-monkey-patch-pre-evaluation.md))
|
|
18
|
+
fixes this: it pre-evaluates the listed project files and registers every
|
|
19
|
+
method they define with a **literal** `def` / `def self.`.
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
- `rigor triage` shows a cluster of `call.undefined-method` /
|
|
24
|
+
`call.unresolved-toplevel` whose receiver is a core class the project
|
|
25
|
+
monkey-patches (or a top-level helper the project defines).
|
|
26
|
+
|
|
27
|
+
## When NOT to use
|
|
28
|
+
|
|
29
|
+
- The methods are **generated dynamically** (`define_method` with a
|
|
30
|
+
computed name, `method_missing`, a `class_eval <<~RUBY … def #{name}`
|
|
31
|
+
heredoc). `pre_eval:` walks for *literal* `def`s and will not find
|
|
32
|
+
them — that residue is a `rigor-plugin-author` job.
|
|
33
|
+
- The cluster is on a **third-party gem** with no RBS — that is
|
|
34
|
+
`rigor-rbs-setup`.
|
|
35
|
+
|
|
36
|
+
## Procedure
|
|
37
|
+
|
|
38
|
+
### Phase 1 — confirm it is a monkey-patch cluster
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
rigor triage --format json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Look at the `selectors` / `hotspots`: a cluster keyed on a core receiver
|
|
45
|
+
(`String#squish`, `Integer#…`) or a top-level helper, all pointing back
|
|
46
|
+
to a file in your project that defines them. Open that file and confirm
|
|
47
|
+
the methods are written as literal `def` / `def self.`.
|
|
48
|
+
|
|
49
|
+
### Phase 2 — wire the file(s) into `pre_eval:`
|
|
50
|
+
|
|
51
|
+
Add the defining file paths to `pre_eval:` in `.rigor.dist.yml` (paths
|
|
52
|
+
are resolved relative to the config file):
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
pre_eval:
|
|
56
|
+
- lib/core_ext/string.rb
|
|
57
|
+
- lib/core_ext/integer.rb
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
List the **files that contain the `def`s**, not the call sites.
|
|
61
|
+
|
|
62
|
+
### Phase 3 — verify the cluster cleared
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
rigor check
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The `call.undefined-method` cluster on those methods should be gone. If
|
|
69
|
+
some survive, those specific methods are almost certainly
|
|
70
|
+
**dynamically generated** — `pre_eval:` cannot reach them, and the
|
|
71
|
+
durable fix is a project plugin (hand off to `rigor-plugin-author`). A
|
|
72
|
+
genuine typo (a real undefined method) correctly keeps firing — do not
|
|
73
|
+
add it to `pre_eval:`.
|
|
74
|
+
|
|
75
|
+
## Next step
|
|
76
|
+
|
|
77
|
+
Re-run `rigor skill describe`. With the monkey-patch noise gone, the
|
|
78
|
+
remaining diagnostics are a cleaner target for `rigor-baseline-reduce`
|
|
79
|
+
or `rigor-protection-uplift`.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rigor-next-steps
|
|
3
|
+
description: |
|
|
4
|
+
Route a project to its next Rigor step from a single entry point: resolve the `rigor` command (install it if missing), onboard the project if it has no Rigor config, then ask `rigor skill describe` what to do next and hand off to the matching skill. Triggers: "what should we do next with Rigor?", "I want to use Rigor on this project", "help me move this project forward with Rigor", "where do I start with Rigor?". NOT a replacement for the task-specific skills it routes to — it hands off to them.
|
|
5
|
+
license: MPL-2.0
|
|
6
|
+
metadata:
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
homepage: https://github.com/rigortype/rigor
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rigor Next Steps
|
|
12
|
+
|
|
13
|
+
The single entry point for "what should we do next with Rigor on this
|
|
14
|
+
project?" This skill is deliberately **thin**: it only does the
|
|
15
|
+
bootstrap that must happen before `rigor` can speak for itself, then it
|
|
16
|
+
hands the decision to the installed `rigor` binary via
|
|
17
|
+
`rigor skill describe`. That keeps the advice **current to the
|
|
18
|
+
installed version** — the routing intelligence lives in the gem, not
|
|
19
|
+
frozen in this file (ADR-73).
|
|
20
|
+
|
|
21
|
+
Work top to bottom. Stop at the first step that is not yet satisfied,
|
|
22
|
+
complete it, then continue.
|
|
23
|
+
|
|
24
|
+
## Step 1 — Resolve the `rigor` command
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
rigor --version
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If it prints a version (`rigor 0.x.y`), `rigor` is installed — go to
|
|
31
|
+
**Step 3**. If the command is not found, go to **Step 2**.
|
|
32
|
+
|
|
33
|
+
## Step 2 — Install Rigor (only if Step 1 failed)
|
|
34
|
+
|
|
35
|
+
Rigor is a standalone tool, **not** a project dependency — do not add it
|
|
36
|
+
to the `Gemfile`. Fetch and follow the install guide, which is written
|
|
37
|
+
for an AI agent and picks the right channel (mise / asdf / gem / Docker)
|
|
38
|
+
for the environment:
|
|
39
|
+
|
|
40
|
+
- <https://raw.githubusercontent.com/rigortype/rigor/master/docs/install.md>
|
|
41
|
+
|
|
42
|
+
Follow it until `rigor --version` succeeds, then continue to Step 3.
|
|
43
|
+
|
|
44
|
+
(This is the one step that cannot be driven by `rigor skill describe` —
|
|
45
|
+
there is no binary to ask yet. It is the only install knowledge this
|
|
46
|
+
skill carries, kept as a pointer rather than a copy so it does not go
|
|
47
|
+
stale.)
|
|
48
|
+
|
|
49
|
+
## Step 3 — Set the project up if it has no Rigor config
|
|
50
|
+
|
|
51
|
+
Check the project root for `.rigor.yml` or `.rigor.dist.yml`. If
|
|
52
|
+
**neither** exists, the project has never been onboarded — set it up
|
|
53
|
+
first:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
rigor skill rigor-project-init
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Follow that skill's body top to bottom (it detects the stack, picks
|
|
60
|
+
plugins, writes `.rigor.dist.yml`, and snapshots a baseline if needed),
|
|
61
|
+
then come back to Step 4.
|
|
62
|
+
|
|
63
|
+
## Step 4 — Ask `rigor skill describe` what to do next
|
|
64
|
+
|
|
65
|
+
Once `rigor` is installed and the project is configured, let the live
|
|
66
|
+
command pick the next move:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
rigor skill describe
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Its output reports the project's current state (config / baseline /
|
|
73
|
+
`sig/` / CI), names a **recommended next skill** with a one-line reason,
|
|
74
|
+
and lists every skill you can run next with its current description.
|
|
75
|
+
|
|
76
|
+
## Step 5 — Route and act
|
|
77
|
+
|
|
78
|
+
Read the `## Recommended next step` and `## For the agent` sections of
|
|
79
|
+
the `describe` output, then:
|
|
80
|
+
|
|
81
|
+
- **Present the recommendation to the user proactively** — e.g. "Rigor
|
|
82
|
+
is set up but not wired into CI; I suggest the `rigor-ci-setup` skill
|
|
83
|
+
to lock in the regression guard. Shall I?" — rather than only asking
|
|
84
|
+
an open question.
|
|
85
|
+
- If the user has a different goal, ask which skill they want from the
|
|
86
|
+
listed catalog.
|
|
87
|
+
- Then load the chosen skill and follow it:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
rigor skill <chosen-skill>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Re-run `rigor skill describe` any time you want the next step again — it
|
|
94
|
+
always reflects the project's current state, so this skill stays useful
|
|
95
|
+
across the whole adoption journey, not just the first run.
|
|
96
|
+
|
|
97
|
+
## Reading the docs offline
|
|
98
|
+
|
|
99
|
+
Once Rigor is installed (Step 1 succeeded), the full manual **and
|
|
100
|
+
handbook** ship inside the gem — read any chapter **without the
|
|
101
|
+
network**:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
rigor docs # the offline doc index (what's available)
|
|
105
|
+
rigor docs <name> # a chapter, e.g. `rigor docs editor-integration`
|
|
106
|
+
rigor docs --list # name + absolute path for every bundled doc
|
|
107
|
+
rigor docs --list handbook # just the handbook chapters
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Prefer this over fetching the same page from the web: it is the exact
|
|
111
|
+
copy that shipped with the installed `rigor`, so it always matches the
|
|
112
|
+
binary's behaviour. (The web copy at <https://rigor.typedduck.fail/llms.txt>
|
|
113
|
+
is for the contributor-facing corpus and for reading before install.)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rigor-plugin-tune
|
|
3
|
+
description: |
|
|
4
|
+
Re-scan a configured project's Gemfile.lock against Rigor's bundled plugin catalogue and enable the plugins that match its current dependencies (Rails, RSpec, dry-rb, Sidekiq, Devise, …), then verify they all load. Run it after adding a gem, or when an onboarding predates a dependency the project now uses. Triggers: "enable the right Rigor plugins", "I added gem X, does Rigor have a plugin?", "which rigor plugins should this project use?", "rigor plugins for my stack". NOT for first-time onboarding (use rigor-project-init, which does the initial selection) and NOT for authoring a new plugin (use rigor-plugin-author).
|
|
5
|
+
license: MPL-2.0
|
|
6
|
+
metadata:
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
homepage: https://github.com/rigortype/rigor
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rigor Plugin Tune
|
|
12
|
+
|
|
13
|
+
`rigor-project-init` selects plugins once, at onboarding. Dependencies
|
|
14
|
+
drift afterward — a project adds Sidekiq, adopts dry-rb, brings in
|
|
15
|
+
shoulda-matchers — and the bundled Rigor plugin that understands the new
|
|
16
|
+
gem is not enabled until someone wires it. This skill is the recurring
|
|
17
|
+
"re-match plugins to the current Gemfile.lock" pass.
|
|
18
|
+
|
|
19
|
+
All `rigor-*` plugins ship **bundled inside the `rigortype` gem** — no
|
|
20
|
+
separate install. Enabling one is just adding its id to `plugins:` in
|
|
21
|
+
`.rigor.dist.yml`.
|
|
22
|
+
|
|
23
|
+
## When to use
|
|
24
|
+
|
|
25
|
+
- The project added (or removed) a gem since it was onboarded.
|
|
26
|
+
- `rigor check` is missing framework knowledge (e.g. ActiveRecord
|
|
27
|
+
relation methods, RSpec matchers) that a bundled plugin would supply.
|
|
28
|
+
|
|
29
|
+
## When NOT to use
|
|
30
|
+
|
|
31
|
+
- First-time setup — `rigor-project-init` does the initial selection.
|
|
32
|
+
- A gem with **no** bundled plugin and no RBS — that is
|
|
33
|
+
`rigor-rbs-setup` (community RBS) or, for your own DSL,
|
|
34
|
+
`rigor-plugin-author`.
|
|
35
|
+
|
|
36
|
+
## Procedure
|
|
37
|
+
|
|
38
|
+
### Phase 1 — list what is enabled vs what is installed
|
|
39
|
+
|
|
40
|
+
Read the current `plugins:` in `.rigor.dist.yml`, and the project's
|
|
41
|
+
dependencies in `Gemfile.lock`.
|
|
42
|
+
|
|
43
|
+
### Phase 2 — match against the bundled catalogue
|
|
44
|
+
|
|
45
|
+
The authoritative, current list of bundled plugins (the count drifts as
|
|
46
|
+
new ones land) is the catalogue:
|
|
47
|
+
|
|
48
|
+
<https://github.com/rigortype/rigor/blob/master/plugins/README.md>
|
|
49
|
+
|
|
50
|
+
For each Gemfile.lock dependency, check whether a bundled `rigor-<gem>`
|
|
51
|
+
plugin exists and is **not** already in `plugins:`. Common matches:
|
|
52
|
+
`activerecord`/`actionpack` → the Rails plugins, `rspec` → `rigor-rspec`,
|
|
53
|
+
`dry-*` → the dry-rb plugins, `devise`, `sidekiq`, `sorbet`, etc. Add the
|
|
54
|
+
missing ones to `plugins:`.
|
|
55
|
+
|
|
56
|
+
### Phase 3 — verify every plugin loads
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
rigor plugins --strict
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`rigor plugins` reports the activation status of every configured plugin;
|
|
63
|
+
`--strict` exits non-zero on any activation failure (ideal as a CI gate).
|
|
64
|
+
Fix any failure it reports (a misspelled id, a missing `signature_paths:`)
|
|
65
|
+
before moving on.
|
|
66
|
+
|
|
67
|
+
### Phase 4 — re-check
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
rigor check
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The framework calls the newly-enabled plugins understand should now
|
|
74
|
+
resolve. If enabling a plugin surfaced new diagnostics, treat the diff as
|
|
75
|
+
usual (baseline regenerate in acknowledge mode, or fix genuine findings).
|
|
76
|
+
|
|
77
|
+
## Next step
|
|
78
|
+
|
|
79
|
+
Re-run `rigor skill describe` for the next move.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rigor-protection-uplift
|
|
3
|
+
description: |
|
|
4
|
+
Close the type-protection holes `rigor coverage --protection` surfaces: for each unprotected dispatch site, run `rigor sig-gen` first, hand-author only the minimal residual annotation, then verify with a double gate — the site becomes protected AND `rigor check` gains no new diagnostic. Triggers: "raise type protection", "add types where Rigor can't catch bugs", "act on coverage --protection / --mutation output", "make more of this code bug-catchable". NOT for Rigor's own `lib/` or the bundled plugins (use `rigor sig-gen` directly there and treat gaps as engine signal), and NOT for first-time setup (use rigor-project-init).
|
|
5
|
+
license: MPL-2.0
|
|
6
|
+
metadata:
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
homepage: https://github.com/rigortype/rigor
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Rigor Protection Uplift
|
|
12
|
+
|
|
13
|
+
`rigor coverage --protection` (Tier 1) and `rigor coverage --protection
|
|
14
|
+
--mutation` (Tier 2) *surface* "add a type here" — they never author the
|
|
15
|
+
type. This skill *acts* on that surfacing under the discipline that keeps
|
|
16
|
+
Rigor false-positive-safe: protection goes up, and not one line of
|
|
17
|
+
working code starts reporting a new diagnostic.
|
|
18
|
+
|
|
19
|
+
## When to use
|
|
20
|
+
|
|
21
|
+
- A user wants to raise how much of their code Rigor can actually catch
|
|
22
|
+
bugs in.
|
|
23
|
+
- You have `coverage --protection` output (an "add a type here" list) and
|
|
24
|
+
want to close it.
|
|
25
|
+
|
|
26
|
+
## When NOT to use
|
|
27
|
+
|
|
28
|
+
- **Rigor's own `lib/`, or the bundled `plugins/` / `examples/`** — the
|
|
29
|
+
self-check tree. Hand-authoring types there collides with the
|
|
30
|
+
sig-gen-first ethos; run `rigor sig-gen` directly and treat residual
|
|
31
|
+
gaps as engine signal to report, not a private fix.
|
|
32
|
+
- **"Make my code more precise" with no protection goal** — that is
|
|
33
|
+
`rigor coverage` (precision), not `--protection`.
|
|
34
|
+
- **A project with no Rigor config yet** — onboard first with
|
|
35
|
+
`rigor-project-init`.
|
|
36
|
+
|
|
37
|
+
## Load-bearing rules (read before touching a single type)
|
|
38
|
+
|
|
39
|
+
1. **The signal prioritises and verifies; the contract sources the
|
|
40
|
+
type.** Never write the type the coverage/mutation signal "wants".
|
|
41
|
+
Write the type the code *actually has*, derived from the
|
|
42
|
+
implementation and its callers. A type guessed from the signal is a
|
|
43
|
+
false-confidence type — worse than no type.
|
|
44
|
+
2. **sig-gen first.** A hand-written annotation is only the *residual*
|
|
45
|
+
`rigor sig-gen` cannot reach. Every residual is a sig-gen-improvement
|
|
46
|
+
to report, not just a private fix.
|
|
47
|
+
3. **"Minimal" = annotation footprint, not minimal-to-kill-the-mutant.**
|
|
48
|
+
Optimising literally for mutant death games the metric. Add the
|
|
49
|
+
smallest *true* annotation that models the contract; if that also
|
|
50
|
+
kills the mutant, good.
|
|
51
|
+
4. **Robustness (ADR-5).** Tighten returns, keep parameters lenient. An
|
|
52
|
+
over-tight param annotation breaks callers and breaches the
|
|
53
|
+
false-positive discipline.
|
|
54
|
+
|
|
55
|
+
## Procedure
|
|
56
|
+
|
|
57
|
+
### Phase 1 — surface the holes
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
rigor coverage --protection --format json PATHS
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Read the ranked "add a type here" list (`count`, `method_name`,
|
|
64
|
+
`examples`). Optionally confirm the highest-traffic ones actually buy
|
|
65
|
+
catching power with the Tier 2 deep dive:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
rigor coverage --protection --mutation --format json # changed-files by default
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Phase 2 — sig-gen first
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
rigor sig-gen --diff PATHS # inspect; --write to apply
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Adopt every concrete inferred signature. Note where sig-gen emits
|
|
78
|
+
`untyped` for a site on the "add a type here" list — that is the
|
|
79
|
+
**residual** Phase 3 owns.
|
|
80
|
+
|
|
81
|
+
### Phase 3 — author the residual (cheapest carrier per hole class)
|
|
82
|
+
|
|
83
|
+
| Hole class | Cheapest carrier |
|
|
84
|
+
| ----------------------------------- | --------------------------------------------- |
|
|
85
|
+
| `Dynamic` method return | annotate that method's return in `sig/…rbs` |
|
|
86
|
+
| `Dynamic[top] \| nil` ivar read | `# @rbs @field: T` (ADR-58 territory) |
|
|
87
|
+
| untyped param feeding the receiver | a *lenient* param annotation |
|
|
88
|
+
|
|
89
|
+
Write the minimal true type. Prefer annotating the *upstream* source of
|
|
90
|
+
the Dynamic (the method return / the ivar) over the call site itself.
|
|
91
|
+
|
|
92
|
+
**Trap (carrier-additivity):** a sidecar `sig/…rbs` is NOT purely
|
|
93
|
+
additive. Declaring a class there flips it from inference-mode to
|
|
94
|
+
RBS-declared mode and *drops every member the RBS omits* — a lone
|
|
95
|
+
`def formatted: () -> String` can make Rigor forget the inferred
|
|
96
|
+
`initialize` and reject `Money.new(500)`. So either (1) adopt the full
|
|
97
|
+
Phase-2 sig-gen base into the file and add the residual on top, or
|
|
98
|
+
(2) use an in-place additive carrier (rbs-inline `#:` / a
|
|
99
|
+
`%a{rigor:v1:…}` return-override) that annotates the method without
|
|
100
|
+
re-declaring the class. "Minimal footprint" means the smallest *true*
|
|
101
|
+
type, never the smallest *file*.
|
|
102
|
+
|
|
103
|
+
### Phase 4 — double-gate verify (both must hold)
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
rigor coverage --protection PATHS # (a) the site is now protected / ratio up
|
|
107
|
+
rigor check PATHS # (b) no NEW diagnostic vs the post-sig-gen baseline
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If (b) regresses, the annotation modeled the wrong contract — **revert
|
|
111
|
+
it**, do not suppress the diagnostic. If (a) did not move, the carrier
|
|
112
|
+
was wrong (often: you typed the call site, not the upstream Dynamic
|
|
113
|
+
source). Gate (b) is a *diff* against the post-sig-gen state, not an
|
|
114
|
+
absolute "zero diagnostics" — sig-gen itself can surface the
|
|
115
|
+
acknowledge-mode FP envelope a project baseline absorbs; this skill owns
|
|
116
|
+
only the increment it adds.
|
|
117
|
+
|
|
118
|
+
### Phase 5 — feed the residual back
|
|
119
|
+
|
|
120
|
+
File each Phase-3 residual as a sig-gen gap (what shape did inference
|
|
121
|
+
miss?). The hand annotation is the stopgap; the durable fix is raising
|
|
122
|
+
inference so the residual disappears.
|
|
123
|
+
|
|
124
|
+
## Honest bounds
|
|
125
|
+
|
|
126
|
+
Hand-RBS uplift is a finisher, not a path to 80% protection. The
|
|
127
|
+
dominant remaining holes after this loop are intractable from
|
|
128
|
+
annotation alone — external-gem Dynamic receivers, polymorphic value
|
|
129
|
+
types, generic type parameters, dynamically-built classes. Those need
|
|
130
|
+
parametric types / external RBS / engine folding (and are where the
|
|
131
|
+
`rigor-plugin-author` escalation or a Rigor issue comes in), not another
|
|
132
|
+
hand annotation. Expect a low-20s → low-30s% protection lift on a mature
|
|
133
|
+
library, at zero diagnostic cost.
|