docscribe 1.4.1 → 1.5.0

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +588 -104
  3. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  4. data/lib/docscribe/cli/config_builder.rb +180 -36
  5. data/lib/docscribe/cli/formatters/json.rb +294 -0
  6. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  7. data/lib/docscribe/cli/formatters/text.rb +208 -0
  8. data/lib/docscribe/cli/formatters.rb +26 -0
  9. data/lib/docscribe/cli/generate.rb +296 -125
  10. data/lib/docscribe/cli/init.rb +58 -14
  11. data/lib/docscribe/cli/options.rb +410 -133
  12. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  13. data/lib/docscribe/cli/run.rb +503 -189
  14. data/lib/docscribe/cli/sigs.rb +366 -0
  15. data/lib/docscribe/cli/update_types.rb +103 -0
  16. data/lib/docscribe/cli.rb +35 -9
  17. data/lib/docscribe/config/defaults.rb +16 -12
  18. data/lib/docscribe/config/emit.rb +18 -0
  19. data/lib/docscribe/config/filtering.rb +37 -31
  20. data/lib/docscribe/config/loader.rb +20 -13
  21. data/lib/docscribe/config/plugin.rb +2 -1
  22. data/lib/docscribe/config/rbs.rb +68 -27
  23. data/lib/docscribe/config/sorbet.rb +40 -17
  24. data/lib/docscribe/config/sorting.rb +2 -1
  25. data/lib/docscribe/config/template.rb +10 -1
  26. data/lib/docscribe/config/utils.rb +12 -9
  27. data/lib/docscribe/config.rb +3 -4
  28. data/lib/docscribe/infer/ast_walk.rb +1 -1
  29. data/lib/docscribe/infer/constants.rb +15 -0
  30. data/lib/docscribe/infer/literals.rb +39 -26
  31. data/lib/docscribe/infer/names.rb +24 -16
  32. data/lib/docscribe/infer/params.rb +57 -13
  33. data/lib/docscribe/infer/raises.rb +23 -15
  34. data/lib/docscribe/infer/returns.rb +784 -199
  35. data/lib/docscribe/infer.rb +28 -28
  36. data/lib/docscribe/inline_rewriter/collector.rb +816 -430
  37. data/lib/docscribe/inline_rewriter/doc_block.rb +323 -150
  38. data/lib/docscribe/inline_rewriter/doc_builder.rb +1837 -648
  39. data/lib/docscribe/inline_rewriter/source_helpers.rb +119 -71
  40. data/lib/docscribe/inline_rewriter/tag_sorter.rb +165 -107
  41. data/lib/docscribe/inline_rewriter.rb +1144 -727
  42. data/lib/docscribe/parsing.rb +29 -10
  43. data/lib/docscribe/plugin/base/collector_plugin.rb +3 -3
  44. data/lib/docscribe/plugin/base/tag_plugin.rb +1 -2
  45. data/lib/docscribe/plugin/context.rb +28 -18
  46. data/lib/docscribe/plugin/registry.rb +49 -23
  47. data/lib/docscribe/plugin/tag.rb +9 -14
  48. data/lib/docscribe/plugin.rb +54 -22
  49. data/lib/docscribe/types/provider_chain.rb +4 -2
  50. data/lib/docscribe/types/rbs/collection_loader.rb +2 -3
  51. data/lib/docscribe/types/rbs/provider.rb +127 -62
  52. data/lib/docscribe/types/rbs/type_formatter.rb +286 -77
  53. data/lib/docscribe/types/signature.rb +22 -42
  54. data/lib/docscribe/types/sorbet/base_provider.rb +51 -27
  55. data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
  56. data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
  57. data/lib/docscribe/types/yard/formatter.rb +100 -0
  58. data/lib/docscribe/types/yard/parser.rb +240 -0
  59. data/lib/docscribe/types/yard/types.rb +52 -0
  60. data/lib/docscribe/version.rb +1 -1
  61. metadata +34 -2
data/README.md CHANGED
@@ -5,6 +5,12 @@
5
5
  [![CI](https://github.com/unurgunite/docscribe/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/unurgunite/docscribe/actions/workflows/ci.yml)
6
6
  [![License](https://img.shields.io/github/license/unurgunite/docscribe.svg)](https://github.com/unurgunite/docscribe/blob/master/LICENSE.txt)
7
7
  [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7-blue.svg)](#installation)
8
+ [![VS Code](https://img.shields.io/badge/VS%20Code-plugin-blue?logo=visualstudiocode)](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
9
+ [![RubyMine](https://img.shields.io/badge/RubyMine-plugin-green?logo=jetbrains)](https://plugins.jetbrains.com/plugin/32349-docscribe)
10
+
11
+ <p>
12
+ <img src="assets/icons/icon_256x256.png" alt="Docscribe logo" width="96">
13
+ </p>
8
14
 
9
15
  ![Docscribe before/after demo](docs/image.png)
10
16
 
@@ -29,6 +35,10 @@ returns), and respects Ruby visibility semantics — without using YARD to parse
29
35
  - `attr_reader` / `attr_writer` / `attr_accessor`;
30
36
  - `Struct.new` declarations in both constant-assigned and class-based styles.
31
37
 
38
+ > [!NOTE]
39
+ > Docscribe is under **active development**. If you run into any edge cases or have ideas for improvement, feel free to
40
+ > [open an issue](https://github.com/unurgunite/docscribe/issues/new) or submit a pull request.
41
+
32
42
  Common workflows:
33
43
 
34
44
  - Inspect what safe doc updates would be applied: `docscribe lib`
@@ -38,19 +48,48 @@ Common workflows:
38
48
  - Use RBS signatures when available: `docscribe -a --rbs --sig-dir sig lib`
39
49
  - Use Sorbet signatures when available: `docscribe -a --sorbet --rbi-dir sorbet/rbi lib`
40
50
 
51
+ ## Quick start
52
+
53
+ ```shell
54
+ # Check what safe doc updates would be applied
55
+ docscribe lib
56
+
57
+ # Apply safe updates (insert missing docs, merge existing)
58
+ docscribe -a lib
59
+
60
+ # Rebuild all doc blocks aggressively
61
+ docscribe -A lib
62
+ ```
63
+
64
+ > [!TIP]
65
+ > See [CLI](#cli) for all options and [Update strategies](#update-strategies) for the
66
+ > difference between safe and aggressive modes.
67
+ >
68
+ > Want IDE integration? Check out
69
+ > the [VS Code](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
70
+ > and [RubyMine](https://plugins.jetbrains.com/plugin/32349-docscribe) plugins.
71
+
41
72
  ## Contents
42
73
 
43
74
  * [Docscribe](#docscribe)
75
+ * [Quick start](#quick-start)
44
76
  * [Contents](#contents)
45
77
  * [Installation](#installation)
46
- * [Quick start](#quick-start)
78
+ * [Architecture](#architecture)
79
+ * [Data flow](#data-flow)
47
80
  * [CLI](#cli)
81
+ * [Exit codes](#exit-codes)
48
82
  * [Options](#options)
49
83
  * [Examples](#examples)
84
+ * [`docscribe sigs` — check RBS signature coverage](#docscribe-sigs--check-rbs-signature-coverage)
85
+ * [`docscribe rbs` — generate RBS from YARD](#docscribe-rbs--generate-rbs-from-yard)
86
+ * [`docscribe update_types` — two-pass type-aware documentation update](#docscribe-update_types--two-pass-type-aware-documentation-update)
87
+ * [`docscribe check_for_comments` — find placeholder documentation](#docscribe-check_for_comments--find-placeholder-documentation)
50
88
  * [Update strategies](#update-strategies)
51
89
  * [Safe strategy](#safe-strategy)
52
90
  * [Aggressive strategy](#aggressive-strategy)
53
91
  * [Output markers](#output-markers)
92
+ * [Tips & tricks](#tips--tricks)
54
93
  * [Parser backend (Parser gem vs Prism)](#parser-backend-parser-gem-vs-prism)
55
94
  * [External type integrations (optional)](#external-type-integrations-optional)
56
95
  * [RBS](#rbs)
@@ -75,6 +114,7 @@ Common workflows:
75
114
  * [Idempotency](#idempotency)
76
115
  * [Plugin examples](#plugin-examples)
77
116
  * [Configuration](#configuration)
117
+ * [Anonymous block parameters](#anonymous-block-parameters)
78
118
  * [Filtering](#filtering)
79
119
  * [`attr_*` example](#attr_-example)
80
120
  * [`Struct.new` examples](#structnew-examples)
@@ -84,12 +124,17 @@ Common workflows:
84
124
  * [Param tag style](#param-tag-style)
85
125
  * [Create a starter config](#create-a-starter-config)
86
126
  * [Generate a plugin skeleton](#generate-a-plugin-skeleton)
127
+ * [Full configuration reference](#full-configuration-reference)
87
128
  * [CI integration](#ci-integration)
88
129
  * [Comparison to YARD's parser](#comparison-to-yards-parser)
89
130
  * [Limitations](#limitations)
90
131
  * [Roadmap](#roadmap)
132
+ * [Editor Integration](#editor-integration)
133
+ * [VS Code](#vs-code)
134
+ * [RubyMine](#rubymine)
91
135
  * [Contributing](#contributing)
92
136
  * [Discussion & Community](#discussion--community)
137
+ * [Logo Attribution](#logo-attribution)
93
138
  * [License](#license)
94
139
 
95
140
  ## Installation
@@ -114,110 +159,193 @@ gem install docscribe
114
159
 
115
160
  Requires Ruby 2.7+.
116
161
 
117
- ## Quick start
118
-
119
- Given code:
120
-
121
- ```ruby
122
- class Demo
123
- def foo(a, options: {})
124
- 42
125
- end
126
-
127
- def bar(verbose: true)
128
- 123
129
- end
130
-
131
- private
132
-
133
- def self.bump
134
- :ok
135
- end
136
-
137
- class << self
138
- private
139
-
140
- def internal; end
141
- end
142
- end
143
- ```
144
-
145
- Run:
162
+ ## Architecture
163
+
164
+ Docscribe is organized into several subsystems. The CLI layer receives user input and orchestrates configuration
165
+ loading, then delegates to the core engine which parses source code, collects methods (using an AST walker), builds YARD
166
+ doc lines — combining heuristic type inference, external RBS/Sorbet signatures, and plugin output — and finally rewrites
167
+ the source via a strategy (safe merge or aggressive replace).
168
+
169
+ ```mermaid
170
+ flowchart TB
171
+ subgraph CLI["CLI Layer"]
172
+ Exe["exe/docscribe\nEntry point"]
173
+ Run["CLI::Run\nMain execution\n· expand paths\n· iterate files\n· report results"]
174
+ Options["CLI::Options\nARGV parsing\n(mode, strategy,\nfilters, flags)"]
175
+ InitCmd["CLI::Init\ndocscribe init\nGenerate config"]
176
+ GenCmd["CLI::Generate\ndocscribe generate\nScaffold plugins"]
177
+ SigsCmd["CLI::Sigs\ndocscribe sigs\nCheck RBS coverage"]
178
+ RbsGenCmd["CLI::RbsGen\ndocscribe rbs\nGenerate RBS from YARD"]
179
+ SarifFormatter["CLI::Formatters::Sarif\nSARIF 2.1 JSON\nCode Scanning"]
180
+ ConfigBuilder["CLI::ConfigBuilder\nApply CLI overrides\nto config"]
181
+ end
146
182
 
147
- ```shell
148
- echo "...code above..." | docscribe --stdin
149
- ```
183
+ subgraph Config["Configuration"]
184
+ ConfigClass["Config\nCentral config object\n· raw hash\n· query methods"]
185
+ Defaults["config/defaults.rb\nDEFAULT hash"]
186
+ Loader["config/loader.rb\nYAML loading\n+ deep merge"]
187
+ Emit["config/emit.rb\nEmission toggles\n(header, tags, etc.)"]
188
+ Filtering["config/filtering.rb\nFile/method\ninclude/exclude"]
189
+ RBSConfig["config/rbs.rb\nRBS provider\nfactory"]
190
+ SorbetConfig["config/sorbet.rb\nSorbet provider\nchain factory"]
191
+ PluginConfig["config/plugin.rb\nPlugin loading\nfrom YAML"]
192
+ end
150
193
 
151
- Output:
194
+ subgraph Parsing["Parsing"]
195
+ ParsingModule["Parsing\nBackend selection\n(:parser / :prism)"]
196
+ ParserGem["Parser gem\n(whitequark/parser)"]
197
+ Prism["Prism translator\n(Ruby 3.4+)"]
198
+ end
152
199
 
153
- ```ruby
154
- class Demo
155
- # +Demo#foo+ -> Integer
156
- #
157
- # Method documentation.
158
- #
159
- # @param [Object] a Param documentation.
160
- # @param [Hash] options Param documentation.
161
- # @return [Integer]
162
- def foo(a, options: {})
163
- 42
164
- end
200
+ subgraph Core["Core Engine"]
201
+ InlineRewriter["InlineRewriter\n· parse -> collect\n· deduplicate -> dispatch\n· rewrite"]
202
+ Collector["Collector\n< Parser::AST::Processor\nAST walker\n· find methods/attrs\n· track visibility\n· track containers"]
203
+ DocBuilder["DocBuilder\nGenerate YARD doc lines\n· combine inference\n· external signatures\n· plugin tags"]
204
+ DocBlock["DocBlock\nSafe strategy:\nparse -> merge -> sort\nexisting doc blocks"]
205
+ SourceHelpers["SourceHelpers\nPosition/range\nutilities"]
206
+ end
165
207
 
166
- # +Demo#bar+ -> Integer
167
- #
168
- # Method documentation.
169
- #
170
- # @param [Boolean] verbose Param documentation.
171
- # @return [Integer]
172
- def bar(verbose: true)
173
- 123
174
- end
208
+ subgraph Infer["Inference Engine"]
209
+ InferModule["Infer\nEntry point"]
210
+ Params["Infer::Params\nParameter type\nfrom name + default"]
211
+ Returns["Infer::Returns\nReturn type\nfrom method body"]
212
+ Raises["Infer::Raises\n@raise tags\nfrom raise/rescue"]
213
+ Literals["Infer::Literals\nAST literal ->\ntype string"]
214
+ Names["Infer::Names\n:const node ->\nFQN string"]
215
+ ASTWalk["Infer::ASTWalk\nRecursive DFS\nAST traversal"]
216
+ end
175
217
 
176
- private
218
+ subgraph Plugins["Plugin System"]
219
+ PluginModule["Plugin\nTag/Collector\ndispatch"]
220
+ Registry["Plugin::Registry\nGlobal registry\n· register -> route\n· tag_entries\n· collector_entries"]
221
+ TagPlugin["Base::TagPlugin\nOverride #call(context)\n-> Array<Tag>"]
222
+ CollectorPlugin["Base::CollectorPlugin\nOverride #collect(ast, buffer)\n-> Array<Hash>"]
223
+ TagValue["Plugin::Tag\nStruct (name, text, types)"]
224
+ Context["Plugin::Context\nMethod snapshot struct"]
225
+ end
177
226
 
178
- # +Demo.bump+ -> Symbol
179
- #
180
- # Method documentation.
181
- #
182
- # @return [Symbol]
183
- def self.bump
184
- :ok
185
- end
227
+ subgraph Types["External Type System"]
228
+ ProviderChain["ProviderChain\nComposite:\nquery in order\nfirst match wins"]
229
+ RBSProvider["RBS::Provider\n.rbs files\n-> RBS lib"]
230
+ RBSFormatter["RBS::TypeFormatter\nRBS type ->\nYARD type string"]
231
+ RBSCollection["RBS::CollectionLoader\nrbs_collection\n.lock.yaml"]
232
+ SorbetBase["Sorbet::BaseProvider\nRBS::Prototype::RBI\nbridge"]
233
+ SorbetSource["Sorbet::SourceProvider\nInline sig{}\ndeclarations"]
234
+ SorbetRBI["Sorbet::RBIProvider\n.rbi files\ndirectories"]
235
+ end
186
236
 
187
- class << self
188
- private
237
+ subgraph YardTypes["YARD Type Parser"]
238
+ YParser["Yard::Parser\nParse YARD type\nstrings -> AST"]
239
+ YFormatter["Yard::Formatter\nYARD AST ->\nRBS string"]
240
+ YTypes["Yard::Types\n9 AST node types\n(Named, Generic, etc.)"]
241
+ end
189
242
 
190
- # +Demo.internal+ -> Object
191
- #
192
- # Method documentation.
193
- #
194
- # @private
195
- # @return [Object]
196
- def internal; end
197
- end
198
- end
243
+ Exe --> Run
244
+ Run --> Options
245
+ Run --> InitCmd
246
+ Run --> GenCmd
247
+ Run --> SigsCmd
248
+ Run --> RbsGenCmd
249
+ Run --> SarifFormatter
250
+ Run --> ConfigBuilder
251
+ ConfigBuilder --> ConfigClass
252
+ ConfigClass --> Defaults
253
+ ConfigClass --> Loader
254
+ ConfigClass --> Emit
255
+ ConfigClass --> Filtering
256
+ ConfigClass --> RBSConfig
257
+ ConfigClass --> SorbetConfig
258
+ ConfigClass --> PluginConfig
259
+ Run --> InlineRewriter
260
+ InlineRewriter --> ParsingModule
261
+ ParsingModule --> ParserGem
262
+ ParsingModule --> Prism
263
+ InlineRewriter --> Collector
264
+ Collector --> PluginModule
265
+ PluginModule --> Registry
266
+ Registry --> CollectorPlugin
267
+ InlineRewriter --> DocBuilder
268
+ DocBuilder --> InferModule
269
+ InferModule --> Params
270
+ InferModule --> Returns
271
+ InferModule --> Raises
272
+ Params --> Literals
273
+ Returns --> Literals
274
+ Raises --> ASTWalk
275
+ Raises --> Names
276
+ DocBuilder --> ProviderChain
277
+ ProviderChain --> SorbetSource
278
+ ProviderChain --> SorbetRBI
279
+ ProviderChain --> RBSProvider
280
+ SorbetSource --> SorbetBase
281
+ SorbetRBI --> SorbetBase
282
+ RBSProvider --> RBSFormatter
283
+ RBSProvider --> RBSCollection
284
+ DocBuilder --> PluginModule
285
+ PluginModule --> Registry
286
+ Registry --> TagPlugin
287
+ TagPlugin --> TagValue
288
+ TagPlugin --> Context
289
+ InlineRewriter --> DocBlock
290
+ InlineRewriter --> SourceHelpers
291
+ RbsGenCmd --> ParsingModule
292
+ RbsGenCmd --> YParser
293
+ YParser --> YTypes
294
+ YParser --> YFormatter
295
+ ```
296
+
297
+ ### Data flow
298
+
299
+ ```mermaid
300
+ flowchart LR
301
+ Input["Source files\n(.rb)"] --> Parse["Parsing.parse_buffer\nParser gem / Prism"]
302
+ Parse --> AST["AST + Comments"]
303
+ AST --> Collect["Collector.process\n· Find methods\n· Track visibility\n· Find attr_*"]
304
+ AST --> CollectPlugins["CollectorPlugin#collect\n· Custom AST walks\n· Non-standard constructs"]
305
+ Collect --> Insertions["Insertion list\n(sorted by position)"]
306
+ CollectPlugins --> Insertions
307
+ Insertions --> Dedup["Deduplicate\n(override by position)"]
308
+ Dedup --> Build["DocBuilder.build_doc_lines\nper insertion"]
309
+ Build --> Infer["Infer params / returns / raises\n(heuristic fallback)"]
310
+ Build --> SigQuery["ProviderChain\nquery external types"]
311
+ Build --> TagPlugins["TagPlugin#call\n(add extra @tags)"]
312
+ Infer --> ResultDoc["Generated YARD doc block"]
313
+ SigQuery --> ResultDoc
314
+ TagPlugins --> ResultDoc
315
+ ResultDoc --> Strategy{"Strategy?"}
316
+ Strategy -->|Safe| Merge["DocBlock.merge\npreserve + append + sort"]
317
+ Strategy -->|Aggressive| Replace["Replace entirely"]
318
+ Merge --> Rewritten["Rewriter#process\n-> rewritten source"]
319
+ Replace --> Rewritten
320
+ Rewritten --> Output["Modified .rb file\n/ STDOUT"]
199
321
  ```
200
322
 
201
- > [!NOTE]
202
- > - The tool inserts doc headers before method headers and preserves everything else.
203
- > - For methods with a leading Sorbet `sig`, docs are inserted above the first `sig`.
204
- > - Class methods show with a dot (`+Demo.bump+`, `+Demo.internal+`).
205
- > - Methods inside `class << self` under `private` are marked `@private`.
206
-
207
323
  ## CLI
208
324
 
209
325
  ```shell
210
326
  docscribe [options] [files...]
327
+ docscribe init [options]
328
+ docscribe generate [type] [name] [options]
329
+ docscribe sigs [options] [files...]
330
+ docscribe rbs [options] [files...]
331
+ docscribe update_types [directory]
332
+ docscribe check_for_comments [paths...]
211
333
  ```
212
334
 
213
335
  Docscribe has three main ways to run:
214
336
 
215
- - **Inspect mode** (default): checks what safe doc updates would be applied and exits non-zero if files need changes.
337
+ - **Inspect mode** (default): checks what safe doc updates would be applied and exits 1 if files need changes.
216
338
  - **Safe autocorrect** (`-a`, `--autocorrect`): writes safe, non-destructive updates in place.
217
339
  - **Aggressive autocorrect** (`-A`, `--autocorrect-all`): rewrites existing doc blocks more aggressively.
218
340
  - **STDIN mode** (`--stdin`): reads Ruby source from STDIN and prints rewritten source to STDOUT.
219
341
 
220
- If you pass no files and dont use `--stdin`, Docscribe processes the current directory recursively.
342
+ If you pass no files and don't use `--stdin`, Docscribe processes the current directory recursively.
343
+
344
+ ### Exit codes
345
+
346
+ - **0** — all files are up to date (no changes needed)
347
+ - **1** — some files need documentation updates
348
+ - **2** — execution error (parse error, missing files, etc.)
221
349
 
222
350
  ### Options
223
351
 
@@ -236,10 +364,28 @@ If you pass no files and don’t use `--stdin`, Docscribe processes the current
236
364
  Read source from STDIN and print rewritten output.
237
365
 
238
366
  - `--verbose`
239
- Print per-file actions.
367
+ Print per-file actions. Also enables `--progress`.
368
+
369
+ - `--progress`
370
+ Show progress (`[N/total] path/to/file.rb`) on stderr as each file is processed.
371
+
372
+ - `--quiet` (`-q`)
373
+ Only show status, no details (suppresses change reasons).
374
+ Overrides the default detailed output.
240
375
 
241
376
  - `--explain`
242
- Show detailed reasons for each file that would change.
377
+ Show detailed reasons for each file (default; no-op for compatibility).
378
+
379
+ - `-k`, `--keep-descriptions`
380
+ Preserve existing documentation text when rebuilding doc blocks in aggressive mode.
381
+
382
+ - `-B`, `--no-boilerplate`
383
+ Suppress boilerplate text (`Method documentation.`, `Param documentation.`) in output.
384
+ Equivalent to `emit.include_default_message: false` and `emit.include_param_documentation: false` in config.
385
+
386
+ - `--format FORMAT`
387
+ Output format: `text` (default, human-readable), `json` (machine-readable, RuboCop-compatible), or `sarif` (SARIF 2.1
388
+ JSON, compatible with GitHub Code Scanning).
243
389
 
244
390
  - `--rbs`
245
391
  Use RBS signatures for `@param`/`@return` when available (falls back to inference).
@@ -247,6 +393,12 @@ If you pass no files and don’t use `--stdin`, Docscribe processes the current
247
393
  - `--sig-dir DIR`
248
394
  Add an RBS signature directory (repeatable). Implies `--rbs`.
249
395
 
396
+ - `--sorbet`
397
+ Use Sorbet signatures for `@param`/`@return` when available (falls back to inference).
398
+
399
+ - `--rbi-dir DIR`
400
+ Add an Sorbet RBI directory (repeatable). Implies `--sorbet`.
401
+
250
402
  - `--include PATTERN`
251
403
  Include PATTERN (method id or file path; glob or `/regex/`).
252
404
 
@@ -307,9 +459,146 @@ If you pass no files and don’t use `--stdin`, Docscribe processes the current
307
459
 
308
460
  - Show detailed reasons for files that would change:
309
461
  ```shell
310
- docscribe --verbose --explain lib
462
+ docscribe --verbose lib
311
463
  ```
312
464
 
465
+ ### `docscribe sigs` — check RBS signature coverage
466
+
467
+ > [!WARNING]
468
+ > `docscribe sigs` requires **Ruby 3.0+** and the `rbs` gem. On Ruby 2.7 it will print an error and exit with code 2.
469
+
470
+ `docscribe sigs` parses Ruby files, extracts method definitions, and checks each
471
+ method against the configured RBS signature directories. Reports methods that lack RBS type signatures.
472
+
473
+ ```shell
474
+ # Check lib/ against sig/ signatures
475
+ docscribe sigs lib
476
+
477
+ # Use RBS collection
478
+ docscribe sigs --rbs-collection lib
479
+
480
+ # Multiple signature directories
481
+ docscribe sigs -s sig -s vendor/sigs lib
482
+
483
+ # Verbose output (also prints methods that have signatures)
484
+ docscribe sigs --verbose lib
485
+ ```
486
+
487
+ **Flags:**
488
+
489
+ - `-s`, `--sig-dir DIR` — add an RBS signature directory (repeatable). Default: `sig`.
490
+ - `--rbs-collection` — use RBS collection (reads `rbs_collection.lock.yaml`).
491
+ - `--verbose` — also print methods that have signatures.
492
+ - `-h`, `--help` — show help.
493
+
494
+ **Exit codes:**
495
+
496
+ - `0` — all methods have RBS signatures.
497
+ - `1` — some methods lack RBS signatures.
498
+ - `2` — error occurred.
499
+
500
+ ### `docscribe rbs` — generate RBS from YARD
501
+
502
+ > [!WARNING]
503
+ > `docscribe rbs` requires **Ruby 3.0+** and the `rbs` gem. On Ruby 2.7 it will print an error and exit with code 2.
504
+
505
+ `docscribe rbs` parses YARD comments (`@param`, `@return`, `@option`) from Ruby files
506
+ and generates corresponding `.rbs` signature files.
507
+
508
+ ```shell
509
+ # Generate RBS files in sig/
510
+ docscribe rbs lib
511
+
512
+ # Print to stdout (no files written)
513
+ docscribe rbs -n lib
514
+
515
+ # Specify output directory
516
+ docscribe rbs -o sig/gen lib
517
+
518
+ # Overwrite existing files
519
+ docscribe rbs -f lib
520
+ ```
521
+
522
+ **Flags:**
523
+
524
+ - `-o`, `--output DIR` — output directory (default: `sig`).
525
+ - `-n`, `--dry-run` — print generated RBS to stdout, do not write files.
526
+ - `-f`, `--force` — overwrite existing files.
527
+ - `-h`, `--help` — show help.
528
+
529
+ **Exit codes:**
530
+
531
+ - `0` — success.
532
+ - `1` — errors occurred during processing.
533
+ - `2` — execution error (no files found).
534
+
535
+ ```ruby
536
+ # Example: lib/user.rb
537
+ class User
538
+ # @param [String] name
539
+ # @param [Integer] age
540
+ # @return [User]
541
+ def initialize(name, age)
542
+ @name = name
543
+ @age = age
544
+ end
545
+ end
546
+ ```
547
+
548
+ ```shell
549
+ docscribe rbs lib
550
+ # Generated sig/user.rbs
551
+ ```
552
+
553
+ ```ruby.rbs
554
+ # sig/user.rbs
555
+ class User
556
+ def initialize: (String name, Integer age) -> User
557
+ end
558
+ ```
559
+
560
+ ### `docscribe update_types` — two-pass type-aware documentation update
561
+
562
+ > [!NOTE]
563
+ > `docscribe update_types` is a convenience alias for the two-pass workflow above. It requires Ruby 3.0+ and the `rbs`
564
+ > gem (because of `--rbs-collection`). The RBS collection must be set up first with
565
+ > `bundle exec rbs collection install`. Type accuracy depends on your RBS signatures — if signatures are incomplete or
566
+ > missing, types will fall back to AST inference.
567
+
568
+ `docscribe update_types` runs two passes to bring both docs and RBS signatures up to date:
569
+
570
+ 1. **Pass 1** — `docscribe -AkB --rbs-collection <dir>`: aggressively rebuilds doc blocks, preserves existing
571
+ descriptions, suppresses boilerplate, uses RBS collection types.
572
+ 2. **Pass 2** — `docscribe -aB --rbs-collection <dir>`: safe merge cleanup with no boilerplate.
573
+
574
+ ```shell
575
+ # Update docs in lib/ using RBS collection
576
+ docscribe update_types lib
577
+
578
+ # Defaults to current directory
579
+ docscribe update_types
580
+ ```
581
+
582
+ ### `docscribe check_for_comments` — find placeholder documentation
583
+
584
+ `docscribe check_for_comments` scans Ruby source files for YARD comments that still contain default placeholder
585
+ text. Reads the configured placeholder messages from `docscribe.yml` (or built-in defaults). Useful in CI to catch
586
+ files where auto-generated text was never replaced with real documentation.
587
+
588
+ ```shell
589
+ # Scan entire project
590
+ docscribe check_for_comments
591
+
592
+ # Scan specific directories
593
+ docscribe check_for_comments lib app
594
+ ```
595
+
596
+ Exit code `0` if no placeholders found, `1` if any are detected.
597
+
598
+ **Flags:**
599
+
600
+ - `-h`, `--help` — show help.
601
+
313
602
  ## Update strategies
314
603
 
315
604
  Docscribe supports two update strategies: **safe** and **aggressive**.
@@ -344,6 +633,9 @@ Aggressive strategy:
344
633
 
345
634
  Use it when you want to rebaseline or regenerate docs wholesale.
346
635
 
636
+ > [!CAUTION]
637
+ > Aggressive mode rewrites existing doc blocks entirely. Review changes with `git diff` before committing.
638
+
347
639
  ### Output markers
348
640
 
349
641
  In inspect mode, Docscribe prints one character per file:
@@ -351,14 +643,20 @@ In inspect mode, Docscribe prints one character per file:
351
643
  - `.` = file is up to date
352
644
  - `F` = file would change
353
645
  - `E` = file had an error
646
+ - `M` = type mismatch detected (external RBS/Sorbet signature differs from inferred type)
354
647
 
355
648
  In write modes:
356
649
 
357
650
  - `.` = file already OK
358
651
  - `C` = file was updated
359
652
  - `E` = file had an error
653
+ - `M` = file updated but type mismatch remains
360
654
 
361
- With `--verbose`, Docscribe prints per-file statuses instead.
655
+ > [!IMPORTANT]
656
+ > The `M` marker only appears when `--rbs` or `--sorbet` is enabled, since type mismatches are detected by
657
+ > comparing external signatures against inferred types.
658
+
659
+ With `--verbose`, Docscribe prints per-file statuses instead; type mismatches show as `MT` with the specific difference.
362
660
 
363
661
  With `--explain`, Docscribe also prints detailed reasons, such as:
364
662
 
@@ -367,6 +665,27 @@ With `--explain`, Docscribe also prints detailed reasons, such as:
367
665
  - missing module_function note
368
666
  - unsorted tags
369
667
 
668
+ Use `--quiet` to suppress these details and show only file names and the summary line.
669
+
670
+ ## Tips & tricks
671
+
672
+ Useful flag combinations for common workflows:
673
+
674
+ - `docscribe -aB lib` — safe autocorrect without boilerplate. Uses safe mode but suppresses default text, leaving only
675
+ type tags (`@param`, `@return`). Clean minimal docs.
676
+ - `docscribe -AkB lib` — aggressive autocorrect with preserved descriptions and no boilerplate. Rebuilds doc blocks,
677
+ keeps your hand-written descriptions, suppresses default text. Best for rebaselining.
678
+ - `docscribe -a --rbs-collection lib` — safe autocorrect using RBS gem collection signatures. Recommended for Rails
679
+ projects.
680
+ - `docscribe -a --sorbet --rbi-dir sorbet/rbi lib` — safe autocorrect using Sorbet RBI signatures.
681
+ - `docscribe update_types lib` — two-pass type-aware update: aggressively rebuilds docs with kept descriptions and RBS
682
+ collection, then safe-merges to clean up.
683
+ See [docscribe update_types](#docscribe-update_types--two-pass-type-aware-documentation-update).
684
+
685
+ > [!NOTE]
686
+ > `docscribe update_types` is a convenient shortcut, but be aware it uses `--rbs-collection` under the hood.
687
+ > If your RBS signatures are incomplete, types may fall back to AST inference.
688
+
370
689
  ## Parser backend (Parser gem vs Prism)
371
690
 
372
691
  Docscribe internally works with `parser`-gem-compatible AST nodes and `Parser::Source::*` objects (so it can use
@@ -375,6 +694,11 @@ Docscribe internally works with `parser`-gem-compatible AST nodes and `Parser::S
375
694
  - On Ruby **<= 3.3**, Docscribe parses using the `parser` gem.
376
695
  - On Ruby **>= 3.4**, Docscribe parses using **Prism** and translates the tree into the `parser` gem's AST.
377
696
 
697
+ > [!NOTE]
698
+ > Prism support is automatic on Ruby 3.4+. On earlier Rubies, the `parser` gem is always used.
699
+ > You can override with `DOCSCRIBE_PARSER_BACKEND=prism` on Ruby 3.1+ if you have the `prism` gem installed,
700
+ > but this is not recommended for production use.
701
+
378
702
  You can force a backend with an environment variable:
379
703
 
380
704
  ```shell
@@ -388,16 +712,33 @@ Docscribe can improve generated `@param` and `@return` types by reading external
388
712
  AST inference.
389
713
 
390
714
  > [!IMPORTANT]
391
- > When external type information is available, Docscribe resolves signatures in this order:
392
- > - inline Sorbet `sig` declarations in the current Ruby source;
393
- > - Sorbet RBI files;
394
- > - RBS files;
395
- > - AST inference fallback.
715
+ > Docscribe resolves types in a two-level chain. For **documentation tags** (`@param`, `@return`), the priority is:
716
+ >
717
+ > | Priority | Source | When active |
718
+ > |----------|-------------------------------------------------------------|----------------------------------------------------------|
719
+ > | **1** | Inline Sorbet `sig { ... }` in current file | `--sorbet` or `sorbet.enabled: true` |
720
+ > | **2** | Sorbet RBI files (`.rbi`) | `--sorbet --rbi-dir` or `sorbet.rbi_dirs` |
721
+ > | **3** | RBS files (sig_dirs + collection, loaded into one env) | `--rbs --sig-dir` / `--rbs-collection` or `rbs.*` config |
722
+ > | **3a** | └─ Fallback: sig_dirs only (collection dropped on conflict) | Automatic if priority 3 env load fails |
723
+ > | **4** | AST inference (fallback) | Always active |
396
724
  >
397
- > If an external signature cannot be loaded or parsed, Docscribe falls back to normal inference instead of failing.
725
+ > For **intra-method body inference** (e.g. resolving `Integer#positive?` -> `Boolean`), a separate core RBS provider
726
+ > loads only stdlib/built-in signatures and is active automatically when the `rbs` gem is available — even without
727
+ > `--rbs`.
728
+ >
729
+ > If an external signature cannot be loaded or parsed, Docscribe falls back to the next source in the chain instead of
730
+ > failing.
398
731
 
399
732
  ### RBS
400
733
 
734
+ > [!IMPORTANT]
735
+ > All RBS features require the `rbs` gem. Add `gem "rbs"` to your Gemfile and run `bundle install`,
736
+ > or install it globally with `gem install rbs`.
737
+ >
738
+ > On Ruby 2.7 the `rbs` gem cannot load at all — `--rbs`, `--sig-dir`, `--rbs-collection`,
739
+ > `docscribe sigs`, and `docscribe rbs` will print a warning and fall back to AST-only inference.
740
+ > On Ruby 3.0+, if the gem is missing, Docscribe silently falls back to inference when you pass RBS flags.
741
+
401
742
  Docscribe can read method signatures from `.rbs` files and use them to generate more accurate parameter and return
402
743
  types.
403
744
 
@@ -417,12 +758,23 @@ Config:
417
758
 
418
759
  ```yaml
419
760
  rbs:
420
- enabled: true
761
+ enabled: false
421
762
  sig_dirs:
422
763
  - sig
764
+ collection: false
423
765
  collapse_generics: false
766
+ collapse_object_generics: false
767
+ warn_missing_collection: true
424
768
  ```
425
769
 
770
+ - `collection` — enable auto-discovery of RBS collection from `rbs_collection.lock.yaml`. Pass `--rbs-collection` on the
771
+ CLI.
772
+ - `collapse_generics` — strip all generic type arguments (e.g. `Array<String>` -> `Array`).
773
+ - `collapse_object_generics` — only strip generic arguments when all are `Object` (e.g. `Hash<Object, Object>` ->
774
+ `Hash`, but `Hash<Symbol, Object>` stays).
775
+ - `warn_missing_collection` — warn on stderr when `rbs_collection.lock.yaml` is found but collection is not enabled. Set
776
+ to `false` to suppress.
777
+
426
778
  Example:
427
779
 
428
780
  ```ruby
@@ -645,24 +997,29 @@ end
645
997
 
646
998
  ### Generic type formatting
647
999
 
648
- Both RBS and Sorbet integrations support `collapse_generics`.
1000
+ Both RBS and Sorbet integrations support generic type collapsing.
649
1001
 
650
- When disabled:
1002
+ **`collapse_generics`** — strips all generic type arguments.
1003
+ **`collapse_object_generics`** — only strips arguments when all are `Object` (e.g. `Hash<Object, Object>` -> `Hash`, but
1004
+ `Hash<Symbol, Object>` stays).
1005
+
1006
+ When both disabled:
651
1007
 
652
1008
  ```yaml
653
1009
  rbs:
654
1010
  collapse_generics: false
1011
+ collapse_object_generics: false
655
1012
 
656
1013
  sorbet:
657
1014
  collapse_generics: false
658
1015
  ```
659
1016
 
660
- Docscribe preserves generic container details where possible, for example:
1017
+ Docscribe preserves generic container details, for example:
661
1018
 
662
1019
  - `Array<String>`
663
1020
  - `Hash<Symbol, Integer>`
664
1021
 
665
- When enabled:
1022
+ When `collapse_generics` is enabled:
666
1023
 
667
1024
  ```yaml
668
1025
  rbs:
@@ -672,7 +1029,7 @@ sorbet:
672
1029
  collapse_generics: true
673
1030
  ```
674
1031
 
675
- Docscribe simplifies container types to their outer names, for example:
1032
+ Docscribe simplifies all container types to their outer names, for example:
676
1033
 
677
1034
  - `Array`
678
1035
  - `Hash`
@@ -707,6 +1064,11 @@ Return values:
707
1064
  - For simple bodies, Docscribe looks at the last expression or explicit `return`.
708
1065
  - Unions with `nil` become optional types (e.g. `String` or `nil` -> `String?`).
709
1066
  - For control flow (`if`/`case`), it unifies branches conservatively.
1067
+
1068
+ > [!TIP]
1069
+ > Docscribe resolves return types for core Ruby methods (`Integer#positive?`, `String#upcase`, etc.)
1070
+ > **even without `--rbs`** — the core RBS provider is always active when the `rbs` gem is available.
1071
+
710
1072
  - **RBS core type inference**: when `--rbs` is enabled, Docscribe resolves return types for method calls on core types
711
1073
  from their RBS definitions:
712
1074
  - `arg.positive?` (`arg = 1`) -> `Boolean` (from `Integer#positive?`)
@@ -1019,6 +1381,19 @@ Sample plugins available at [examples](examples/plugins):
1019
1381
 
1020
1382
  Docscribe can be configured via a YAML file (`docscribe.yml` by default, or pass `--config PATH`).
1021
1383
 
1384
+ ### Anonymous block parameters
1385
+
1386
+ Ruby 3.2+ allows anonymous block arguments (`def foo(&)`). By default, Docscribe generates `@param [Proc] block` for
1387
+ these — but since the parameter has no name, the tag is misleading.
1388
+
1389
+ To suppress the `@param` tag for anonymous block arguments:
1390
+
1391
+ ```yaml
1392
+ skip_anonymous_block_params: true
1393
+ ```
1394
+
1395
+ When `false` (default), anonymous block params generate `@param [Proc] block`.
1396
+
1022
1397
  ### Filtering
1023
1398
 
1024
1399
  Docscribe can filter both *files* and *methods*.
@@ -1242,6 +1617,7 @@ docscribe generate tag SincePlugin --stdout
1242
1617
  ```
1243
1618
 
1244
1619
  The generated file contains:
1620
+
1245
1621
  - the correct base class (`Base::TagPlugin` or `Base::CollectorPlugin`)
1246
1622
  - inline comments describing every available `Context` attribute (TagPlugin)
1247
1623
  or the expected return shape (CollectorPlugin)
@@ -1252,6 +1628,58 @@ The generated file contains:
1252
1628
  > The class name must be a valid Ruby constant (`MyPlugin`, `My::Plugin`).
1253
1629
  > The output filename is the snake_case equivalent (`my_plugin.rb`, `my/plugin.rb`).
1254
1630
 
1631
+ ### Full configuration reference
1632
+
1633
+ <details>
1634
+ <summary>Click to expand — 43 config keys</summary>
1635
+
1636
+ | Key | Type | Default | Description |
1637
+ |-------------------------------------------|------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
1638
+ | `keep_descriptions` | `bool` | `false` | Preserve existing doc text in aggressive mode |
1639
+ | `skip_anonymous_block_params` | `bool` | `false` | Skip `@param [Proc] block` for anonymous `&` params |
1640
+ | `emit.header` | `bool` | `false` | Generate method header line (`+#foo+ -> ...`) |
1641
+ | `emit.include_default_message` | `bool` | `true` | Insert default message (`Method documentation.`) |
1642
+ | `emit.include_param_documentation` | `bool` | `true` | Insert param description text (`Param documentation.`) |
1643
+ | `emit.param_tags` | `bool` | `true` | Generate `@param` tags |
1644
+ | `emit.return_tag` | `bool` | `true` | Generate `@return` tag |
1645
+ | `emit.visibility_tags` | `bool` | `true` | Generate `@private`/`@protected` tags |
1646
+ | `emit.raise_tags` | `bool` | `true` | Generate `@raise` tags (for `raise` in method body) |
1647
+ | `emit.rescue_conditional_returns` | `bool` | `true` | Consider `rescue` branches in return type inference |
1648
+ | `emit.attributes` | `bool` | `false` | Generate `@!attribute` for `attr_*` and `Struct.new` |
1649
+ | `doc.default_message` | `string` | `"Method documentation."` | Default text for method description |
1650
+ | `doc.param_documentation` | `string` | `"Param documentation."` | Default text for param description |
1651
+ | `doc.sort_tags` | `bool` | `true` | Sort tags according to `tag_order` |
1652
+ | `doc.tag_order` | `string[]` | `["todo","note","api","private","protected","param","option","yieldparam","raise","return"]` | Tag sort order |
1653
+ | `doc.param_tag_style` | `string` | `"type_name"` | `@param` style: `type_name` (`[Type] name`) or `name_type` (`name [Type]`) |
1654
+ | `inference.fallback_type` | `string` | `"Object"` | Fallback type when inference yields no result |
1655
+ | `inference.nil_as_optional` | `bool` | `true` | Treat `nil` as an optional type |
1656
+ | `inference.treat_options_keyword_as_hash` | `bool` | `true` | Treat `**options` as `Hash` in `@option` tags |
1657
+ | `filter.include` | `string[]` | `[]` | Only include methods matching pattern |
1658
+ | `filter.exclude` | `string[]` | `[]` | Exclude methods matching pattern |
1659
+ | `filter.visibilities` | `string[]` | `["public","protected","private"]` | Visibilities to process |
1660
+ | `filter.scopes` | `string[]` | `["instance","class"]` | Scopes to process |
1661
+ | `filter.files.include` | `string[]` | `[]` | Only process files matching pattern |
1662
+ | `filter.files.exclude` | `string[]` | `["spec"]` | Skip files matching pattern |
1663
+ | `methods.instance.public` | `object` | `{}` | Overrides for instance public methods |
1664
+ | `methods.instance.protected` | `object` | `{}` | Overrides for instance protected methods |
1665
+ | `methods.instance.private` | `object` | `{}` | Overrides for instance private methods |
1666
+ | `methods.class.public` | `object` | `{}` | Overrides for class public methods |
1667
+ | `methods.class.protected` | `object` | `{}` | Overrides for class protected methods |
1668
+ | `methods.class.private` | `object` | `{}` | Overrides for class private methods |
1669
+ | `rbs.enabled` | `bool` | `false` | Enable RBS integration |
1670
+ | `rbs.collection` | `bool` | `false` | Auto-discover RBS collection from `rbs_collection.lock.yaml` |
1671
+ | `rbs.sig_dirs` | `string[]` | `["sig"]` | RBS signature directories |
1672
+ | `rbs.collection_dirs` | `string[]` | `[]` | RBS collection directories (set automatically) |
1673
+ | `rbs.collapse_generics` | `bool` | `false` | Strip generic arguments (`Array<String>` -> `Array`) |
1674
+ | `rbs.collapse_object_generics` | `bool` | `false` | Strip generics only when all are `Object` |
1675
+ | `rbs.warn_missing_collection` | `bool` | `true` | Warn when `rbs_collection.lock.yaml` found but collection not enabled |
1676
+ | `sorbet.enabled` | `bool` | `false` | Enable Sorbet integration |
1677
+ | `sorbet.rbi_dirs` | `string[]` | `["sorbet/rbi","rbi"]` | RBI file directories |
1678
+ | `sorbet.collapse_generics` | `bool` | `false` | Strip generic arguments from Sorbet signatures |
1679
+ | `plugins.require` | `string[]` | `[]` | Plugin paths for `require` |
1680
+
1681
+ </details>
1682
+
1255
1683
  ## CI integration
1256
1684
 
1257
1685
  Fail the build if files would need safe updates:
@@ -1268,11 +1696,33 @@ Apply safe fixes before the test stage:
1268
1696
  run: docscribe -a lib
1269
1697
  ```
1270
1698
 
1271
- Aggressively rebuild docs:
1699
+ Rebuild docs aggressively, preserve descriptions, suppress boilerplate, with verbose output:
1272
1700
 
1273
1701
  ```yaml
1274
1702
  - name: Rebuild inline docs
1275
- run: docscribe -A lib
1703
+ run: docscribe -AkB --verbose
1704
+ ```
1705
+
1706
+ Run static type checking with Steep (requires Ruby ≥ 3.2):
1707
+
1708
+ ```yaml
1709
+ - name: Steep (type check)
1710
+ if: ${{ matrix.ruby >= 3.2 }}
1711
+ run: bundle exec steep check
1712
+ ```
1713
+
1714
+ Fail the build if any generated docs still contain default placeholder text:
1715
+
1716
+ ```yaml
1717
+ - name: Check for placeholder docs
1718
+ run: docscribe check_for_comments lib/
1719
+ ```
1720
+
1721
+ For stricter validation, check for empty doc blocks:
1722
+
1723
+ ```yaml
1724
+ - name: Check for empty doc blocks
1725
+ run: "! grep -rn '^ # $' lib/"
1276
1726
  ```
1277
1727
 
1278
1728
  ## Comparison to YARD's parser
@@ -1301,13 +1751,39 @@ yard doc -o docs
1301
1751
  ## Roadmap
1302
1752
 
1303
1753
  - Method behavior inference from AST;
1304
- - YAML-based plugin configuration;
1754
+ - Deeper YARD integration — parse `.c` source comments and generate docs for C-extensions;
1755
+ - Custom parser plugins — support non-Ruby languages (Crystal, etc.) via the plugin system;
1305
1756
  - Effective config dump;
1306
- - JSON output;
1307
1757
  - Overload-aware signature selection;
1308
1758
  - Manual `@!attribute` merge policy;
1309
1759
  - Richer inference for common APIs;
1310
- - Editor integration.
1760
+ - Editor integration (LSP, VS Code extension);
1761
+ - Documentation coverage report — percentage of documented methods, params, returns;
1762
+ - Pre-commit hook auto-install (`docscribe init --pre-commit`);
1763
+ - Parallel processing for large codebases.
1764
+
1765
+ ## Editor Integration
1766
+
1767
+ Docscribe provides IDE plugins for a better development experience:
1768
+
1769
+ ### VS Code
1770
+
1771
+ [![VS Code](https://img.shields.io/badge/VS%20Code-plugin-blue?logo=visualstudiocode)](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
1772
+
1773
+ - **Repository:** [github.com/FlorexLabs/docscribe-vscode](https://github.com/FlorexLabs/docscribe-vscode)
1774
+ - **Marketplace:** [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=unurgunite.docscribe-vscode)
1775
+ - **Features:** inline diagnostics, quick-fix, auto-check on save, status bar
1776
+
1777
+ ### RubyMine
1778
+
1779
+ [![RubyMine](https://img.shields.io/badge/RubyMine-plugin-green?logo=jetbrains)](https://plugins.jetbrains.com/plugin/32349-docscribe)
1780
+
1781
+ - **Repository:** [github.com/FlorexLabs/docscribe-rubymine](https://github.com/FlorexLabs/docscribe-rubymine)
1782
+ - **Marketplace:** [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/32349-docscribe)
1783
+ - **Features:** ExternalAnnotator, AnAction, IntentionAction, Settings UI
1784
+
1785
+ > [!NOTE]
1786
+ > Both plugins require **docscribe >= 1.5.0**.
1311
1787
 
1312
1788
  ## Contributing
1313
1789
 
@@ -1322,6 +1798,14 @@ bundle exec rubocop
1322
1798
  - [Dev.to article](https://dev.to/unurgunite)
1323
1799
  - [GitHub Discussions](https://github.com/unurgunite/docscribe/discussions)
1324
1800
 
1801
+ ## Logo Attribution
1802
+
1803
+ The Docscribe logo uses the Ruby gem icon by [FlorexLabs](https://github.com/FlorexLabs).
1804
+
1805
+ <p>
1806
+ <img src="assets/icons/icon_128x128.png" alt="Docscribe logo">
1807
+ </p>
1808
+
1325
1809
  ## License
1326
1810
 
1327
1811
  MIT