assistant 0.0.2 → 1.0.0.rc1

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +13 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +39 -0
  4. data/.github/dependabot.yml +4 -0
  5. data/.github/workflows/ci.yml +140 -0
  6. data/.github/workflows/docs.yml +64 -0
  7. data/.github/workflows/release.yml +46 -0
  8. data/.gitignore +5 -1
  9. data/.markdownlint.json +6 -0
  10. data/.opencode/.gitignore +4 -0
  11. data/.opencode/opencode.json +13 -0
  12. data/.opencode/skills/create-pr/SKILL.md +138 -0
  13. data/.opencode/skills/ruby-services/SKILL.md +81 -0
  14. data/.rubocop.yml +40 -148
  15. data/.ruby-version +1 -1
  16. data/.yardopts +17 -0
  17. data/CHANGELOG.md +434 -0
  18. data/CONTRIBUTING.md +131 -0
  19. data/Gemfile +10 -0
  20. data/Gemfile.lock +264 -94
  21. data/README.md +125 -16
  22. data/Rakefile +53 -3
  23. data/SECURITY.md +50 -0
  24. data/Steepfile +49 -0
  25. data/_config.yml +87 -0
  26. data/assistant.gemspec +33 -20
  27. data/docs/api-reference.md +264 -0
  28. data/docs/changelog.md +26 -0
  29. data/docs/deprecations.md +86 -0
  30. data/docs/examples/cli-handler.md +17 -0
  31. data/docs/examples/composing-services.md +17 -0
  32. data/docs/examples/execute-callbacks.md +17 -0
  33. data/docs/examples/index.md +29 -0
  34. data/docs/examples/instrumentation-notifier.md +17 -0
  35. data/docs/examples/rails-service.md +17 -0
  36. data/docs/examples/rbs-generator.md +17 -0
  37. data/docs/examples/sidekiq-worker.md +17 -0
  38. data/docs/getting-started.md +136 -0
  39. data/docs/guides/composing-services.md +222 -0
  40. data/docs/guides/index.md +25 -0
  41. data/docs/guides/inputs.md +333 -0
  42. data/docs/guides/logging-and-results.md +202 -0
  43. data/docs/guides/rbs-and-types.md +16 -0
  44. data/docs/guides/validation.md +180 -0
  45. data/docs/index.md +69 -0
  46. data/docs/roadmap.md +33 -0
  47. data/exe/assistant-rbs +7 -0
  48. data/lib/assistant/execute_callbacks.rb +103 -0
  49. data/lib/assistant/execute_callbacks.rbs +30 -0
  50. data/lib/assistant/input_builder/accessors.rb +36 -0
  51. data/lib/assistant/input_builder/accessors.rbs +10 -0
  52. data/lib/assistant/input_builder/default_option.rb +41 -0
  53. data/lib/assistant/input_builder/default_option.rbs +11 -0
  54. data/lib/assistant/input_builder/dsl.rb +37 -0
  55. data/lib/assistant/input_builder/dsl.rbs +12 -0
  56. data/lib/assistant/input_builder/optional_option.rb +45 -0
  57. data/lib/assistant/input_builder/optional_option.rbs +10 -0
  58. data/lib/assistant/input_builder/registry.rb +27 -0
  59. data/lib/assistant/input_builder/registry.rbs +13 -0
  60. data/lib/assistant/input_builder/require_validator.rb +104 -0
  61. data/lib/assistant/input_builder/require_validator.rbs +24 -0
  62. data/lib/assistant/input_builder/type_validator.rb +47 -0
  63. data/lib/assistant/input_builder/type_validator.rbs +18 -0
  64. data/lib/assistant/input_builder.rb +28 -0
  65. data/lib/assistant/input_builder.rbs +15 -0
  66. data/lib/assistant/log_item.rb +75 -17
  67. data/lib/assistant/log_item.rbs +40 -0
  68. data/lib/assistant/log_list.rb +44 -12
  69. data/lib/assistant/log_list.rbs +48 -0
  70. data/lib/assistant/rbs_generator/cli.rb +109 -0
  71. data/lib/assistant/rbs_generator/cli.rbs +24 -0
  72. data/lib/assistant/rbs_generator/renderer.rb +67 -0
  73. data/lib/assistant/rbs_generator/renderer.rbs +11 -0
  74. data/lib/assistant/rbs_generator/writer.rb +65 -0
  75. data/lib/assistant/rbs_generator/writer.rbs +24 -0
  76. data/lib/assistant/rbs_generator.rb +38 -0
  77. data/lib/assistant/rbs_generator.rbs +5 -0
  78. data/lib/assistant/refinements/string_blankness.rb +14 -0
  79. data/lib/assistant/refinements/string_blankness.rbs +6 -0
  80. data/lib/assistant/service.rb +328 -8
  81. data/lib/assistant/service.rbs +86 -0
  82. data/lib/assistant/version.rb +5 -1
  83. data/lib/assistant/version.rbs +5 -0
  84. data/lib/assistant.rb +53 -4
  85. data/lib/assistant.rbs +25 -0
  86. data/mise.toml +6 -0
  87. data/sig/examples/greeter.rbs +14 -0
  88. metadata +128 -112
  89. data/.circleci/config.yml +0 -45
  90. data/.fasterer.yml +0 -19
  91. data/.rspec +0 -3
  92. data/.rubocop_todo.yml +0 -0
@@ -0,0 +1,29 @@
1
+ ---
2
+ title: Examples
3
+ nav_order: 5
4
+ has_children: true
5
+ permalink: /examples/
6
+ ---
7
+
8
+ # Examples
9
+
10
+ > **Status:** gallery scaffolding — each entry below ships with a
11
+ > runnable script under `examples/<slug>/`, a writeup on this site,
12
+ > and a regression test. See
13
+ > [P6–P12 of the GitHub Pages plan](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example)
14
+ > for the per-example schedule.
15
+
16
+ | Example | Demonstrates |
17
+ | --- | --- |
18
+ | [Rails service](rails-service.md) | Rails-shaped controller; `case service.run in { result:, status: :ok }`. |
19
+ | [CLI handler](cli-handler.md) | `OptionParser` driving a service; exit code derived from `#status`. |
20
+ | [Sidekiq worker](sidekiq-worker.md) | Worker class that runs a service; idempotent; logs warnings vs errors separately. |
21
+ | [Composing services](composing-services.md) | Outer service uses `call_service` to chain two inner services; log timeline merging. |
22
+ | [Execute callbacks](execute-callbacks.md) | `before_execute` audit logger; `around_execute` timing wrapper; failure cases. |
23
+ | [Instrumentation notifier](instrumentation-notifier.md) | `Assistant.notifier=` wired to a fake `ActiveSupport::Notifications`-shaped sink. |
24
+ | [RBS generator](rbs-generator.md) | Service definition → `bin/assistant-rbs --output sig` → Steep proving per-input return types. |
25
+
26
+ Each example is intentionally small enough to be read in one sitting.
27
+ Source for every script lives under
28
+ [`examples/`](https://github.com/ramongr/assistant/tree/main/examples)
29
+ in the repository.
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Instrumentation notifier
3
+ parent: Examples
4
+ nav_order: 6
5
+ ---
6
+
7
+ # Instrumentation notifier
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P11](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example) of
11
+ > the GitHub Pages plan.
12
+
13
+ `Assistant.notifier=` wired to a fake `ActiveSupport::Notifications`-shaped sink.
14
+
15
+ When the runnable script under `examples/instrumentation_notifier/` lands, this
16
+ page will include it verbatim via Jekyll `include_relative` so the
17
+ prose stays in lockstep with the code.
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Rails service
3
+ parent: Examples
4
+ nav_order: 1
5
+ ---
6
+
7
+ # Rails service
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P6](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example) of
11
+ > the GitHub Pages plan.
12
+
13
+ A Rails-shaped controller calling a service and pattern-matching on the result hash.
14
+
15
+ When the runnable script under `examples/rails_service/` lands, this
16
+ page will include it verbatim via Jekyll `include_relative` so the
17
+ prose stays in lockstep with the code.
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: RBS generator
3
+ parent: Examples
4
+ nav_order: 7
5
+ ---
6
+
7
+ # RBS generator
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P12](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example) of
11
+ > the GitHub Pages plan.
12
+
13
+ Service definition → `bin/assistant-rbs --output sig` → Steep proving the per-input return type.
14
+
15
+ When the runnable script under `examples/rbs_generator/` lands, this
16
+ page will include it verbatim via Jekyll `include_relative` so the
17
+ prose stays in lockstep with the code.
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Sidekiq worker
3
+ parent: Examples
4
+ nav_order: 3
5
+ ---
6
+
7
+ # Sidekiq worker
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P8](https://github.com/ramongr/assistant/blob/main/docs/v1/08-github-pages.md#p6p12-examples-one-pr-per-example) of
11
+ > the GitHub Pages plan.
12
+
13
+ A background job that runs a service idempotently, logging warnings vs errors separately.
14
+
15
+ When the runnable script under `examples/sidekiq_worker/` lands, this
16
+ page will include it verbatim via Jekyll `include_relative` so the
17
+ prose stays in lockstep with the code.
@@ -0,0 +1,136 @@
1
+ ---
2
+ title: Getting started
3
+ nav_order: 1
4
+ ---
5
+
6
+ <!-- markdownlint-disable MD013 MD024 -->
7
+ # Getting started
8
+
9
+ > **TL;DR** — Subclass `Assistant::Service`, declare your inputs with
10
+ > `input`, write an `execute` body, and call `YourService.run(**args)`.
11
+ > You always get a hash back: either
12
+ > `{ result:, status: :ok | :with_warnings, warnings: [...] }` or
13
+ > `{ result: nil, status: :with_errors, errors: [...] }`. The gem
14
+ > never raises for expected failures.
15
+
16
+ This page walks a brand-new user from `gem install` to a working
17
+ service. For deeper topics, jump to the guides:
18
+
19
+ - [Inputs](./guides/inputs.md) — every option on `input` and `inputs`.
20
+ - [Validation](./guides/validation.md) — the `validate` hook and how
21
+ to log warnings vs. errors.
22
+ - [Logging and results](./guides/logging-and-results.md) — `LogItem`,
23
+ the `log_item_*` shorthands, the result hash.
24
+ - [Composing services](./guides/composing-services.md) — `call_service`,
25
+ callbacks, the instrumentation notifier, `#input_snapshot`.
26
+
27
+ The API contract is enumerated in
28
+ [`api-reference.md`](./api-reference.md).
29
+
30
+ ## Install
31
+
32
+ With Bundler (recommended):
33
+
34
+ ```sh
35
+ bundle add assistant
36
+ ```
37
+
38
+ Without Bundler:
39
+
40
+ ```sh
41
+ gem install assistant
42
+ ```
43
+
44
+ Ruby `>= 3.4` is required. The gem has **zero runtime dependencies** —
45
+ adding it never pulls in another gem.
46
+
47
+ ## Your first service
48
+
49
+ Create a file `create_user.rb`:
50
+
51
+ ```ruby
52
+ require 'assistant'
53
+
54
+ class CreateUser < Assistant::Service
55
+ input :email, type: String, required: true
56
+ input :name, type: String, required: true
57
+ input :age, type: Integer, allow_nil: true, default: nil
58
+
59
+ def validate
60
+ return if email.include?('@')
61
+
62
+ log_item_error(source: :validate, detail: :email, message: 'must contain @')
63
+ end
64
+
65
+ def execute
66
+ log_item_warning(source: :execute, detail: :age, message: 'age missing') if age.nil?
67
+
68
+ { id: 42, email:, name:, age: }
69
+ end
70
+ end
71
+ ```
72
+
73
+ Run it:
74
+
75
+ ```ruby
76
+ CreateUser.run(email: 'a@b.com', name: 'Alice')
77
+ # => { result: { id: 42, email: "a@b.com", name: "Alice", age: nil },
78
+ # status: :with_warnings,
79
+ # warnings: [#<Assistant::LogItem level=:warning ...>] }
80
+
81
+ CreateUser.run(email: 'oops', name: 'Bob', age: 30)
82
+ # => { result: nil,
83
+ # status: :with_errors,
84
+ # errors: [#<Assistant::LogItem level=:error ...>,
85
+ # #<Assistant::LogItem level=:error ...>] }
86
+
87
+ CreateUser.run(email: 'c@d.com', name: 'Carol', age: 30)
88
+ # => { result: { id: 42, ... }, status: :ok, warnings: [] }
89
+ ```
90
+
91
+ Three runs, three different statuses, **zero exceptions**.
92
+
93
+ ## Reading the result hash
94
+
95
+ Every `Service.run` returns one of two shapes:
96
+
97
+ ```ruby
98
+ # Success (status is :ok or :with_warnings)
99
+ { result: <Object>, status: :ok | :with_warnings, warnings: Array<LogItem> }
100
+
101
+ # Failure
102
+ { result: nil, status: :with_errors, errors: Array<LogItem> }
103
+ ```
104
+
105
+ Pattern-matching is the cleanest way to consume it:
106
+
107
+ ```ruby
108
+ case CreateUser.run(email: 'a@b.com', name: 'Alice')
109
+ in { result:, status: :ok }
110
+ render json: result
111
+ in { result:, status: :with_warnings, warnings: }
112
+ WarningsLogger.log(warnings)
113
+ render json: result
114
+ in { errors:, status: :with_errors }
115
+ render json: { errors: errors.map(&:item) }, status: :unprocessable_entity
116
+ end
117
+ ```
118
+
119
+ The `:status` value is exhaustively one of `:ok`, `:with_warnings`,
120
+ `:with_errors`. No new status values can be introduced in 1.x without a
121
+ deprecation cycle.
122
+
123
+ ## What's next
124
+
125
+ - The same example, but with optional / multi-type inputs and a
126
+ `default:` lambda → [Inputs guide](./guides/inputs.md).
127
+ - Logging structured warnings during `#execute`, conditional
128
+ validation, the strict `LogItem` constructor →
129
+ [Logging and results](./guides/logging-and-results.md) +
130
+ [Validation](./guides/validation.md).
131
+ - Wrapping one service inside another with shared log merging →
132
+ [Composing services](./guides/composing-services.md).
133
+ - Per-class RBS signatures for Steep users → run
134
+ `bundle exec assistant-rbs lib --output sig`. See the
135
+ [Inputs guide](./guides/inputs.md) for the limitation it works
136
+ around.
@@ -0,0 +1,222 @@
1
+ ---
2
+ title: Composing services
3
+ parent: Guides
4
+ nav_order: 4
5
+ ---
6
+
7
+ <!-- markdownlint-disable MD013 MD024 -->
8
+ # Composing services
9
+
10
+ > **TL;DR** — Use `call_service(OtherService, **inputs)` to nest one
11
+ > service inside another's `#execute`; logs from the inner service
12
+ > are merged into the outer timeline automatically. Use
13
+ > `before_execute` / `after_execute` / `around_execute` to share
14
+ > cross-cutting concerns at the class level. Register
15
+ > `Assistant.notifier =` once to instrument every service. Use
16
+ > `#input_snapshot` to forward a read-only view of the inputs to a
17
+ > collaborator.
18
+
19
+ This guide covers the four composition surfaces shipped in 1.0:
20
+ service-to-service calls (M-S2), execute callbacks (M-S1), the
21
+ instrumentation notifier (M-S3), and the input snapshot (M-S4).
22
+
23
+ ## `#call_service`: nest one service inside another
24
+
25
+ `call_service(klass, **inputs)` instantiates `klass`, runs it, merges
26
+ its `#logs` into the outer service's timeline, and returns the inner
27
+ service instance. It does **not** raise on inner failure — the
28
+ outer `#execute` decides what to do based on `inner.success?` /
29
+ `inner.failure?` / `inner.result`.
30
+
31
+ ```ruby
32
+ class CreateUser < Assistant::Service
33
+ input :email, type: String, required: true
34
+ def execute = { id: 1, email: }
35
+ end
36
+
37
+ class SignUp < Assistant::Service
38
+ input :email, type: String, required: true
39
+
40
+ def execute
41
+ user = call_service(CreateUser, email:)
42
+ return if user.failure? # inner errors already on the outer timeline
43
+
44
+ { user: user.result, signed_up_at: Time.now }
45
+ end
46
+ end
47
+
48
+ result = SignUp.run(email: 'a@b.com')
49
+ result.fetch(:status) # => :ok
50
+ result.fetch(:result)[:user][:id] # => 1
51
+ ```
52
+
53
+ Notes:
54
+
55
+ - `klass` must be a subclass of `Assistant::Service`. Anything else
56
+ raises `ArgumentError`.
57
+ - The inner service's `#logs` are appended to the outer service's
58
+ timeline via `merge_logs(logs: inner.logs)`. Because the outer
59
+ service's `errors` / `warnings` / `status` are derived from its
60
+ full `@logs`, inner errors **automatically** downgrade the outer
61
+ terminal status to `:with_errors`, and inner warnings surface as
62
+ `:with_warnings` — no special handling required.
63
+ - `call_service` does **not** rescue exceptions raised by the inner
64
+ service's `#execute` (or by the configured `Assistant.notifier`).
65
+ Wrap in `begin/rescue` and record via `add_log(level: :error, ...)`
66
+ if the inner service may raise.
67
+ - `call_service` always calls `inner.run`, so calling it twice would
68
+ re-execute the inner service.
69
+
70
+ ## Execute callbacks (M-S1)
71
+
72
+ Three class-level DSL methods register callbacks around `#execute`.
73
+ Hooks are evaluated in the context of the service instance.
74
+
75
+ ```ruby
76
+ class CreateUser < Assistant::Service
77
+ input :email, type: String, required: true
78
+
79
+ before_execute do
80
+ log_item_info(source: :hook, detail: :before, message: "starting #{email}")
81
+ end
82
+
83
+ around_execute do |&blk|
84
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
85
+ value = blk.call
86
+ log_item_info(source: :hook, detail: :around,
87
+ message: "took #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - started}s")
88
+ value
89
+ end
90
+
91
+ after_execute do |result|
92
+ log_item_info(source: :hook, detail: :after, message: "result=#{result.inspect}")
93
+ end
94
+
95
+ def execute = { id: 1, email: }
96
+ end
97
+ ```
98
+
99
+ Behavior:
100
+
101
+ - **Order:** `before_execute` hooks run in declaration order; then
102
+ `around_execute` hooks wrap the chain with the **first-declared**
103
+ hook as the outermost layer; finally `after_execute` hooks run
104
+ with the execute result as the single positional argument.
105
+ - **Inheritance:** subclasses inherit a `dup` of each parent's hook
106
+ arrays. Adding hooks in a subclass does not affect the parent.
107
+ - **Exceptions:** a `StandardError` raised inside any hook is logged
108
+ with `level: :error, source: :hook, detail: <hook_type>` and the
109
+ remaining hooks still fire. An `around_execute` hook that raises
110
+ before yielding to its continuation produces `nil` for that layer
111
+ and outer hooks still wrap normally.
112
+ - **Missing block:** registering a hook without a block raises
113
+ `ArgumentError` at class-definition time.
114
+
115
+ ## Instrumentation notifier (M-S3)
116
+
117
+ Assign a callable to `Assistant.notifier =` to receive a fixed set
118
+ of events for **every** service execution:
119
+
120
+ ```ruby
121
+ Assistant.notifier = lambda do |event, payload|
122
+ StatsD.increment("assistant.#{event}",
123
+ tags: ["service:#{payload[:service_class]}"])
124
+ StatsD.timing("assistant.duration_s.#{event}",
125
+ payload[:duration_s] * 1000.0,
126
+ tags: ["service:#{payload[:service_class]}"])
127
+ end
128
+ ```
129
+
130
+ Events (frozen for 1.0):
131
+
132
+ | Event | When |
133
+ |----------------------|-------------------------------------------------|
134
+ | `:service_started` | Top of `#run`, before any validation. |
135
+ | `:service_validated` | After declarative + `#validate` checks pass. |
136
+ | `:service_executed` | Success path — after `#execute` returns. |
137
+ | `:service_failed` | Failure path — `#execute` was skipped. |
138
+
139
+ Payload always includes:
140
+
141
+ - `:service_class` — the `Service` subclass.
142
+ - `:duration_s` — Float seconds since the start of `#run`
143
+ (`Process::CLOCK_MONOTONIC`).
144
+
145
+ The notifier is treated as untrusted infrastructure: any
146
+ `StandardError` it raises is rescued and warned (`Kernel.warn`),
147
+ so a misconfigured notifier cannot tear down every service in the
148
+ process. `SystemExit` / `Interrupt` propagate.
149
+
150
+ To disable instrumentation entirely, restore the default no-op:
151
+
152
+ ```ruby
153
+ Assistant.notifier = Assistant::DEFAULT_NOTIFIER
154
+ ```
155
+
156
+ ## `#input_snapshot` (M-S4)
157
+
158
+ `#input_snapshot` returns a read-only `Data` view of the service's
159
+ declared inputs (post-`default:` / post-`allow_nil:`). It's the
160
+ canonical way to hand a value object to a collaborator without
161
+ exposing the full service instance:
162
+
163
+ ```ruby
164
+ class CreateUser < Assistant::Service
165
+ input :email, type: String, required: true
166
+ input :role, type: Symbol, default: :member
167
+
168
+ def execute
169
+ Mailer.welcome(input_snapshot)
170
+ { id: 1, **input_snapshot.to_h }
171
+ end
172
+ end
173
+
174
+ snapshot = CreateUser.new(email: 'a@b.com').tap(&:run).input_snapshot
175
+ snapshot.email # => "a@b.com"
176
+ snapshot.role # => :member
177
+ snapshot.to_h # => { email: "a@b.com", role: :member }
178
+
179
+ snapshot.email = 'x' # => NoMethodError (Data is structurally immutable)
180
+ ```
181
+
182
+ Notes:
183
+
184
+ - Members are exactly the keys of `input_definitions` — extra keyword
185
+ arguments to `#initialize` that have no `input` declaration are
186
+ excluded so the snapshot mirrors the public DSL.
187
+ - A declared input with no default and no caller-supplied value
188
+ appears with `nil`, mirroring the per-input getter.
189
+ - The snapshot class is memoized at the class level via
190
+ `Service.input_snapshot_class`, so repeated calls are cheap.
191
+ - `Data` is structurally immutable. Mutable member values (an
192
+ `Array` passed as an input, say) keep their normal mutability —
193
+ the snapshot does not deep-freeze.
194
+
195
+ ## Common pitfalls
196
+
197
+ - **Forgetting that `call_service` doesn't fail the outer service.**
198
+ If the inner failure should also fail the outer, check
199
+ `inner.failure?` (or `inner.errors.any?`) and call
200
+ `log_item_error(...)` in your `#execute`.
201
+ - **Re-running `inner` after `call_service`.** `inner.run` was
202
+ already called; calling it again will re-execute and double-log.
203
+ - **Mutating `#input_snapshot` member values.** They share identity
204
+ with the underlying inputs. Treat the snapshot as a view, not a
205
+ defensive copy.
206
+ - **Putting validation in `before_execute`.** Use `#validate` — it
207
+ short-circuits `#execute` on errors. Hooks log but don't change
208
+ flow.
209
+ - **Raising from a notifier.** It's swallowed (with a `warn`).
210
+ Non-`StandardError` exceptions still propagate, so don't use
211
+ `Kernel#exit`.
212
+
213
+ ## See also
214
+
215
+ - [Inputs guide](./inputs.md) — `Service.input` / `Service.inputs`
216
+ and `#input_snapshot`.
217
+ - [Validation guide](./validation.md) — `#validate`, conditional
218
+ requirements.
219
+ - [Logging and results](./logging-and-results.md) — what
220
+ `merge_logs(logs:)` does, the result hash shape.
221
+ - [API reference](../api-reference.md#assistantservice) for the
222
+ full callback / call_service / notifier surfaces.
@@ -0,0 +1,25 @@
1
+ ---
2
+ title: Guides
3
+ nav_order: 2
4
+ has_children: true
5
+ permalink: /guides/
6
+ ---
7
+
8
+ # Guides
9
+
10
+ Topic-focused walkthroughs of every part of the Assistant DSL.
11
+ Each guide opens with a TL;DR, ships runnable examples mirrored by
12
+ tests under `test/docs/<guide>_examples_test.rb`, and ends with a
13
+ "common pitfalls" + "see also" section.
14
+
15
+ - [Inputs](./inputs.md) — `input`/`inputs` DSL, `type:`, `required:`,
16
+ `default:`, `allow_nil:`, `optional:`, `if:` conditional requirement,
17
+ and the `assistant-rbs` Steep recipe.
18
+ - [Validation](./validation.md) — auto-checks, `#validate`, warnings
19
+ vs errors, conditional requirements, strict `LogItem.new` (M10).
20
+ - [Logging and results](./logging-and-results.md) — `LogItem`,
21
+ levels, `log_item_*` shorthands, `merge_logs`, the result hash.
22
+ - [Composing services](./composing-services.md) — `call_service`,
23
+ callbacks, the notifier, `#input_snapshot`.
24
+ - [RBS and types](./rbs-and-types.md) — per-class generator, the R1
25
+ metaprogramming limitation, Steep CI hookup.