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.
- checksums.yaml +4 -4
- data/README.md +41 -14
- 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 +569 -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 +539 -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 +1 -1
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli/triage_command.rb +8 -2
- data/lib/rigor/cli/triage_renderer.rb +4 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +124 -32
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/triage/catalogue.rb +16 -1
- data/lib/rigor/triage.rb +30 -7
- 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 +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
|
-
#
|
|
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
|
|
23
|
-
# - `rigor skill
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# the
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# - `rigor skill
|
|
31
|
-
#
|
|
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`
|
|
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 <
|
|
46
|
+
Usage: rigor skill [<name>] [--path <name>] [--list] [--describe]
|
|
47
|
+
|
|
48
|
+
With no argument, lists the bundled skills.
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
45
|
-
rigor skill
|
|
46
|
-
rigor skill path
|
|
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
|
-
|
|
84
|
+
rewrite_legacy_verb!
|
|
56
85
|
|
|
57
|
-
case
|
|
58
|
-
when
|
|
59
|
-
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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
|
|
113
|
-
#
|
|
114
|
-
#
|
|
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
|