docit 0.3.1 → 0.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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- data/CHANGELOG.md +33 -2
- data/CONTRIBUTING.md +1 -1
- data/README.md +41 -14
- data/app/controllers/docit/ui_controller.rb +57 -3
- data/config/routes.rb +3 -0
- data/lib/docit/ai/autodoc_runner.rb +25 -24
- data/lib/docit/ai/system_insight_generator.rb +127 -0
- data/lib/docit/ai/tag_injector.rb +1 -1
- data/lib/docit/ai.rb +1 -0
- data/lib/docit/configuration.rb +37 -1
- data/lib/docit/doc_file.rb +1 -1
- data/lib/docit/dsl.rb +6 -3
- data/lib/docit/operation.rb +7 -2
- data/lib/docit/route_inspector.rb +1 -1
- data/lib/docit/schema_generator.rb +24 -5
- data/lib/docit/system_graph/edge.rb +29 -0
- data/lib/docit/system_graph/generator.rb +15 -0
- data/lib/docit/system_graph/graph.rb +55 -0
- data/lib/docit/system_graph/node.rb +32 -0
- data/lib/docit/system_graph/rails_analyzer.rb +251 -0
- data/lib/docit/system_graph/source_scanner.rb +77 -0
- data/lib/docit/system_graph.rb +8 -0
- data/lib/docit/ui/base_renderer.rb +54 -21
- data/lib/docit/ui/system_renderer.rb +113 -0
- data/lib/docit/ui/system_script.rb +1495 -0
- data/lib/docit/ui/system_styles.rb +582 -0
- data/lib/docit/version.rb +1 -1
- data/lib/docit.rb +4 -0
- data/lib/generators/docit/install/install_generator.rb +3 -3
- data/lib/generators/docit/install/templates/initializer.rb +4 -0
- data/lib/tasks/docit.rake +1 -1
- metadata +15 -6
- data/docs/images/scalar_image.png +0 -0
- data/docs/images/swagger_image.png +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85a7407aab5a5fea2aa0c679d469360f124884ebeac2e4479265cbcdd818210d
|
|
4
|
+
data.tar.gz: c946eea9c5a664931630b00726f33b8e5bb296c1493e799c67cd905f330cba1d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3dd0a62c6d0f138916839a7bcc445f98921abaf9e0ef477473e4dddba67f2033fd29ffb51a4803597d98f0807ee24c53d6b5a90a8d144d87c54669ee51155fef
|
|
7
|
+
data.tar.gz: d953ceef06cd91034fa79503a07da386dcefc7cdddabcb3567d1638d6546a2c484bdebd82f263d732a94b89447a9e9f6b659d7008c322d2e238e002aca286a1b
|
|
@@ -17,7 +17,7 @@ Explain the problem this feature would solve or the workflow it would improve.
|
|
|
17
17
|
Describe how you'd like it to work, including example DSL usage if applicable:
|
|
18
18
|
|
|
19
19
|
```ruby
|
|
20
|
-
|
|
20
|
+
doc_for :index do
|
|
21
21
|
# your proposed syntax here
|
|
22
22
|
end
|
|
23
23
|
```
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,35 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.5.0] - 2026-05-31
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **System Map**: a local, deterministic architecture graph at `/api-docs/system` (JSON at `/api-docs/system.json`), built from Rails routes, controllers, actions, Docit docs coverage, schemas, models, and source-derived service/job/mailer nodes — no external service required
|
|
5
|
+
- System Map navigation alongside the Scalar and Swagger UIs
|
|
6
|
+
- Light and dark themes for the System Map (light by default), with a toolbar toggle persisted across visits
|
|
7
|
+
- **Diagram view**: interactive architecture graph with drag-to-arrange, scroll-to-zoom, pan, fit-to-screen, and PNG export; click a node to inspect its connections, with neighbouring nodes highlighted; `/` focuses node search
|
|
8
|
+
- Endpoints-section filter for the diagram (Users, Orders, …) that narrows the graph to one resource and everything it touches
|
|
9
|
+
- **Docs view**: a request/response reference grouped by resource, with human-readable endpoint titles derived from doc summaries (falling back to REST conventions), a documentation-coverage indicator per section, and a detail panel showing parameters, request body, and responses
|
|
10
|
+
- AI section explanations in the Docs view: "Explain section" summarises how a resource's endpoints work together, gated by a coverage warning so undocumented sections don't silently spend tokens
|
|
11
|
+
|
|
12
|
+
## [0.4.0] - 2026-04-18
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `operation_id` DSL method: set a custom `operationId` per endpoint for cleaner SDK codegen
|
|
16
|
+
- Auto-generated `operationId` for every endpoint (e.g., `index_users`, `login_auth`) when not explicitly set
|
|
17
|
+
- `config.license` option: add license info (`name`, `url`) to the OpenAPI spec
|
|
18
|
+
- `config.contact` option: add contact info (`name`, `email`, `url`) to the OpenAPI spec
|
|
19
|
+
- `config.terms_of_service` option: add terms of service URL to the OpenAPI spec
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Renamed `swagger_doc` DSL to `doc_for` (`swagger_doc` still works as a backward-compatible alias)
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- `TagInjector` no longer crashes when `initializer_path` is nil
|
|
26
|
+
- `docit:autodoc` rake task: removed broken `--dry-run` CLI flag (use `DRY_RUN=1` env var instead)
|
|
27
|
+
- Fixed dummy app `auth_docs.rb` summary and request body to match integration test expectations
|
|
28
|
+
|
|
29
|
+
## [0.3.1] - 2026-04-17
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Fixed gem packaging: `doc_block_validator.rb` was missing from the published 0.3.0 gem, causing `LoadError` on require
|
|
2
33
|
|
|
3
34
|
## [0.3.0] - 2026-04-16
|
|
4
35
|
|
|
@@ -51,7 +82,7 @@
|
|
|
51
82
|
## [0.1.0] - 2026-04-08
|
|
52
83
|
|
|
53
84
|
- Initial release
|
|
54
|
-
- DSL: `
|
|
85
|
+
- DSL: `doc_for` macro for inline controller documentation
|
|
55
86
|
- DSL: `use_docs` + `Docit::DocFile` for separate doc files
|
|
56
87
|
- Builders: request body, response, and parameter builders with nested object/array support
|
|
57
88
|
- Schema `$ref` components via `Docit.define_schema`
|
data/CONTRIBUTING.md
CHANGED
|
@@ -55,7 +55,7 @@ bundle exec rspec spec/docit/v2_features_spec.rb:30
|
|
|
55
55
|
lib/docit.rb # Entry point, configuration, schema registry
|
|
56
56
|
lib/docit/configuration.rb # Config class (title, auth, tags)
|
|
57
57
|
lib/docit/registry.rb # Global operation store
|
|
58
|
-
lib/docit/dsl.rb #
|
|
58
|
+
lib/docit/dsl.rb # doc_for macro
|
|
59
59
|
lib/docit/operation.rb # Single endpoint documentation
|
|
60
60
|
lib/docit/builders/ # DSL builders (response, request_body, parameter)
|
|
61
61
|
lib/docit/schema_definition.rb # Reusable $ref schema definitions
|
data/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Decorator-style API documentation for Ruby on Rails. Write OpenAPI 3.0.3 docs wi
|
|
|
11
11
|
### Swagger
|
|
12
12
|

|
|
13
13
|
|
|
14
|
+
> **Full documentation:** [doc-it.dev/docs](https://doc-it.dev/docs)
|
|
15
|
+
|
|
14
16
|
## Table Of Contents
|
|
15
17
|
|
|
16
18
|
- Getting started
|
|
@@ -39,6 +41,7 @@ Decorator-style API documentation for Ruby on Rails. Write OpenAPI 3.0.3 docs wi
|
|
|
39
41
|
- [What the AI generates](#what-the-ai-generates)
|
|
40
42
|
- Runtime and development
|
|
41
43
|
- [Documentation UIs](#documentation-uis)
|
|
44
|
+
- [System Map](#system-map)
|
|
42
45
|
- [How it works](#how-it-works)
|
|
43
46
|
- [Mounting at a different path](#mounting-at-a-different-path)
|
|
44
47
|
- [JSON spec only](#json-spec-only)
|
|
@@ -105,6 +108,15 @@ Docit.configure do |config|
|
|
|
105
108
|
config.server "https://api.example.com", description: "Production"
|
|
106
109
|
config.server "https://staging.example.com", description: "Staging"
|
|
107
110
|
config.server "http://localhost:3000", description: "Development"
|
|
111
|
+
|
|
112
|
+
# License information:
|
|
113
|
+
config.license name: "MIT", url: "https://opensource.org/licenses/MIT"
|
|
114
|
+
|
|
115
|
+
# Contact information:
|
|
116
|
+
config.contact name: "API Team", email: "api@example.com", url: "https://example.com/support"
|
|
117
|
+
|
|
118
|
+
# Terms of service:
|
|
119
|
+
config.terms_of_service "https://example.com/tos"
|
|
108
120
|
end
|
|
109
121
|
```
|
|
110
122
|
|
|
@@ -114,11 +126,11 @@ Docit supports two styles for documenting endpoints. Choose whichever fits your
|
|
|
114
126
|
|
|
115
127
|
### Style 1: Inline (simple APIs)
|
|
116
128
|
|
|
117
|
-
Add `
|
|
129
|
+
Add `doc_for` blocks directly in your controller:
|
|
118
130
|
|
|
119
131
|
```ruby
|
|
120
132
|
class Api::V1::UsersController < ApplicationController
|
|
121
|
-
|
|
133
|
+
doc_for :index do
|
|
122
134
|
summary "List all users"
|
|
123
135
|
tags "Users"
|
|
124
136
|
response 200, "Users retrieved"
|
|
@@ -129,6 +141,8 @@ class Api::V1::UsersController < ApplicationController
|
|
|
129
141
|
end
|
|
130
142
|
```
|
|
131
143
|
|
|
144
|
+
> **Upgrading?** The previous `swagger_doc` method still works as an alias for `doc_for`, so existing code won't break.
|
|
145
|
+
|
|
132
146
|
### Style 2: Separate doc files (recommended for larger APIs)
|
|
133
147
|
|
|
134
148
|
Keep controllers clean by defining docs in dedicated files:
|
|
@@ -189,13 +203,13 @@ class Api::V1::UsersController < ApplicationController
|
|
|
189
203
|
end
|
|
190
204
|
```
|
|
191
205
|
|
|
192
|
-
You can also mix both styles — use `use_docs` for most actions and add inline `
|
|
206
|
+
You can also mix both styles — use `use_docs` for most actions and add inline `doc_for` for one-offs:
|
|
193
207
|
|
|
194
208
|
```ruby
|
|
195
209
|
class Api::V1::UsersController < ApplicationController
|
|
196
210
|
use_docs Api::V1::UsersDocs # loads :index and :create from doc file
|
|
197
211
|
|
|
198
|
-
|
|
212
|
+
doc_for :destroy do # inline doc for this one action
|
|
199
213
|
summary "Delete user"
|
|
200
214
|
tags "Users"
|
|
201
215
|
response 204, "Deleted"
|
|
@@ -209,12 +223,12 @@ end
|
|
|
209
223
|
|
|
210
224
|
### Endpoint documentation DSL
|
|
211
225
|
|
|
212
|
-
The following examples work in both `
|
|
226
|
+
The following examples work in both `doc_for` blocks and `doc` blocks.
|
|
213
227
|
|
|
214
228
|
### Request bodies
|
|
215
229
|
|
|
216
230
|
```ruby
|
|
217
|
-
|
|
231
|
+
doc_for :create do
|
|
218
232
|
summary "Create a user"
|
|
219
233
|
tags "Users"
|
|
220
234
|
|
|
@@ -247,7 +261,7 @@ end
|
|
|
247
261
|
### Path parameters
|
|
248
262
|
|
|
249
263
|
```ruby
|
|
250
|
-
|
|
264
|
+
doc_for :show do
|
|
251
265
|
summary "Get a user"
|
|
252
266
|
tags "Users"
|
|
253
267
|
|
|
@@ -271,7 +285,7 @@ end
|
|
|
271
285
|
### Enums
|
|
272
286
|
|
|
273
287
|
```ruby
|
|
274
|
-
|
|
288
|
+
doc_for :index do
|
|
275
289
|
summary "List orders"
|
|
276
290
|
tags "Orders"
|
|
277
291
|
|
|
@@ -293,7 +307,7 @@ end
|
|
|
293
307
|
Mark endpoints as requiring authentication:
|
|
294
308
|
|
|
295
309
|
```ruby
|
|
296
|
-
|
|
310
|
+
doc_for :destroy do
|
|
297
311
|
summary "Delete a user"
|
|
298
312
|
tags "Users"
|
|
299
313
|
security :bearer_auth # references the scheme from your config
|
|
@@ -306,7 +320,7 @@ end
|
|
|
306
320
|
### Deprecated endpoints
|
|
307
321
|
|
|
308
322
|
```ruby
|
|
309
|
-
|
|
323
|
+
doc_for :legacy_search do
|
|
310
324
|
summary "Search (legacy)"
|
|
311
325
|
tags "Search"
|
|
312
326
|
deprecated
|
|
@@ -373,7 +387,7 @@ end
|
|
|
373
387
|
Reference them in any endpoint with `schema ref:`:
|
|
374
388
|
|
|
375
389
|
```ruby
|
|
376
|
-
|
|
390
|
+
doc_for :show do
|
|
377
391
|
summary "Get user"
|
|
378
392
|
tags "Users"
|
|
379
393
|
|
|
@@ -386,7 +400,7 @@ swagger_doc :show do
|
|
|
386
400
|
end
|
|
387
401
|
end
|
|
388
402
|
|
|
389
|
-
|
|
403
|
+
doc_for :create do
|
|
390
404
|
summary "Create user"
|
|
391
405
|
tags "Users"
|
|
392
406
|
|
|
@@ -407,7 +421,7 @@ This outputs `$ref: '#/components/schemas/User'` in the spec — Swagger UI reso
|
|
|
407
421
|
Use `type: :file` with `content_type: "multipart/form-data"` for file upload endpoints:
|
|
408
422
|
|
|
409
423
|
```ruby
|
|
410
|
-
|
|
424
|
+
doc_for :upload_avatar do
|
|
411
425
|
summary "Upload avatar"
|
|
412
426
|
tags "Users"
|
|
413
427
|
|
|
@@ -486,15 +500,28 @@ Docit ships with two documentation UIs, both reading from the same OpenAPI spec:
|
|
|
486
500
|
| `/api-docs` | Default (Scalar) | Configurable via `config.default_ui` |
|
|
487
501
|
| `/api-docs/scalar` | Scalar API Reference | Modern UI with built-in API client, dark mode, code samples |
|
|
488
502
|
| `/api-docs/swagger` | Swagger UI | Classic OpenAPI explorer |
|
|
503
|
+
| `/api-docs/system` | System Map | Local architecture graph for routes, controllers, docs coverage, and app structure |
|
|
489
504
|
| `/api-docs/spec` | Raw JSON | OpenAPI 3.0.3 spec |
|
|
490
505
|
|
|
491
506
|
Both UIs include a navigation bar to switch between them. Set `config.default_ui = :swagger` to make Swagger the default at `/api-docs`.
|
|
492
507
|
|
|
508
|
+
## System Map
|
|
509
|
+
|
|
510
|
+
Docit includes a local system map for understanding how your Rails API fits together. It builds a **deterministic** graph from Rails routes, controller actions, Docit docs, schemas, models, and source references — no external service is required to view it.
|
|
511
|
+
|
|
512
|
+
Visit `/api-docs/system` to open it. It has two views, a light/dark theme toggle, and PNG export:
|
|
513
|
+
|
|
514
|
+
- **Diagram** — an interactive architecture graph. Drag nodes to rearrange, scroll to zoom, and click a node to inspect its connections (its neighbours highlight while the rest fade back). Filter to a single resource with the section dropdown, or press `/` to search nodes.
|
|
515
|
+
- **Docs** — a request/response reference grouped by resource. Each endpoint shows a human-readable title, its method and path, parameters, request body, and responses, drawn from your Docit docs. A coverage indicator shows how many endpoints in each section are documented.
|
|
516
|
+
|
|
517
|
+
If you have an AI provider configured, the Docs view's **Explain section** button summarises how a resource's endpoints work together. It warns before running on sections with undocumented endpoints, so documenting first gives a better result.
|
|
518
|
+
|
|
493
519
|
## How it works
|
|
494
520
|
|
|
495
|
-
1. `
|
|
521
|
+
1. `doc_for` registers an **Operation** for each controller action in a global **Registry**
|
|
496
522
|
2. When someone visits `/api-docs/spec`, Docit's **SchemaGenerator** combines all registered operations with your Rails routes (via **RouteInspector**) to produce an OpenAPI 3.0.3 JSON document
|
|
497
523
|
3. The **Engine** serves the configured documentation UI at `/api-docs`, pointing it at the generated spec
|
|
524
|
+
4. The **SystemGraph** builds a local architecture graph for `/api-docs/system`
|
|
498
525
|
|
|
499
526
|
The DSL is included in all controllers automatically via a Rails Engine initializer — no manual `include` needed if you're using `ActionController::API` or `ActionController::Base`.
|
|
500
527
|
|
|
@@ -16,6 +16,31 @@ module Docit
|
|
|
16
16
|
render_ui(:scalar)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def system
|
|
20
|
+
render_ui(:system)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def system_spec
|
|
24
|
+
unless Docit.configuration.system_graph_enabled
|
|
25
|
+
return render(json: { error: "System graph disabled" }, status: :not_found)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
RouteInspector.eager_load_controllers!
|
|
29
|
+
render json: SystemGraph::Generator.generate
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def system_insights
|
|
33
|
+
graph = system_graph
|
|
34
|
+
insight = Ai::SystemInsightGenerator.new(
|
|
35
|
+
graph: graph,
|
|
36
|
+
selected_node_ids: selected_node_ids,
|
|
37
|
+
mode: insight_mode
|
|
38
|
+
).generate
|
|
39
|
+
render json: { insight: insight }
|
|
40
|
+
rescue Docit::Error => e
|
|
41
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
42
|
+
end
|
|
43
|
+
|
|
19
44
|
def spec
|
|
20
45
|
RouteInspector.eager_load_controllers!
|
|
21
46
|
render json: SchemaGenerator.generate
|
|
@@ -25,21 +50,50 @@ module Docit
|
|
|
25
50
|
|
|
26
51
|
RENDERERS = {
|
|
27
52
|
swagger: UI::SwaggerRenderer,
|
|
28
|
-
scalar: UI::ScalarRenderer
|
|
53
|
+
scalar: UI::ScalarRenderer,
|
|
54
|
+
system: UI::SystemRenderer
|
|
29
55
|
}.freeze
|
|
30
56
|
|
|
31
57
|
def render_ui(ui_name)
|
|
32
|
-
renderer = RENDERERS.fetch(ui_name).new(
|
|
58
|
+
renderer = RENDERERS.fetch(ui_name).new(
|
|
59
|
+
spec_url: spec_url,
|
|
60
|
+
system_url: system_url,
|
|
61
|
+
system_insights_url: system_insights_url,
|
|
62
|
+
nav_paths: nav_paths
|
|
63
|
+
)
|
|
33
64
|
render html: renderer.render.html_safe, layout: false
|
|
34
65
|
end
|
|
35
66
|
|
|
67
|
+
def system_graph
|
|
68
|
+
RouteInspector.eager_load_controllers!
|
|
69
|
+
SystemGraph::Generator.generate
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def selected_node_ids
|
|
73
|
+
params.fetch(:node_ids, "").to_s.split(",").reject(&:empty?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Validate at the boundary: only the known modes are honored, anything
|
|
77
|
+
# else falls back to the safe per-node default.
|
|
78
|
+
def insight_mode
|
|
79
|
+
params[:mode].to_s == "section" ? :section : :nodes
|
|
80
|
+
end
|
|
81
|
+
|
|
36
82
|
def spec_url
|
|
37
83
|
"#{request.base_url}#{Docit::Engine.routes.url_helpers.spec_path}"
|
|
38
84
|
end
|
|
39
85
|
|
|
86
|
+
def system_url
|
|
87
|
+
"#{request.base_url}#{Docit::Engine.routes.url_helpers.system_spec_path}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def system_insights_url
|
|
91
|
+
"#{request.base_url}#{Docit::Engine.routes.url_helpers.system_insights_path}"
|
|
92
|
+
end
|
|
93
|
+
|
|
40
94
|
def nav_paths
|
|
41
95
|
helpers = Docit::Engine.routes.url_helpers
|
|
42
|
-
{ swagger: helpers.swagger_path, scalar: helpers.scalar_path }
|
|
96
|
+
{ swagger: helpers.swagger_path, scalar: helpers.scalar_path, system: helpers.system_path }
|
|
43
97
|
end
|
|
44
98
|
end
|
|
45
99
|
end
|
data/config/routes.rb
CHANGED
|
@@ -4,5 +4,8 @@ Docit::Engine.routes.draw do
|
|
|
4
4
|
root to: "ui#index"
|
|
5
5
|
get "swagger", to: "ui#swagger"
|
|
6
6
|
get "scalar", to: "ui#scalar"
|
|
7
|
+
get "system.json", to: "ui#system_spec", defaults: { format: :json }, as: :system_spec
|
|
8
|
+
get "system/insights", to: "ui#system_insights", defaults: { format: :json }, as: :system_insights
|
|
9
|
+
get "system", to: "ui#system", as: :system
|
|
7
10
|
get "spec", to: "ui#spec", defaults: { format: :json }
|
|
8
11
|
end
|
|
@@ -62,11 +62,11 @@ module Docit
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
routes_file = Rails.root.join("config", "routes.rb")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
return unless File.exist?(routes_file) && !File.read(routes_file).include?("Docit::Engine")
|
|
66
|
+
|
|
67
|
+
@output.puts "Warning: Docit engine is not mounted in config/routes.rb"
|
|
68
|
+
@output.puts " Run: rails generate docit:install (or add: mount Docit::Engine => \"/api-docs\")"
|
|
69
|
+
@output.puts ""
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def detect_gaps
|
|
@@ -84,20 +84,20 @@ module Docit
|
|
|
84
84
|
@output.puts "Docit will send controller source code to #{config.provider.capitalize} to generate documentation."
|
|
85
85
|
@output.puts "Review the endpoints first if they contain secrets or proprietary logic."
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
return unless @input.respond_to?(:tty?) && @input.tty?
|
|
88
|
+
|
|
89
|
+
loop do
|
|
90
|
+
@output.print "Continue? (y/n): "
|
|
91
|
+
choice = @input.gets.to_s.strip.downcase
|
|
92
|
+
|
|
93
|
+
case choice
|
|
94
|
+
when "y", "yes"
|
|
95
|
+
@output.puts ""
|
|
96
|
+
return
|
|
97
|
+
when "n", "no"
|
|
98
|
+
raise Docit::Error, "Autodoc cancelled."
|
|
99
|
+
else
|
|
100
|
+
@output.puts "Please enter y or n."
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -138,6 +138,7 @@ module Docit
|
|
|
138
138
|
invalid_output_retries += 1
|
|
139
139
|
if invalid_output_retries <= MAX_INVALID_OUTPUT_RETRIES
|
|
140
140
|
validation_error = e.message
|
|
141
|
+
@output.puts " invalid output, retrying"
|
|
141
142
|
retry
|
|
142
143
|
end
|
|
143
144
|
|
|
@@ -184,11 +185,11 @@ module Docit
|
|
|
184
185
|
|
|
185
186
|
def inject_tags(generated)
|
|
186
187
|
all_tags = generated.values.flatten.join("\n").scan(/tags\s+["']([^"']+)["']/).flatten
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
return unless all_tags.any?
|
|
189
|
+
|
|
190
|
+
injected = TagInjector.new(tags: all_tags).inject
|
|
191
|
+
injected.each { |tag| @output.puts " Added tag \"#{tag}\" to config/initializers/docit.rb" }
|
|
192
|
+
@results[:tags] = injected
|
|
192
193
|
end
|
|
193
194
|
|
|
194
195
|
def strip_markdown_fences(text)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Docit
|
|
6
|
+
module Ai
|
|
7
|
+
class SystemInsightGenerator
|
|
8
|
+
# mode: :nodes -> explain an arbitrary selection (diagram "AI Explain")
|
|
9
|
+
# :section -> explain one resource and how its endpoints work together
|
|
10
|
+
def initialize(graph:, selected_node_ids: [], mode: :nodes)
|
|
11
|
+
@graph = graph
|
|
12
|
+
@selected_node_ids = selected_node_ids
|
|
13
|
+
@mode = mode
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate
|
|
17
|
+
config = Configuration.load
|
|
18
|
+
Client.for(config).generate(prompt)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :graph, :selected_node_ids, :mode
|
|
24
|
+
|
|
25
|
+
def prompt
|
|
26
|
+
mode == :section ? section_prompt : nodes_prompt
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def nodes_prompt
|
|
30
|
+
<<~PROMPT
|
|
31
|
+
You are a senior engineer explaining a Rails system architecture to a developer. Keep your explanation extremely concise, professional, and clear. Avoid fluff, unnecessary details, or general tutorial information. Focus only on the provided components.
|
|
32
|
+
|
|
33
|
+
FORMAT YOUR RESPONSE EXACTLY LIKE THIS:
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
A 1-2 sentence plain-English summary of what this component/action does.
|
|
37
|
+
|
|
38
|
+
## Data Flow
|
|
39
|
+
Show a simple, linear flow diagram using text and arrows (→). Keep it to 1 line if possible.
|
|
40
|
+
Example:
|
|
41
|
+
Client → GET /api/v1/users → UsersController#index → queries User model
|
|
42
|
+
|
|
43
|
+
## Connections & Interactions
|
|
44
|
+
List only the direct, relevant relationships from the graph (max 3 bullets):
|
|
45
|
+
- **Component** (type): Action details/purpose.
|
|
46
|
+
|
|
47
|
+
Do not invent or assume anything outside of the provided graph. Keep the total response under 150 words.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Selected node ids:
|
|
52
|
+
#{selected_node_ids.join("\n")}
|
|
53
|
+
|
|
54
|
+
Graph JSON:
|
|
55
|
+
#{JSON.pretty_generate(compact_graph)}
|
|
56
|
+
PROMPT
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def section_prompt
|
|
60
|
+
<<~PROMPT
|
|
61
|
+
You are a senior engineer writing the introduction to a section of API documentation. The section covers ONE resource and all of its endpoints. Explain, for a developer new to this codebase, what the resource is for and how its endpoints work together as a workflow.
|
|
62
|
+
|
|
63
|
+
Use the documented summaries where available. Where an endpoint has no documentation, infer cautiously from its HTTP method and path, and do not fabricate behavior.
|
|
64
|
+
|
|
65
|
+
FORMAT YOUR RESPONSE EXACTLY LIKE THIS:
|
|
66
|
+
|
|
67
|
+
## What this section does
|
|
68
|
+
1-2 sentences on the resource and its overall purpose.
|
|
69
|
+
|
|
70
|
+
## How the endpoints work together
|
|
71
|
+
A short narrative (2-4 sentences) describing the typical flow across these endpoints — e.g. how a client lists, creates, then updates this resource. Reference endpoints by their HTTP method and path.
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
Up to 2 bullets on related models/services or anything a consumer must know. Omit this section if there is nothing concrete to say.
|
|
75
|
+
|
|
76
|
+
Do not invent endpoints, fields, or behavior not present in the graph. Keep the total response under 180 words.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
Endpoint node ids in this section:
|
|
81
|
+
#{selected_node_ids.join("\n")}
|
|
82
|
+
|
|
83
|
+
Graph JSON:
|
|
84
|
+
#{JSON.pretty_generate(compact_graph)}
|
|
85
|
+
PROMPT
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def compact_graph
|
|
89
|
+
nodes = selected_nodes
|
|
90
|
+
node_ids = nodes.map { |node| node[:id] }
|
|
91
|
+
|
|
92
|
+
# Include edges where at least one end is in the selection
|
|
93
|
+
related_edges = graph[:edges].select do |edge|
|
|
94
|
+
node_ids.include?(edge[:source]) || node_ids.include?(edge[:target])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Also include neighbor nodes (one hop away) for context
|
|
98
|
+
neighbor_ids = Set.new(node_ids)
|
|
99
|
+
related_edges.each do |edge|
|
|
100
|
+
neighbor_ids.add(edge[:source])
|
|
101
|
+
neighbor_ids.add(edge[:target])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
neighbor_nodes = graph[:nodes].select { |node| neighbor_ids.include?(node[:id]) }
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
selected_nodes: nodes.map { |node| compact_hash(node, %i[id type label status file metadata]) },
|
|
108
|
+
context_nodes: (neighbor_nodes - nodes).map { |node| compact_hash(node, %i[id type label status]) },
|
|
109
|
+
edges: related_edges.map { |edge| compact_hash(edge, %i[source target type confidence evidence]) },
|
|
110
|
+
stats: graph[:stats]
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def selected_nodes
|
|
115
|
+
return graph[:nodes] if selected_node_ids.empty?
|
|
116
|
+
|
|
117
|
+
graph[:nodes].select { |node| selected_node_ids.include?(node[:id]) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def compact_hash(hash, keys)
|
|
121
|
+
keys.each_with_object({}) do |key, result|
|
|
122
|
+
result[key] = hash[key] if hash.key?(key)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -8,7 +8,7 @@ module Docit
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def inject
|
|
11
|
-
return []
|
|
11
|
+
return [] unless initializer_path && File.exist?(initializer_path)
|
|
12
12
|
|
|
13
13
|
content = File.read(initializer_path)
|
|
14
14
|
existing_tags = content.scan(/config\.tag\s+["']([^"']+)["']/).flatten
|
data/lib/docit/ai.rb
CHANGED
data/lib/docit/configuration.rb
CHANGED
|
@@ -5,7 +5,8 @@ module Docit
|
|
|
5
5
|
class Configuration
|
|
6
6
|
SUPPORTED_UIS = %i[scalar swagger].freeze
|
|
7
7
|
|
|
8
|
-
attr_accessor :title, :version, :description, :base_url
|
|
8
|
+
attr_accessor :title, :version, :description, :base_url,
|
|
9
|
+
:system_graph_enabled, :system_graph_excluded_paths
|
|
9
10
|
attr_reader :default_ui
|
|
10
11
|
|
|
11
12
|
def initialize
|
|
@@ -13,10 +14,15 @@ module Docit
|
|
|
13
14
|
@version = "1.0.0"
|
|
14
15
|
@description = "Welcome to the API documentation. Browse the endpoints below to get started."
|
|
15
16
|
@base_url = "/"
|
|
17
|
+
@system_graph_enabled = true
|
|
18
|
+
@system_graph_excluded_paths = []
|
|
16
19
|
@default_ui = :scalar
|
|
17
20
|
@security_schemes = {}
|
|
18
21
|
@tags = []
|
|
19
22
|
@servers = []
|
|
23
|
+
@license = nil
|
|
24
|
+
@contact = nil
|
|
25
|
+
@terms_of_service = nil
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
def default_ui=(value)
|
|
@@ -75,5 +81,35 @@ module Docit
|
|
|
75
81
|
def servers
|
|
76
82
|
@servers.dup
|
|
77
83
|
end
|
|
84
|
+
|
|
85
|
+
def license(name:, url: nil)
|
|
86
|
+
entry = { name: name }
|
|
87
|
+
entry[:url] = url if url
|
|
88
|
+
@license = entry
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def license_info
|
|
92
|
+
@license&.dup
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def contact(name: nil, email: nil, url: nil)
|
|
96
|
+
entry = {}
|
|
97
|
+
entry[:name] = name if name
|
|
98
|
+
entry[:email] = email if email
|
|
99
|
+
entry[:url] = url if url
|
|
100
|
+
@contact = entry
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def contact_info
|
|
104
|
+
@contact&.dup
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def terms_of_service(url)
|
|
108
|
+
@terms_of_service = url
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def terms_of_service_url
|
|
112
|
+
@terms_of_service
|
|
113
|
+
end
|
|
78
114
|
end
|
|
79
115
|
end
|
data/lib/docit/doc_file.rb
CHANGED