rigortype 0.2.1 → 0.2.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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -14
  3. data/docs/handbook/01-getting-started.md +311 -0
  4. data/docs/handbook/02-everyday-types.md +337 -0
  5. data/docs/handbook/03-narrowing.md +359 -0
  6. data/docs/handbook/04-tuples-and-shapes.md +321 -0
  7. data/docs/handbook/05-methods-and-blocks.md +339 -0
  8. data/docs/handbook/06-classes.md +305 -0
  9. data/docs/handbook/07-rbs-and-extended.md +427 -0
  10. data/docs/handbook/08-understanding-errors.md +373 -0
  11. data/docs/handbook/09-plugins.md +241 -0
  12. data/docs/handbook/10-sorbet.md +347 -0
  13. data/docs/handbook/11-sig-gen.md +312 -0
  14. data/docs/handbook/12-lightweight-hkt.md +333 -0
  15. data/docs/handbook/README.md +275 -0
  16. data/docs/handbook/appendix-elixir.md +370 -0
  17. data/docs/handbook/appendix-go.md +399 -0
  18. data/docs/handbook/appendix-java-csharp.md +470 -0
  19. data/docs/handbook/appendix-liskov.md +580 -0
  20. data/docs/handbook/appendix-mypy.md +370 -0
  21. data/docs/handbook/appendix-phpstan.md +338 -0
  22. data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
  23. data/docs/handbook/appendix-rust.md +446 -0
  24. data/docs/handbook/appendix-steep.md +336 -0
  25. data/docs/handbook/appendix-type-theory.md +1662 -0
  26. data/docs/handbook/appendix-typeprof.md +416 -0
  27. data/docs/handbook/appendix-typescript.md +332 -0
  28. data/docs/install.md +189 -0
  29. data/docs/llms.txt +72 -0
  30. data/docs/manual/01-installation.md +342 -0
  31. data/docs/manual/02-cli-reference.md +569 -0
  32. data/docs/manual/03-configuration.md +152 -0
  33. data/docs/manual/04-diagnostics.md +206 -0
  34. data/docs/manual/05-inspecting-types.md +109 -0
  35. data/docs/manual/06-baseline.md +104 -0
  36. data/docs/manual/07-plugins.md +92 -0
  37. data/docs/manual/08-skills.md +143 -0
  38. data/docs/manual/09-editor-integration.md +245 -0
  39. data/docs/manual/10-mcp-server.md +539 -0
  40. data/docs/manual/11-ci.md +274 -0
  41. data/docs/manual/12-caching.md +116 -0
  42. data/docs/manual/13-troubleshooting.md +120 -0
  43. data/docs/manual/14-rails-quickstart.md +332 -0
  44. data/docs/manual/15-type-protection-coverage.md +204 -0
  45. data/docs/manual/16-rbs-extended-annotations.md +190 -0
  46. data/docs/manual/17-driving-improvement.md +160 -0
  47. data/docs/manual/README.md +87 -0
  48. data/docs/manual/ci-templates/README.md +58 -0
  49. data/docs/manual/plugins/README.md +86 -0
  50. data/docs/manual/plugins/rigor-actioncable.md +78 -0
  51. data/docs/manual/plugins/rigor-actionmailer.md +74 -0
  52. data/docs/manual/plugins/rigor-actionpack.md +80 -0
  53. data/docs/manual/plugins/rigor-activejob.md +58 -0
  54. data/docs/manual/plugins/rigor-activerecord.md +102 -0
  55. data/docs/manual/plugins/rigor-activestorage.md +74 -0
  56. data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
  57. data/docs/manual/plugins/rigor-devise.md +70 -0
  58. data/docs/manual/plugins/rigor-dry-schema.md +56 -0
  59. data/docs/manual/plugins/rigor-dry-struct.md +60 -0
  60. data/docs/manual/plugins/rigor-dry-types.md +59 -0
  61. data/docs/manual/plugins/rigor-dry-validation.md +62 -0
  62. data/docs/manual/plugins/rigor-factorybot.md +76 -0
  63. data/docs/manual/plugins/rigor-graphql.md +89 -0
  64. data/docs/manual/plugins/rigor-hanami.md +83 -0
  65. data/docs/manual/plugins/rigor-mangrove.md +73 -0
  66. data/docs/manual/plugins/rigor-minitest.md +86 -0
  67. data/docs/manual/plugins/rigor-pundit.md +72 -0
  68. data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
  69. data/docs/manual/plugins/rigor-rails-routes.md +94 -0
  70. data/docs/manual/plugins/rigor-rails.md +44 -0
  71. data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
  72. data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
  73. data/docs/manual/plugins/rigor-rspec.md +86 -0
  74. data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
  75. data/docs/manual/plugins/rigor-sidekiq.md +78 -0
  76. data/docs/manual/plugins/rigor-sinatra.md +61 -0
  77. data/docs/manual/plugins/rigor-sorbet.md +63 -0
  78. data/docs/manual/plugins/rigor-statesman.md +75 -0
  79. data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
  80. data/exe/rigor +1 -1
  81. data/lib/rigor/analysis/incremental_session.rb +4 -2
  82. data/lib/rigor/analysis/run_stats.rb +13 -1
  83. data/lib/rigor/analysis/runner.rb +54 -12
  84. data/lib/rigor/cli/check_command.rb +1 -1
  85. data/lib/rigor/cli/docs_command.rb +248 -0
  86. data/lib/rigor/cli/skill_command.rb +103 -41
  87. data/lib/rigor/cli/skill_describe.rb +346 -0
  88. data/lib/rigor/cli/triage_command.rb +8 -2
  89. data/lib/rigor/cli/triage_renderer.rb +4 -0
  90. data/lib/rigor/cli.rb +25 -3
  91. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +124 -32
  92. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
  93. data/lib/rigor/inference/scope_indexer.rb +87 -89
  94. data/lib/rigor/plugin/isolation.rb +5 -5
  95. data/lib/rigor/plugin/loader.rb +4 -2
  96. data/lib/rigor/triage/catalogue.rb +16 -1
  97. data/lib/rigor/triage.rb +30 -7
  98. data/lib/rigor/version.rb +1 -1
  99. data/skills/rigor-ask/SKILL.md +172 -0
  100. data/skills/rigor-doctor/SKILL.md +87 -0
  101. data/skills/rigor-editor-setup/SKILL.md +114 -0
  102. data/skills/rigor-mcp-setup/SKILL.md +117 -0
  103. data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
  104. data/skills/rigor-next-steps/SKILL.md +113 -0
  105. data/skills/rigor-plugin-tune/SKILL.md +79 -0
  106. data/skills/rigor-protection-uplift/SKILL.md +133 -0
  107. data/skills/rigor-rbs-setup/SKILL.md +128 -0
  108. data/skills/rigor-upgrade/SKILL.md +79 -0
  109. metadata +90 -1
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command"
4
+
5
+ module Rigor
6
+ class CLI
7
+ # `rigor docs` — serve the documentation bundled with the
8
+ # `rigortype` gem OFFLINE (ADR-74). The skills (`rigor skill <name>`)
9
+ # already ride in the gem; this is the doc twin, so once Rigor is
10
+ # installed an agent can read the guidance the SKILL-driven UX routes
11
+ # to without the network. The canonical web copy is
12
+ # rigor.typedduck.fail/llms.txt; the gem ships `docs/install.md`,
13
+ # `docs/llms.txt`, and the full user-facing **manual** and
14
+ # **handbook** (the drive-Rigor chapters — the contributor-facing
15
+ # ADR / spec / notes corpus stays web-only).
16
+ #
17
+ # Grammar (mirrors `rigor skill`): the positional slot is always a
18
+ # doc *name*; alternative outputs are flags, so a page named `list`
19
+ # or `path` can never be shadowed by a verb.
20
+ #
21
+ # - `rigor docs` — print the bundled `llms.txt` index.
22
+ # - `rigor docs <name>` — print a doc page to stdout.
23
+ # - `rigor docs --path <name>` — one-line absolute path, for a Read tool.
24
+ # - `rigor docs --list [<cat>]` — table of name + path (optionally one category).
25
+ #
26
+ # `<name>` resolves a category-qualified path (`handbook/03-narrowing`),
27
+ # a prefixed basename (`03-narrowing`), or a short name (`narrowing`,
28
+ # when it is unique across categories).
29
+ #
30
+ # The pre-v0.3.0 verb spellings `rigor docs list` / `rigor docs path
31
+ # <name>` still work but emit a stderr deprecation notice; they are
32
+ # removed in v0.3.0 (see docs/ROADMAP.md § "Scheduled CLI
33
+ # deprecations").
34
+ class DocsCommand < Command
35
+ USAGE = <<~USAGE
36
+ Usage: rigor docs [<name>] [--path <name>] [--list [<category>]]
37
+
38
+ With no argument, prints the bundled llms.txt offline doc index.
39
+
40
+ rigor docs Print the offline doc index (llms.txt)
41
+ rigor docs <name> Print a doc page to stdout
42
+ rigor docs --path <name> Print the absolute path of a doc
43
+ rigor docs --list [<category>] List bundled docs (optionally one category)
44
+
45
+ Categories: manual, handbook (plus the top-level install guide).
46
+ A page is addressable by its category-qualified path
47
+ (`handbook/03-narrowing`), its prefixed name (`03-narrowing`),
48
+ or its short name (`narrowing`, when unique across categories).
49
+
50
+ Examples:
51
+ rigor docs
52
+ rigor docs install
53
+ rigor docs handbook/03-narrowing
54
+ rigor docs editor-integration
55
+ rigor docs --path 17-driving-improvement
56
+ rigor docs --list handbook
57
+
58
+ Deprecated (removed in v0.3.0) — use the flags above:
59
+ rigor docs list -> rigor docs --list
60
+ rigor docs path <name> -> rigor docs --path <name>
61
+ USAGE
62
+
63
+ # The bundled docs live at `<gem_root>/docs/`. From
64
+ # `lib/rigor/cli/docs_command.rb` that is three directories up.
65
+ DOCS_ROOT = File.expand_path("../../../docs", __dir__)
66
+ MANUAL_ROOT = File.join(DOCS_ROOT, "manual")
67
+ HANDBOOK_ROOT = File.join(DOCS_ROOT, "handbook")
68
+ LLMS_INDEX = File.join(DOCS_ROOT, "llms.txt")
69
+
70
+ # The verb subcommands the flags superseded keep working with a
71
+ # stderr deprecation notice until this version drops them. Each maps
72
+ # to the canonical advice printed and the flag it rewrites to.
73
+ LEGACY_VERB_REMOVAL = "v0.3.0"
74
+ LEGACY_VERBS = {
75
+ "list" => { old: "list", advice: "--list", flag: "--list" },
76
+ "path" => { old: "path <name>", advice: "--path <name>", flag: "--path" }
77
+ }.freeze
78
+
79
+ # @return [Integer] CLI exit status.
80
+ def run
81
+ rewrite_legacy_verb!
82
+
83
+ case @argv.first
84
+ when nil
85
+ run_index
86
+ when "-h", "--help", "help"
87
+ @out.puts(USAGE)
88
+ 0
89
+ when "--list"
90
+ @argv.shift
91
+ run_list(@argv.shift)
92
+ when "--path"
93
+ @argv.shift
94
+ run_path(@argv.shift)
95
+ when "--print"
96
+ @argv.shift
97
+ run_print(@argv.shift)
98
+ else
99
+ run_print(@argv.shift)
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ # Translate a deprecated verb spelling into its flag form, warning
106
+ # once on stderr, so the dispatch above only handles canonical forms.
107
+ def rewrite_legacy_verb!
108
+ spec = LEGACY_VERBS[@argv.first]
109
+ return unless spec
110
+
111
+ @err.puts(
112
+ "rigor docs: `#{spec.fetch(:old)}` is deprecated and will be removed in " \
113
+ "#{LEGACY_VERB_REMOVAL}; use `rigor docs #{spec.fetch(:advice)}` instead."
114
+ )
115
+ @argv[0] = spec.fetch(:flag)
116
+ end
117
+
118
+ def run_index
119
+ if File.file?(LLMS_INDEX)
120
+ @out.write(File.read(LLMS_INDEX))
121
+ 0
122
+ else
123
+ run_list(nil)
124
+ end
125
+ end
126
+
127
+ def run_list(category)
128
+ docs = discover_docs
129
+ if category
130
+ categories = docs.map { |doc| doc.fetch(:category) }.uniq
131
+ unless categories.include?(category)
132
+ @err.puts("Unknown category: #{category}")
133
+ @err.puts("Available categories: #{categories.join(', ')}")
134
+ return 1
135
+ end
136
+ docs = docs.select { |doc| doc.fetch(:category) == category }
137
+ end
138
+
139
+ if docs.empty?
140
+ @err.puts("No bundled docs found under #{DOCS_ROOT}")
141
+ return 1
142
+ end
143
+
144
+ width = docs.map { |doc| doc.fetch(:relative).length }.max
145
+ docs.each do |doc|
146
+ @out.puts(format("%-#{width}s %s", doc.fetch(:relative), doc.fetch(:path)))
147
+ end
148
+ 0
149
+ end
150
+
151
+ def run_print(name)
152
+ return usage_error("a doc name is required") if name.nil?
153
+
154
+ doc = resolve_doc(name)
155
+ return doc if doc.is_a?(Integer) # error status, already reported
156
+
157
+ # ASCII-only provenance header: the doc body is read with the
158
+ # external encoding (US-ASCII under a C locale), so a UTF-8 header
159
+ # would set the output buffer to UTF-8 and clash with the body.
160
+ @out.puts("<!-- rigor docs #{doc.fetch(:name)} (rigortype #{Rigor::VERSION}, offline) -->")
161
+ @out.puts
162
+ @out.write(File.read(doc.fetch(:path)))
163
+ 0
164
+ end
165
+
166
+ def run_path(name)
167
+ return usage_error("`--path` requires a doc name") if name.nil?
168
+
169
+ doc = resolve_doc(name)
170
+ return doc if doc.is_a?(Integer)
171
+
172
+ @out.puts(doc.fetch(:path))
173
+ 0
174
+ end
175
+
176
+ # Resolve a query to a single doc. Exact relative-path and
177
+ # prefixed-basename aliases are unique; a short (prefix-stripped)
178
+ # name is accepted only when one category owns it.
179
+ #
180
+ # @return [Hash, Integer] the doc entry, or an error exit status
181
+ # after the error has been written to `@err`.
182
+ def resolve_doc(query)
183
+ docs = discover_docs
184
+
185
+ exact = docs.find { |doc| doc.fetch(:exact_aliases).include?(query) }
186
+ return exact if exact
187
+
188
+ short = docs.select { |doc| doc.fetch(:short_name) == query }
189
+ case short.size
190
+ when 1 then short.first
191
+ when 0 then name_error(query)
192
+ else ambiguous_error(query, short)
193
+ end
194
+ end
195
+
196
+ # Every bundled doc, each carrying the aliases `rigor docs <name>`
197
+ # accepts and the category used by `--list`. `install.md` sits at
198
+ # the docs root (category `guide`); the rest are manual / handbook
199
+ # chapters.
200
+ def discover_docs
201
+ paths = [File.join(DOCS_ROOT, "install.md")]
202
+ paths += Dir.glob(File.join(MANUAL_ROOT, "**", "*.md")) # Dir.glob is already sorted
203
+ paths += Dir.glob(File.join(HANDBOOK_ROOT, "**", "*.md"))
204
+ paths.select { |path| File.file?(path) }.map { |path| doc_entry(path) }
205
+ end
206
+
207
+ def doc_entry(path)
208
+ relative = path.delete_prefix("#{DOCS_ROOT}/").delete_suffix(".md")
209
+ base = File.basename(path, ".md")
210
+ short = base.sub(/\A\d+-/, "")
211
+ segments = relative.split("/")
212
+ category = segments.size > 1 ? segments.first : "guide"
213
+ {
214
+ name: base,
215
+ relative: relative,
216
+ path: path,
217
+ category: category,
218
+ # Always-unique addresses: the `docs/`-relative path and the
219
+ # prefixed basename. `handbook/03-narrowing` and `03-narrowing`.
220
+ exact_aliases: [relative, base].uniq,
221
+ # The prefix-stripped short name (`narrowing`); ambiguous when
222
+ # two categories carry the same chapter slug (`plugins`).
223
+ short_name: short
224
+ }
225
+ end
226
+
227
+ def name_error(name)
228
+ @err.puts("Unknown doc: #{name}")
229
+ @err.puts("Available docs (try `rigor docs --list`):")
230
+ discover_docs.each { |doc| @err.puts(" #{doc.fetch(:relative)}") }
231
+ 1
232
+ end
233
+
234
+ def ambiguous_error(query, candidates)
235
+ @err.puts("Ambiguous doc name: #{query}")
236
+ @err.puts("Matches several docs — qualify with the category path:")
237
+ candidates.each { |doc| @err.puts(" #{doc.fetch(:relative)}") }
238
+ 1
239
+ end
240
+
241
+ def usage_error(message)
242
+ @err.puts(message)
243
+ @err.puts(USAGE)
244
+ Rigor::CLI::EXIT_USAGE
245
+ end
246
+ end
247
+ end
248
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require_relative "command"
4
4
 
5
- require "optparse"
6
-
7
5
  module Rigor
8
6
  class CLI
9
7
  # `rigor skill` — discover and print the SKILL.md files
@@ -17,59 +15,112 @@ module Rigor
17
15
  # gem checkout — the project being analysed has no copy, so an
18
16
  # AI agent has no a priori way to find them.
19
17
  #
20
- # This command exposes the bundled skills via three subcommands:
18
+ # Grammar (mirrors `rigor docs`): the positional slot is always a
19
+ # skill *name*; alternative outputs are flags, so a skill named
20
+ # `list` or `path` can never be shadowed by a verb.
21
21
  #
22
- # - `rigor skill list` table of name + absolute path.
23
- # - `rigor skill print <name>` short header (paths + how to use)
24
- # followed by the SKILL.md body. This
25
- # is the form AI agents should call;
26
- # the inline body plus the header's
27
- # absolute paths together let the
28
- # agent act with or without a file
29
- # reading tool.
30
- # - `rigor skill path <name>` one-line absolute path, suitable
31
- # as input to a Read tool.
22
+ # - `rigor skill` list bundled skills (the default).
23
+ # - `rigor skill <name>` print the SKILL.md body (header + body).
24
+ # This is the form AI agents call; the
25
+ # inline body plus the header's absolute
26
+ # paths let the agent act with or without
27
+ # a file-reading tool.
28
+ # - `rigor skill --path <name>`— one-line absolute path, for a Read tool.
29
+ # - `rigor skill --list` — table of name + absolute path.
30
+ # - `rigor skill --describe` ADR-73's live entry point: a cheap
31
+ # project-state probe + the recommended
32
+ # next skill + every skill's current
33
+ # description. The `rigor-next-steps`
34
+ # SKILL routes off this so no
35
+ # version-coupled guidance is frozen into
36
+ # the SKILL. Also spelled `describe`, and
37
+ # surfaced top-level as `rigor describe`.
32
38
  #
33
- # `rigor skill` with no subcommand is an alias for `list`.
39
+ # The pre-v0.3.0 verb spellings `rigor skill list` / `print <name>` /
40
+ # `path <name>` still work but emit a stderr deprecation notice; they
41
+ # are removed in v0.3.0 (see docs/ROADMAP.md § "Scheduled CLI
42
+ # deprecations"). `describe` is a no-argument action, not a name-slot
43
+ # verb, so it stays first-class alongside `--describe`.
34
44
  class SkillCommand < Command
35
45
  USAGE = <<~USAGE
36
- Usage: rigor skill <subcommand> [args]
46
+ Usage: rigor skill [<name>] [--path <name>] [--list] [--describe]
47
+
48
+ With no argument, lists the bundled skills.
37
49
 
38
- Subcommands:
39
- list List bundled skills (default when no subcommand given)
40
- print <name> Print the SKILL.md body for <name> to stdout, with a header
41
- path <name> Print the absolute path of the SKILL.md file for <name>
50
+ rigor skill List bundled skills
51
+ rigor skill <name> Print the SKILL.md body for <name> (with a header)
52
+ rigor skill --path <name> Print the absolute path of the SKILL.md file for <name>
53
+ rigor skill --list List bundled skills (name + absolute path)
54
+ rigor skill --describe Report project state + recommend the next skill to run
42
55
 
43
56
  Examples:
44
- rigor skill list
45
- rigor skill print rigor-project-init
46
- rigor skill path rigor-baseline-reduce
57
+ rigor skill
58
+ rigor skill rigor-project-init
59
+ rigor skill --path rigor-baseline-reduce
60
+ rigor skill --describe (also: rigor describe)
61
+
62
+ Deprecated (removed in v0.3.0) — use the forms above:
63
+ rigor skill list -> rigor skill --list
64
+ rigor skill print <name> -> rigor skill <name>
65
+ rigor skill path <name> -> rigor skill --path <name>
47
66
  USAGE
48
67
 
49
68
  # The bundled skills live at `<gem_root>/skills/`. From
50
69
  # `lib/rigor/cli/skill_command.rb` that is three directories up.
51
70
  SKILLS_ROOT = File.expand_path("../../../skills", __dir__)
52
71
 
72
+ # The verb subcommands the flags superseded keep working with a
73
+ # stderr deprecation notice until this version drops them. Each maps
74
+ # to the canonical advice printed and the flag it rewrites to.
75
+ LEGACY_VERB_REMOVAL = "v0.3.0"
76
+ LEGACY_VERBS = {
77
+ "list" => { old: "list", advice: "--list", flag: "--list" },
78
+ "print" => { old: "print <name>", advice: "<name>", flag: "--print" },
79
+ "path" => { old: "path <name>", advice: "--path <name>", flag: "--path" }
80
+ }.freeze
81
+
53
82
  # @return [Integer] CLI exit status.
54
83
  def run
55
- subcommand = @argv.shift || "list"
84
+ rewrite_legacy_verb!
56
85
 
57
- case subcommand
58
- when "list" then run_list
59
- when "print" then run_print
60
- when "path" then run_path
86
+ case @argv.first
87
+ when nil
88
+ run_list
61
89
  when "-h", "--help", "help"
62
90
  print_usage(@out)
63
91
  0
92
+ when "describe", "--describe"
93
+ @argv.shift
94
+ run_describe
95
+ when "--list"
96
+ @argv.shift
97
+ run_list
98
+ when "--path"
99
+ @argv.shift
100
+ run_path(@argv.shift)
101
+ when "--print"
102
+ @argv.shift
103
+ run_print(@argv.shift)
64
104
  else
65
- @err.puts("Unknown subcommand: #{subcommand}")
66
- print_usage(@err)
67
- Rigor::CLI::EXIT_USAGE
105
+ run_print(@argv.shift)
68
106
  end
69
107
  end
70
108
 
71
109
  private
72
110
 
111
+ # Translate a deprecated verb spelling into its flag form, warning
112
+ # once on stderr, so the dispatch above only handles canonical forms.
113
+ def rewrite_legacy_verb!
114
+ spec = LEGACY_VERBS[@argv.first]
115
+ return unless spec
116
+
117
+ @err.puts(
118
+ "rigor skill: `#{spec.fetch(:old)}` is deprecated and will be removed in " \
119
+ "#{LEGACY_VERB_REMOVAL}; use `rigor skill #{spec.fetch(:advice)}` instead."
120
+ )
121
+ @argv[0] = spec.fetch(:flag)
122
+ end
123
+
73
124
  def run_list
74
125
  skills = discover_skills
75
126
  if skills.empty?
@@ -84,9 +135,8 @@ module Rigor
84
135
  0
85
136
  end
86
137
 
87
- def run_print
88
- name = @argv.shift
89
- return usage_error("`print` requires a skill name") if name.nil?
138
+ def run_print(name)
139
+ return usage_error("a skill name is required") if name.nil?
90
140
 
91
141
  skill = find_skill(name)
92
142
  return name_error(name) if skill.nil?
@@ -97,9 +147,8 @@ module Rigor
97
147
  0
98
148
  end
99
149
 
100
- def run_path
101
- name = @argv.shift
102
- return usage_error("`path` requires a skill name") if name.nil?
150
+ def run_path(name)
151
+ return usage_error("`--path` requires a skill name") if name.nil?
103
152
 
104
153
  skill = find_skill(name)
105
154
  return name_error(name) if skill.nil?
@@ -108,11 +157,24 @@ module Rigor
108
157
  0
109
158
  end
110
159
 
160
+ # `rigor skill --describe` (also spelled `describe`) — ADR-73's
161
+ # live "brain", delegated to {SkillDescribe}: it probes the current
162
+ # project's state with cheap presence checks (it never runs `rigor
163
+ # check`), recommends the next skill to run, and prints every
164
+ # bundled skill's current frontmatter description, so the
165
+ # `rigor-next-steps` SKILL can route without copying any
166
+ # version-coupled guidance into itself.
167
+ def run_describe
168
+ require_relative "skill_describe"
169
+
170
+ @out.puts(SkillDescribe.new(skills: discover_skills).render)
171
+ 0
172
+ end
173
+
111
174
  # The header that precedes the SKILL.md body when an agent
112
- # runs `rigor skill print <name>`. Kept as `# `-prefixed
113
- # comment lines so the combined output remains parseable as
114
- # markdown — anything below `---` (the SKILL frontmatter
115
- # marker) is unchanged.
175
+ # runs `rigor skill <name>`. Kept as `# `-prefixed comment lines
176
+ # so the combined output remains parseable as markdown — anything
177
+ # below `---` (the SKILL frontmatter marker) is unchanged.
116
178
  def render_print_header(skill)
117
179
  references_dir = File.join(File.dirname(skill.fetch(:path)), "references")
118
180
  ref_line = if File.directory?(references_dir)
@@ -147,7 +209,7 @@ module Rigor
147
209
 
148
210
  def name_error(name)
149
211
  @err.puts("Unknown skill: #{name}")
150
- @err.puts("Available skills:")
212
+ @err.puts("Available skills (try `rigor skill --list`):")
151
213
  discover_skills.each { |s| @err.puts(" #{s.fetch(:name)}") }
152
214
  1
153
215
  end