docit 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb189284c9d9b8ad90f9d39fccaea0265223c3be58009b95b51b0dfbc12a3d78
4
- data.tar.gz: 5bd6b3277e7686470f1c24bc2824b2cd9ec97ebc785d9e19fefe9fafe7a2be1d
3
+ metadata.gz: 3fe9c345834352ce5e4219a291760ced965cdbad10084c28cbc2aabc2823a5b5
4
+ data.tar.gz: 86aa4599733b72c52fe66335c4581e4f69b408af1bbb00c0c2d5f42b1b7a51fe
5
5
  SHA512:
6
- metadata.gz: f921f704eb18446ecc475377d7fa8f0604c34349182139fa79ac26dd76260e04028b0b4443ca300e877298c96423ac5d8cc04b5a0887c6fa379a2752de8d6c21
7
- data.tar.gz: 1b6fbfc6aae77f31e74700243535e12be9c6caae790e87614903e7538e769746d774e4d31899fc3617fee01896f0f2a7e2a489fa0026d35e08cc5afd602e7c13
6
+ metadata.gz: 9ad86a75a50a1a1177cf7cbde2bf49f12eb7cc36e1df9fd268ff2b6519c7b01efffc680aab2484a83f3dbdfb410740633a7cabbf8072d873403deafc6c14ccd3
7
+ data.tar.gz: 2fbf560890376ef66614e12f961496b77ab5bfb2e994ffaeb006cc2a5ddd9ef82128115cf7d1fba39a89a889b0aec8342d487525eedad276a9b8244a6ee574d3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-04-11
4
+
5
+ ### Fixed
6
+ - Engine autoloading: `Docit::Engine` is now properly required when Rails is present, fixing `uninitialized constant Docit::Engine` and `Could not find generator 'docit:install'` in consuming apps
7
+ - AI provider clients (OpenAI, Anthropic, Groq) now raise `Docit::Ai::RateLimitError` on 429 responses with parsed retry-after timing
8
+ - `AutodocRunner` now retries rate-limited requests up to 3 times with exponential backoff (capped at 5 minutes) instead of failing immediately
9
+
3
10
  ## [0.2.0] - 2026-04-11
4
11
 
5
12
  ### Added
@@ -30,7 +37,7 @@
30
37
 
31
38
  - Initial release
32
39
  - DSL: `swagger_doc` macro for inline controller documentation
33
- - DSL: `use_docs` + `Docit::DocFile` for separate doc files (drf-spectacular style)
40
+ - DSL: `use_docs` + `Docit::DocFile` for separate doc files
34
41
  - Builders: request body, response, and parameter builders with nested object/array support
35
42
  - Schema `$ref` components via `Docit.define_schema`
36
43
  - File upload support (`type: :file` → `string/binary`)
data/README.md CHANGED
@@ -5,7 +5,43 @@
5
5
 
6
6
  Decorator-style API documentation for Ruby on Rails. Write OpenAPI 3.0.3 docs with clean controller DSL macros, separate doc modules, or AI-assisted scaffolding for undocumented endpoints.
7
7
 
8
- Inspired by [drf-spectacular](https://github.com/tfranzel/drf-spectacular) for Django REST Framework.
8
+ ## Table Of Contents
9
+
10
+ - Getting started
11
+ - [Installation](#installation)
12
+ - [Configuration](#configuration)
13
+ - [Usage](#usage)
14
+ - Documentation styles
15
+ - [Style 1: Inline (simple APIs)](#style-1-inline-simple-apis)
16
+ - [Style 2: Separate doc files (recommended for larger APIs)](#style-2-separate-doc-files-recommended-for-larger-apis)
17
+ - Endpoint DSL reference
18
+ - [Endpoint documentation DSL](#endpoint-documentation-dsl)
19
+ - [Request bodies](#request-bodies)
20
+ - [Path parameters](#path-parameters)
21
+ - [Enums](#enums)
22
+ - [Security](#security)
23
+ - [Deprecated endpoints](#deprecated-endpoints)
24
+ - [Nested objects and arrays](#nested-objects-and-arrays)
25
+ - [Response examples](#response-examples)
26
+ - [Shared schemas (`$ref`)](#shared-schemas-ref)
27
+ - [File uploads](#file-uploads)
28
+ - AI documentation
29
+ - [AI Automatic Documentation](#ai-automatic-documentation)
30
+ - [Quick start (included in install)](#quick-start-included-in-install)
31
+ - [Standalone commands](#standalone-commands)
32
+ - [Supported providers](#supported-providers)
33
+ - [What the AI generates](#what-the-ai-generates)
34
+ - Runtime and development
35
+ - [How it works](#how-it-works)
36
+ - [Mounting at a different path](#mounting-at-a-different-path)
37
+ - [JSON spec only](#json-spec-only)
38
+ - [Development](#development)
39
+ - [Contributing](#contributing)
40
+ - [License](#license)
41
+ - Project docs
42
+ - [CHANGELOG](CHANGELOG.md)
43
+ - [CONTRIBUTING](CONTRIBUTING.md)
44
+ - [CODE OF CONDUCT](CODE_OF_CONDUCT.md)
9
45
 
10
46
  ## Installation
11
47
 
@@ -85,7 +121,7 @@ end
85
121
 
86
122
  ### Style 2: Separate doc files (recommended for larger APIs)
87
123
 
88
- Keep controllers clean by defining docs in dedicated files, just like drf-spectacular:
124
+ Keep controllers clean by defining docs in dedicated files:
89
125
 
90
126
  ```ruby
91
127
  # app/docs/api/v1/users_docs.rb
@@ -414,6 +450,8 @@ DRY_RUN=1 rails docit:autodoc
414
450
  | Anthropic | Requires API key from console.anthropic.com |
415
451
  | Groq | Free tier at console.groq.com |
416
452
 
453
+ All providers automatically retry on rate-limit (429) errors with exponential backoff, so free-tier usage works out of the box.
454
+
417
455
  AI configuration is stored in `.docit_ai.yml`.
418
456
  If your app does not have a `.gitignore`, add `.docit_ai.yml` manually.
419
457
 
@@ -41,6 +41,12 @@ module Docit
41
41
 
42
42
  if response.is_a?(Net::HTTPSuccess) == false
43
43
  message = body.dig("error", "message") || "Unknown API error"
44
+
45
+ if response.code == "429"
46
+ retry_after = response["Retry-After"]&.to_f
47
+ raise RateLimitError.new("Anthropic rate limit exceeded", retry_after: retry_after)
48
+ end
49
+
44
50
  raise Error, "Anthropic API error (#{response.code}): #{message}"
45
51
  end
46
52
 
@@ -103,6 +103,7 @@ module Docit
103
103
  def generate_docs(gaps, config)
104
104
  client = Client.for(config)
105
105
  generated = Hash.new { |h, k| h[k] = [] }
106
+ max_retries = 3
106
107
 
107
108
  @output.puts "Generating documentation............."
108
109
 
@@ -116,14 +117,29 @@ module Docit
116
117
  end
117
118
 
118
119
  prompt = builder.build
119
- doc_block = client.generate(prompt).strip
120
- doc_block = strip_markdown_fences(doc_block)
121
-
122
- generated[gap[:controller]] << doc_block
123
- @results[:generated] += 1
124
- @output.puts " done"
125
- rescue Docit::Ai::Error => e
126
- @output.puts " failed (#{e.message})"
120
+ retries = 0
121
+
122
+ begin
123
+ doc_block = client.generate(prompt).strip
124
+ doc_block = strip_markdown_fences(doc_block)
125
+
126
+ generated[gap[:controller]] << doc_block
127
+ @results[:generated] += 1
128
+ @output.puts " done"
129
+ rescue Docit::Ai::RateLimitError => e
130
+ retries += 1
131
+ if retries <= max_retries
132
+ wait = e.retry_after || (2**retries * 10)
133
+ wait = [wait, 300].min # cap at 5 minutes
134
+ @output.puts " rate limited, waiting #{wait.round}s (attempt #{retries}/#{max_retries})..."
135
+ sleep(wait)
136
+ retry
137
+ else
138
+ @output.puts " failed (rate limit exceeded after #{max_retries} retries)"
139
+ end
140
+ rescue Docit::Ai::Error => e
141
+ @output.puts " failed (#{e.message})"
142
+ end
127
143
  end
128
144
 
129
145
  generated
@@ -4,6 +4,15 @@ module Docit
4
4
  module Ai
5
5
  class Error < Docit::Error; end
6
6
 
7
+ class RateLimitError < Error
8
+ attr_reader :retry_after
9
+
10
+ def initialize(message, retry_after: nil)
11
+ @retry_after = retry_after
12
+ super(message)
13
+ end
14
+ end
15
+
7
16
  # Factory for AI provider clients.
8
17
  module Client
9
18
  def self.for(config)
@@ -39,6 +39,12 @@ module Docit
39
39
 
40
40
  if response.is_a?(Net::HTTPSuccess) == false
41
41
  message = body.dig("error", "message") || "Unknown API error"
42
+
43
+ if response.code == "429"
44
+ retry_after = parse_retry_after(response, message)
45
+ raise RateLimitError.new("Groq rate limit exceeded", retry_after: retry_after)
46
+ end
47
+
42
48
  raise Error, "Groq API error (#{response.code}): #{message}"
43
49
  end
44
50
 
@@ -50,6 +56,24 @@ module Docit
50
56
  rescue JSON::ParserError
51
57
  raise Error, "#{provider_name} returned invalid JSON (HTTP #{response.code})"
52
58
  end
59
+
60
+ def parse_retry_after(response, message)
61
+ # Check Retry-After header first (seconds)
62
+ if (header = response["Retry-After"])
63
+ return header.to_f if header.to_f > 0
64
+ end
65
+
66
+ # Parse "try again in XmY.Zs" from error message
67
+ if message =~ /(\d+)m([\d.]+)s/
68
+ return (Regexp.last_match(1).to_i * 60) + Regexp.last_match(2).to_f
69
+ end
70
+
71
+ if message =~ /([\d.]+)s/
72
+ return Regexp.last_match(1).to_f
73
+ end
74
+
75
+ nil
76
+ end
53
77
  end
54
78
  end
55
79
  end
@@ -39,6 +39,12 @@ module Docit
39
39
 
40
40
  if response.is_a?(Net::HTTPSuccess) == false
41
41
  message = body.dig("error", "message") || "Unknown API error"
42
+
43
+ if response.code == "429"
44
+ retry_after = response["Retry-After"]&.to_f
45
+ raise RateLimitError.new("OpenAI rate limit exceeded", retry_after: retry_after)
46
+ end
47
+
42
48
  raise Error, "OpenAI API error (#{response.code}): #{message}"
43
49
  end
44
50
 
data/lib/docit/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docit
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/docit.rb CHANGED
@@ -47,4 +47,5 @@ module Docit
47
47
  end
48
48
  end
49
49
 
50
+ require_relative "docit/engine" if defined?(Rails::Engine)
50
51
  require_relative "docit/ai"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - S13G