assistant 0.1.0 → 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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +39 -0
  3. data/.github/workflows/ci.yml +99 -0
  4. data/.github/workflows/docs.yml +64 -0
  5. data/.github/workflows/release.yml +1 -1
  6. data/.gitignore +5 -1
  7. data/.opencode/.gitignore +4 -0
  8. data/.opencode/opencode.json +13 -0
  9. data/.opencode/skills/create-pr/SKILL.md +138 -0
  10. data/.opencode/skills/ruby-services/SKILL.md +81 -0
  11. data/.rubocop.yml +14 -4
  12. data/.yardopts +17 -0
  13. data/CHANGELOG.md +378 -0
  14. data/CONTRIBUTING.md +131 -0
  15. data/Gemfile +10 -0
  16. data/Gemfile.lock +196 -29
  17. data/README.md +125 -16
  18. data/Rakefile +45 -0
  19. data/SECURITY.md +50 -0
  20. data/Steepfile +49 -0
  21. data/_config.yml +87 -0
  22. data/assistant.gemspec +24 -7
  23. data/docs/api-reference.md +264 -0
  24. data/docs/changelog.md +26 -0
  25. data/docs/deprecations.md +86 -0
  26. data/docs/examples/cli-handler.md +17 -0
  27. data/docs/examples/composing-services.md +17 -0
  28. data/docs/examples/execute-callbacks.md +17 -0
  29. data/docs/examples/index.md +29 -0
  30. data/docs/examples/instrumentation-notifier.md +17 -0
  31. data/docs/examples/rails-service.md +17 -0
  32. data/docs/examples/rbs-generator.md +17 -0
  33. data/docs/examples/sidekiq-worker.md +17 -0
  34. data/docs/getting-started.md +136 -0
  35. data/docs/guides/composing-services.md +222 -0
  36. data/docs/guides/index.md +25 -0
  37. data/docs/guides/inputs.md +333 -0
  38. data/docs/guides/logging-and-results.md +202 -0
  39. data/docs/guides/rbs-and-types.md +16 -0
  40. data/docs/guides/validation.md +180 -0
  41. data/docs/index.md +69 -0
  42. data/docs/roadmap.md +33 -0
  43. data/exe/assistant-rbs +7 -0
  44. data/lib/assistant/execute_callbacks.rb +103 -0
  45. data/lib/assistant/execute_callbacks.rbs +30 -0
  46. data/lib/assistant/input_builder/accessors.rb +36 -0
  47. data/lib/assistant/input_builder/accessors.rbs +10 -0
  48. data/lib/assistant/input_builder/default_option.rb +41 -0
  49. data/lib/assistant/input_builder/default_option.rbs +11 -0
  50. data/lib/assistant/input_builder/dsl.rb +37 -0
  51. data/lib/assistant/input_builder/dsl.rbs +12 -0
  52. data/lib/assistant/input_builder/optional_option.rb +45 -0
  53. data/lib/assistant/input_builder/optional_option.rbs +10 -0
  54. data/lib/assistant/input_builder/registry.rb +27 -0
  55. data/lib/assistant/input_builder/registry.rbs +13 -0
  56. data/lib/assistant/input_builder/require_validator.rb +104 -0
  57. data/lib/assistant/input_builder/require_validator.rbs +24 -0
  58. data/lib/assistant/input_builder/type_validator.rb +47 -0
  59. data/lib/assistant/input_builder/type_validator.rbs +18 -0
  60. data/lib/assistant/input_builder.rb +25 -81
  61. data/lib/assistant/input_builder.rbs +15 -0
  62. data/lib/assistant/log_item.rb +74 -16
  63. data/lib/assistant/log_item.rbs +40 -0
  64. data/lib/assistant/log_list.rb +43 -17
  65. data/lib/assistant/log_list.rbs +48 -0
  66. data/lib/assistant/rbs_generator/cli.rb +109 -0
  67. data/lib/assistant/rbs_generator/cli.rbs +24 -0
  68. data/lib/assistant/rbs_generator/renderer.rb +67 -0
  69. data/lib/assistant/rbs_generator/renderer.rbs +11 -0
  70. data/lib/assistant/rbs_generator/writer.rb +65 -0
  71. data/lib/assistant/rbs_generator/writer.rbs +24 -0
  72. data/lib/assistant/rbs_generator.rb +38 -0
  73. data/lib/assistant/rbs_generator.rbs +5 -0
  74. data/lib/assistant/refinements/string_blankness.rb +9 -13
  75. data/lib/assistant/refinements/string_blankness.rbs +6 -0
  76. data/lib/assistant/service.rb +300 -11
  77. data/lib/assistant/service.rbs +82 -1
  78. data/lib/assistant/version.rb +5 -1
  79. data/lib/assistant/version.rbs +5 -0
  80. data/lib/assistant.rb +54 -4
  81. data/lib/assistant.rbs +25 -0
  82. data/mise.toml +2 -0
  83. data/sig/examples/greeter.rbs +14 -0
  84. metadata +142 -38
  85. data/.fasterer.yml +0 -19
  86. data/.rubocop_todo.yml +0 -7
data/_config.yml ADDED
@@ -0,0 +1,87 @@
1
+ # Site configuration for the Assistant docs site, deployed to
2
+ # https://ramongr.github.io/assistant/ via .github/workflows/docs.yml.
3
+ # Toolchain: Jekyll 4 + just-the-docs (Ruby). Source files live under
4
+ # docs/. The v1 planning corpus under docs/v1/ is excluded — those are
5
+ # authored-for-GitHub-render plan docs, not site pages.
6
+
7
+ title: Assistant
8
+ description: >-
9
+ Tiny, dependency-free soft-fail service objects for Ruby. Uniform result
10
+ hash, RBS signatures, 1.0-frozen public API, zero runtime gem dependencies.
11
+ url: https://ramongr.github.io
12
+ baseurl: /assistant
13
+ repository: ramongr/assistant
14
+
15
+ # Source layout
16
+ source: docs
17
+ destination: _site
18
+
19
+ # Theme
20
+ theme: just-the-docs
21
+ color_scheme: light
22
+
23
+ # Search
24
+ search_enabled: true
25
+ search:
26
+ heading_level: 3
27
+ previews: 3
28
+ preview_words_before: 5
29
+ preview_words_after: 10
30
+ tokenizer_separator: /[\s/]+/
31
+ rel_url: true
32
+ button: true
33
+ focus_shortcut_key: 'k'
34
+
35
+ # Navigation
36
+ nav_enabled: true
37
+ heading_anchors: true
38
+
39
+ # Aux links (top-right)
40
+ aux_links:
41
+ View on GitHub:
42
+ - https://github.com/ramongr/assistant
43
+ RubyGems:
44
+ - https://rubygems.org/gems/assistant
45
+ aux_links_new_tab: true
46
+
47
+ # Footer
48
+ gh_edit_link: true
49
+ gh_edit_link_text: Edit this page on GitHub
50
+ gh_edit_repository: https://github.com/ramongr/assistant
51
+ gh_edit_branch: main
52
+ gh_edit_source: docs
53
+ gh_edit_view_mode: tree
54
+
55
+ back_to_top: true
56
+ back_to_top_text: Back to top
57
+
58
+ # Build settings
59
+ markdown: kramdown
60
+ kramdown:
61
+ syntax_highlighter: rouge
62
+ input: GFM
63
+ hard_wrap: false
64
+
65
+ # Files Jekyll should ignore. Paths are relative to `source:` (docs/),
66
+ # so `v1` skips docs/v1/ — the v1 planning corpus authored for GitHub's
67
+ # Markdown renderer (relative links into ../ and across plan files). It
68
+ # is intentionally NOT a site route.
69
+ exclude:
70
+ - v1
71
+ - README.md
72
+
73
+ # Jekyll plugins (the GitHub Pages workflow installs these via Bundler;
74
+ # we are not using github-pages gem, so plugins must be safe-listed too)
75
+ plugins:
76
+ - jekyll-seo-tag
77
+ - jekyll-relative-links
78
+
79
+ # Rewrite intra-docs Markdown links (foo.md) to Jekyll URLs so the
80
+ # same files render both on GitHub (where authors keep .md links for
81
+ # context) and on the site (where .html is needed).
82
+ relative_links:
83
+ enabled: true
84
+ collections: false
85
+
86
+ # just-the-docs strict mode is off because the v1 plan docs use
87
+ # kramdown-incompatible link anchors; this site doesn't include them.
data/assistant.gemspec CHANGED
@@ -13,35 +13,52 @@ Gem::Specification.new do |spec|
13
13
  spec.authors = ['Ramon Rodrigues']
14
14
  spec.email = ['cerberus.ramon@gmail.com']
15
15
 
16
- spec.summary = 'Simple, soft fail enabled, composable services'
17
- spec.description = 'Simple, composable services'
16
+ spec.summary = 'Tiny, dependency-free soft-fail service objects for Ruby'
17
+ spec.description = <<~DESC
18
+ Assistant is a tiny, dependency-free Ruby library for writing soft-fail,
19
+ composable service objects. A service declares its inputs, validates
20
+ them, runs its body, and returns a uniform result that always carries
21
+ either a value plus warnings or a list of errors — never raising for
22
+ expected failures. Ships with RBS signatures, a 1.0-frozen public API,
23
+ and zero runtime gem dependencies.
24
+ DESC
18
25
  spec.homepage = 'https://github.com/ramongr/assistant'
19
26
  spec.license = 'MIT'
20
27
  spec.required_ruby_version = '>= 3.4'
21
28
 
22
29
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
30
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/ramongr/assistant/issues'
23
31
  spec.metadata['changelog_uri'] = 'https://github.com/ramongr/assistant/blob/main/CHANGELOG.md'
32
+ spec.metadata['documentation_uri'] = 'https://rubydoc.info/gems/assistant'
24
33
  spec.metadata['homepage_uri'] = 'https://github.com/ramongr/assistant'
25
34
  spec.metadata['rubygems_mfa_required'] = 'true'
26
35
  spec.metadata['source_code_uri'] = 'https://github.com/ramongr/assistant'
27
36
 
28
37
  # Specify which files should be added to the gem when it is released.
29
38
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
39
+ # Internal planning docs (`docs/v1/`, `docs/v1.x/`) and runnable examples
40
+ # are excluded from the packaged gem (Q9 decision).
30
41
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
31
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
42
+ `git ls-files -z`.split("\x0").reject do |f|
43
+ f.match(%r{^(test|spec|features|examples|docs/v1(\.x)?)/})
44
+ end
32
45
  end
33
46
 
47
+ spec.bindir = 'exe'
48
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
49
  spec.require_paths = 'lib'
35
50
 
36
- spec.add_development_dependency 'brakeman', '~> 8.0'
37
51
  spec.add_development_dependency 'bundler', '~> 4.0'
38
52
  spec.add_development_dependency 'byebug', '~> 13.0'
39
53
  spec.add_development_dependency 'colorize', '~> 1.1'
40
- spec.add_development_dependency 'fasterer', '~> 0.11.0'
41
- spec.add_development_dependency 'minitest', '~> 5.25'
54
+ spec.add_development_dependency 'minitest', '~> 6.0'
42
55
  spec.add_development_dependency 'rake', '~> 13.4'
43
- spec.add_development_dependency 'rubocop', '~> 1.86'
56
+ spec.add_development_dependency 'rubocop', '~> 1.86', '>= 1.86.2'
44
57
  spec.add_development_dependency 'rubocop-minitest', '~> 0.39'
45
58
  spec.add_development_dependency 'rubocop-performance', '~> 1.26'
46
59
  spec.add_development_dependency 'rubocop-rake', '~> 0.7'
60
+ spec.add_development_dependency 'rubocop-style-compact_nesting', '~> 0.1'
61
+ spec.add_development_dependency 'simplecov', '~> 0.22'
62
+ spec.add_development_dependency 'steep', '~> 2.0'
63
+ spec.add_development_dependency 'yard', '~> 0.9'
47
64
  end
@@ -0,0 +1,264 @@
1
+ ---
2
+ title: API reference
3
+ nav_order: 3
4
+ ---
5
+
6
+ <!-- markdownlint-disable MD013 MD024 -->
7
+ # API reference
8
+
9
+ This is the hand-written, curated reference for every public symbol
10
+ that ships in `assistant` 1.0.0. The source of truth for stability
11
+ labels is
12
+ [`docs/v1/01-api-surface.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/01-api-surface.md); the table
13
+ of contents here mirrors it.
14
+
15
+ Anything not listed on this page is **internal** and may change
16
+ without a major version bump.
17
+
18
+ ## Stability labels
19
+
20
+ - **Frozen** — covered by semver from 1.0.0 onward. Breaking changes
21
+ require a 2.0.
22
+ - **Experimental** — public but subject to change in a 1.x minor
23
+ with a deprecation cycle.
24
+
25
+ ## Table of contents
26
+
27
+ - [`Assistant` module](#assistant-module)
28
+ - [`Assistant::Service`](#assistantservice)
29
+ - [Class methods](#class-methods)
30
+ - [Instance methods](#instance-methods)
31
+ - [Generated per-input methods](#generated-per-input-methods)
32
+ - [Result shape](#result-shape)
33
+ - [`Assistant::LogItem`](#assistantlogitem)
34
+ - [`Assistant::LogList`](#assistantloglist)
35
+ - [Execute callbacks](#execute-callbacks)
36
+ - [Service composition](#service-composition)
37
+ - [Instrumentation notifier](#instrumentation-notifier)
38
+ - [Input snapshot](#input-snapshot)
39
+ - [`assistant-rbs` CLI](#assistant-rbs-cli)
40
+ - [Semver and deprecation policy](#semver-and-deprecation-policy)
41
+
42
+ ---
43
+
44
+ ## `Assistant` module
45
+
46
+ | Symbol | Stability | Description |
47
+ |---------------------------------|--------------------------|------------------------------------------------------------------------------------------|
48
+ | `Assistant::VERSION` | Frozen | Semver-compliant `String`. Defined in `lib/assistant/version.rb`. |
49
+ | `Assistant.notifier` | Frozen *(new in 1.0)* | Reader for the instrumentation proc. Defaults to a no-op. |
50
+ | `Assistant.notifier=(callable)` | Frozen *(new in 1.0)* | Writer. Accepts any object responding to `#call(event, payload)`. |
51
+
52
+ See [Instrumentation notifier](#instrumentation-notifier) below for the
53
+ event catalogue.
54
+
55
+ ---
56
+
57
+ ## `Assistant::Service`
58
+
59
+ Subclass `Assistant::Service` and override `#execute` (and optionally
60
+ `#validate`). Every other method on the table below is provided for
61
+ you.
62
+
63
+ ### Class methods
64
+
65
+ | Signature | Stability | Description |
66
+ |---------------------------------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
67
+ | `Service.run(**inputs) -> Hash` | Frozen | One-shot. Instantiates with `inputs`, calls `#run`, returns the result hash. |
68
+ | `Service.input(name, type:, required: false, optional: nil, if: nil, default: nil, allow_nil: false)` | Frozen | Declarative input. `name` is a leading positional symbol; everything else is keyword. See [Inputs](./guides/inputs.md). |
69
+ | `Service.inputs(names, type:, **options)` | Frozen | Bulk declaration. `names` is an `Array<Symbol>`. Options apply to every input. |
70
+ | `Service.before_execute(&block)` | Frozen | Hook block runs after validation, before `#execute`. `instance_exec`'d on the service. |
71
+ | `Service.after_execute(&block)` | Frozen | Hook block runs after `#execute` returns; receives the result as the block argument. |
72
+ | `Service.around_execute(&block)` | Frozen | Hook block is `instance_exec`'d with an inner block argument that yields to the next layer. |
73
+ | `Service.input_snapshot_class -> Class` | Frozen *(new in 1.0)* | Memoised `Data.define(*declared_input_names)` class used by `#input_snapshot`. |
74
+
75
+ > **M12 keyword-only sweep.** Every other public DSL helper (e.g.
76
+ > `merge_logs`, all `InputBuilder` internals) takes keyword arguments
77
+ > only. The two exemptions above — `Service.input` and
78
+ > `Service.inputs` — keep their leading positional `name` / `names`
79
+ > because the class-body declaration reads better as
80
+ > `input :email, type: String` than `input name: :email, type: String`.
81
+
82
+ ### Instance methods
83
+
84
+ | Signature | Stability | Description |
85
+ |----------------------------------------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------|
86
+ | `#initialize(**inputs)` | Frozen | Stores `inputs` after applying every `default:`. Does **not** run validation or `#execute`. |
87
+ | `#run -> Hash` | Frozen | Runs `#validate`, the `before_execute` chain, `#execute` wrapped in `around_execute`, then `after_execute`. Returns the result hash. |
88
+ | `#result -> Object` | Frozen | Memoised return value of `#execute`. `nil` on failure. |
89
+ | `#success? -> Boolean` | Frozen | True when status is `:ok` or `:with_warnings`. |
90
+ | `#failure? -> Boolean` | Frozen | True when status is `:with_errors`. |
91
+ | `#status -> Symbol` | Frozen | Exactly one of `:ok`, `:with_warnings`, `:with_errors`. |
92
+ | `#logs -> Array<Assistant::LogItem>` | Frozen *(new in 1.0)* | All accumulated log items, in the order they were added. |
93
+ | `#infos / #warnings / #errors` | Frozen | Filtered subsets of `#logs`, by level. |
94
+ | `#call_service(klass, **inputs) -> Service` | Frozen *(new in 1.0)* | Runs another service, merges its logs into this one, returns the inner instance. Errors on the inner flip this service to `:with_errors`. |
95
+ | `#input_snapshot -> Data` | Frozen *(new in 1.0)* | Frozen value object capturing the post-default, post-`allow_nil` inputs. See [Composing services](./guides/composing-services.md). |
96
+ | `#execute -> Object` (override) | Frozen | Your subclass overrides this. Return value becomes `result:`. |
97
+ | `#validate -> void` (override) | Frozen | Optional. Add log items here to short-circuit `#execute`. |
98
+
99
+ ### Generated per-input methods
100
+
101
+ For every `input :name, type: T` declaration, the following are
102
+ generated as instance methods on the service:
103
+
104
+ | Method | When generated | Description |
105
+ |-----------------------------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------|
106
+ | `#name` | Always | Reader for the post-default value of the input. |
107
+ | `#name?` | Always | Present-and-truthy predicate. Whitespace-only strings are treated as missing. |
108
+ | `#valid_type_name? -> Boolean` | Always | True when `name` matches the declared `type:` (or is one of an `Array` type). |
109
+ | `#valid_required_name? -> Boolean` | When `required: true` | True when `name` is present (uses `#name?`). **Canonical name in 1.0.** |
110
+ | `#valid_require_name? -> Boolean` | When `required: true` | **Deprecated in 1.0**, removed in 2.0. Delegates to `#valid_required_name?` with a `Kernel.warn` per call site. |
111
+ | `#valid_required_conditional_name? -> Boolean` | When `required: true` **and** `if:` is supplied | True when the `if:` predicate is truthy **and** `name` is present. |
112
+ | `#valid_require_conditional_name? -> Boolean` | Same | **Deprecated in 1.0**, removed in 2.0. Delegates with a warning. |
113
+
114
+ See [`docs/deprecations.md`](./deprecations.md) for the deprecation
115
+ table and migration recipe.
116
+
117
+ ### Result shape
118
+
119
+ `Service#run` (and therefore `Service.run`) always returns one of two
120
+ hash shapes:
121
+
122
+ ```ruby
123
+ # Success
124
+ { result: <Object>, status: :ok | :with_warnings, warnings: Array<LogItem> }
125
+
126
+ # Failure
127
+ { result: nil, status: :with_errors, errors: Array<LogItem> }
128
+ ```
129
+
130
+ The status enum is exhaustively `:ok`, `:with_warnings`, `:with_errors`.
131
+ No new status values may be added in 1.x without a deprecation cycle.
132
+
133
+ ---
134
+
135
+ ## `Assistant::LogItem`
136
+
137
+ > **Breaking change in 1.0 (M10).** `LogItem.new` now raises
138
+ > `ArgumentError` when any required attribute is invalid. The
139
+ > `#valid?` family is **retained** for introspection, but in normal
140
+ > flows it always returns `true` after a successful `new`.
141
+
142
+ | Signature | Stability |
143
+ |------------------------------------------------------------------------------------------|--------------------------|
144
+ | `LogItem.new(level:, source:, detail:, message:, trace: nil)` | Frozen *(raises in 1.0)* |
145
+ | `#level / #source / #detail / #message / #trace` — readers | Frozen |
146
+ | `#info? / #warning? / #error?` — level predicates | Frozen |
147
+ | `#valid? / #valid_level? / #valid_source? / #valid_detail? / #valid_message?` | Frozen |
148
+ | `#item -> Hash{Symbol => Object}` | Frozen |
149
+ | `Assistant::LogItem::VALID_LEVELS` — `%i[info warning error]` | Frozen |
150
+
151
+ `#level`, `#source`, `#detail` are always `Symbol`; `#message` is
152
+ `String`; `#trace` is `String` or `nil`.
153
+
154
+ ---
155
+
156
+ ## `Assistant::LogList`
157
+
158
+ Mixin module included in `Assistant::Service`. Users don't normally
159
+ include it themselves; the methods are available on any service
160
+ instance.
161
+
162
+ | Signature | Stability |
163
+ |--------------------------------------------------------------------|--------------------------|
164
+ | `#add_log(level:, source:, detail:, message:, trace: nil)` | Frozen |
165
+ | `#merge_logs(logs:)` | Frozen *(keyword-only in 1.0)* |
166
+ | `#log_item_error_initialize(attr_name:, message:)` | Frozen |
167
+ | `#log_item_info(source:, detail:, message:, trace: nil)` | Frozen *(new in 1.0)* |
168
+ | `#log_item_warning(source:, detail:, message:, trace: nil)` | Frozen *(new in 1.0)* |
169
+ | `#log_item_error(source:, detail:, message:, trace: nil)` | Frozen *(new in 1.0)* |
170
+ | `#infos / #warnings / #errors -> Array<LogItem>` | Frozen |
171
+
172
+ See [Logging and results](./guides/logging-and-results.md) for the
173
+ `log_item_*` shorthands vs. the explicit `add_log` form.
174
+
175
+ ---
176
+
177
+ ## Execute callbacks
178
+
179
+ New in 1.0. Class-level DSL on `Assistant::Service`; see the table
180
+ under [Class methods](#class-methods).
181
+
182
+ Hook error semantics: an exception raised inside any `before_execute`,
183
+ `after_execute`, or `around_execute` hook is caught and logged via
184
+ `add_log(level: :error, source: :hook, ...)` — it **never** propagates
185
+ out of `#run`. See [Composing services](./guides/composing-services.md).
186
+
187
+ ---
188
+
189
+ ## Service composition
190
+
191
+ | Signature | Stability |
192
+ |------------------------------------------------------------|--------------------------|
193
+ | `#call_service(klass, **inputs) -> Assistant::Service` | Frozen *(new in 1.0)* |
194
+
195
+ Constructs `klass`, runs it, merges the inner instance's `#logs` into
196
+ the caller's, and returns the inner instance for further inspection.
197
+ If the inner service has any error-level log item, the outer service's
198
+ status becomes `:with_errors`.
199
+
200
+ ---
201
+
202
+ ## Instrumentation notifier
203
+
204
+ Configured via `Assistant.notifier = ->(event, payload) { ... }`.
205
+
206
+ Events emitted in 1.0, each with a payload that always carries
207
+ `:service_class` and `:duration_s`:
208
+
209
+ - `:service_started`
210
+ - `:service_validated`
211
+ - `:service_executed`
212
+ - `:service_failed`
213
+
214
+ The event set is **Frozen** for 1.0. Adding events requires a minor
215
+ release; removing one or removing a payload key requires a full
216
+ deprecation cycle.
217
+
218
+ ---
219
+
220
+ ## Input snapshot
221
+
222
+ | Signature | Stability | Description |
223
+ |------------------------------------|--------------------------|--------------------------------------------------------------------------------------------------------------|
224
+ | `#input_snapshot -> Data` | Frozen *(new in 1.0)* | Returns a frozen `Data.define(*declared_input_names).new(**post_default_inputs)`. Reflects post-`default:` / post-`allow_nil:` values. |
225
+
226
+ The `Data` class is memoised on the service class — repeated calls
227
+ return values whose `class` is `equal?`. See
228
+ [Composing services](./guides/composing-services.md).
229
+
230
+ ---
231
+
232
+ ## `assistant-rbs` CLI
233
+
234
+ Bundled executable shipped at `exe/assistant-rbs` (M11). Generates
235
+ per-class RBS signatures for `Assistant::Service` subclasses so Steep
236
+ can type-check user code.
237
+
238
+ | Invocation | Stability |
239
+ |-------------------------------------------------------------|---------------|
240
+ | `bundle exec assistant-rbs PATH [--output sig/]` | Experimental |
241
+
242
+ Scans `PATH` for `Assistant::Service` subclasses and emits
243
+ `sig/<class>.rbs` with `def name: () -> Type` and `def name?: () -> bool`
244
+ per declared input. A multi-type `type:` produces a union; `allow_nil:`
245
+ produces a nullable type. The output is idempotent — running twice
246
+ overwrites the same file with the same contents.
247
+
248
+ Labelled **Experimental** in 1.0 because the output format may evolve
249
+ in 1.x as RBS support for richer types matures.
250
+
251
+ ---
252
+
253
+ ## Semver and deprecation policy
254
+
255
+ - **Patch (1.0.x)**: bug fixes, doc updates, internal refactors with no
256
+ observable behaviour change.
257
+ - **Minor (1.x.0)**: additive API only — new `input` options, new
258
+ `LogItem` predicates, new `Service` hooks. No removals.
259
+ - **Major (2.0.0)**: removals, renames, behaviour changes that break
260
+ the contract above.
261
+
262
+ Every deprecated symbol lives for **at least one further minor** after
263
+ its deprecation, with a `Kernel.warn` runtime warning and a row in
264
+ [`docs/deprecations.md`](./deprecations.md).
data/docs/changelog.md ADDED
@@ -0,0 +1,26 @@
1
+ ---
2
+ title: Changelog
3
+ nav_order: 7
4
+ ---
5
+
6
+ # Changelog
7
+
8
+ The full release history is in
9
+ [`CHANGELOG.md`](https://github.com/ramongr/assistant/blob/main/CHANGELOG.md)
10
+ at the repository root, formatted per
11
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
12
+
13
+ The same file is what `assistant.gemspec` points to via
14
+ `spec.metadata['changelog_uri']`, so RubyGems shows the identical content
15
+ under the gem's **Changelog** tab.
16
+
17
+ ## How to read it
18
+
19
+ - `[Unreleased]` collects changes that have merged to `main` but have not
20
+ been cut into a release tag.
21
+ - Each released version uses an ISO date and groups entries by
22
+ `### Added` / `### Changed` / `### Deprecated` / `### Removed` /
23
+ `### Fixed` / `### Security`.
24
+ - `### Changed (Breaking)` callouts mark semver-breaking changes; for
25
+ the 0.x → 1.x sweep, those are catalogued in
26
+ [`docs/v1/06-migration-0x-to-1.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md).
@@ -0,0 +1,86 @@
1
+ ---
2
+ title: Deprecations
3
+ nav_order: 4
4
+ ---
5
+
6
+ # Deprecations
7
+
8
+ This page tracks every public symbol that is **deprecated** in a 1.x release
9
+ and the version in which it will be removed. Each entry includes a 1:1
10
+ replacement and the runtime warning users will see.
11
+
12
+ The deprecation policy is one full minor cycle: anything marked here in
13
+ `1.x` is removed in `2.0`.
14
+
15
+ ## Index
16
+
17
+ | Symbol | Replacement | Deprecated in | Removed in |
18
+ |---------------------------------------------------------|-------------------------------------------------------------------|---------------|------------|
19
+ | `Assistant::Service#valid_require_<name>?` | `Assistant::Service#valid_required_<name>?` | `1.0.0` (M9) | `2.0.0` |
20
+ | `Assistant::Service#valid_require_conditional_<name>?` | `Assistant::Service#valid_required_conditional_<name>?` | `1.0.0` (M9) | `2.0.0` |
21
+
22
+ ---
23
+
24
+ ## `valid_require_<name>?` → `valid_required_<name>?`
25
+
26
+ **Deprecated in**: `1.0.0` (M9). **Removed in**: `2.0.0`.
27
+
28
+ ### What changed
29
+
30
+ Per-input requirement validators are now generated under their
31
+ grammatically correct names. For each `input :name, required: true`
32
+ declaration, `Assistant::Service` subclasses gain:
33
+
34
+ | Canonical (use this) | Deprecated alias (still works, warns once per call site) |
35
+ |-----------------------------------------------|----------------------------------------------------------|
36
+ | `#valid_required_<name>?` | `#valid_require_<name>?` |
37
+ | `#valid_required_conditional_<name>?` | `#valid_require_conditional_<name>?` |
38
+
39
+ Both names refer to the same underlying predicate — the deprecated alias
40
+ simply delegates to the canonical method after emitting the deprecation
41
+ warning. Return values, side effects, and `log_item_error_initialize`
42
+ behaviour are unchanged.
43
+
44
+ ### Runtime warning
45
+
46
+ ```text
47
+ assistant: `#valid_require_email?` is deprecated; use `#valid_required_email?` (removed in assistant 2.0)
48
+ ```
49
+
50
+ The warning fires through `Kernel.warn` (i.e. on `$stderr`) **once per
51
+ textual call site** (`caller_locations(1, 1).first` is keyed by `path +
52
+ lineno`). Calling the alias 100 times from the same line yields exactly
53
+ one warning; calling it from two different lines yields two warnings.
54
+
55
+ Internal framework code (`Service#validate_inputs`) calls only the
56
+ canonical name, so the warning never fires from inside the gem.
57
+
58
+ ### Migration
59
+
60
+ Search-and-replace at the call site:
61
+
62
+ ```ruby
63
+ # Before
64
+ service.valid_require_email?
65
+ service.valid_require_conditional_token?
66
+
67
+ # After
68
+ service.valid_required_email?
69
+ service.valid_required_conditional_token?
70
+ ```
71
+
72
+ If your code overrides one of these predicates (`def valid_require_email?
73
+ ...`), rename the override to the canonical name. The deprecated alias
74
+ will still exist in `1.x` and will continue to delegate to the canonical
75
+ method, so an override under the old name is silently bypassed.
76
+
77
+ ### Why
78
+
79
+ Q2 in
80
+ [`docs/v1/07-risks-and-open-questions.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/07-risks-and-open-questions.md)
81
+ was decided in favour of Option B: the new names read better, match
82
+ standard English, and are easier to grep for. The old names live one
83
+ minor cycle to give downstream services an upgrade window.
84
+
85
+ See [`docs/v1/02-features.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/02-features.md) **M9** for the
86
+ implementation plan and acceptance criteria.
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: CLI handler
3
+ parent: Examples
4
+ nav_order: 2
5
+ ---
6
+
7
+ # CLI handler
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P7](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
+ An `OptionParser`-driven script whose exit code derives from `#status`.
14
+
15
+ When the runnable script under `examples/cli_handler/` 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: Composing services
3
+ parent: Examples
4
+ nav_order: 4
5
+ ---
6
+
7
+ # Composing services
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P9](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
+ An outer service uses `call_service` to chain two inner services with log timeline merging.
14
+
15
+ When the runnable script under `examples/composing_services/` 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: Execute callbacks
3
+ parent: Examples
4
+ nav_order: 5
5
+ ---
6
+
7
+ # Execute callbacks
8
+
9
+ > **Status:** placeholder — ships in
10
+ > [P10](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
+ `before_execute` audit logger plus an `around_execute` timing wrapper, including failure cases.
14
+
15
+ When the runnable script under `examples/execute_callbacks/` 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,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.