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.
- checksums.yaml +4 -4
- data/.editorconfig +13 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +39 -0
- data/.github/dependabot.yml +4 -0
- data/.github/workflows/ci.yml +140 -0
- data/.github/workflows/docs.yml +64 -0
- data/.github/workflows/release.yml +46 -0
- data/.gitignore +5 -1
- data/.markdownlint.json +6 -0
- data/.opencode/.gitignore +4 -0
- data/.opencode/opencode.json +13 -0
- data/.opencode/skills/create-pr/SKILL.md +138 -0
- data/.opencode/skills/ruby-services/SKILL.md +81 -0
- data/.rubocop.yml +40 -148
- data/.ruby-version +1 -1
- data/.yardopts +17 -0
- data/CHANGELOG.md +434 -0
- data/CONTRIBUTING.md +131 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +264 -94
- data/README.md +125 -16
- data/Rakefile +53 -3
- data/SECURITY.md +50 -0
- data/Steepfile +49 -0
- data/_config.yml +87 -0
- data/assistant.gemspec +33 -20
- data/docs/api-reference.md +264 -0
- data/docs/changelog.md +26 -0
- data/docs/deprecations.md +86 -0
- data/docs/examples/cli-handler.md +17 -0
- data/docs/examples/composing-services.md +17 -0
- data/docs/examples/execute-callbacks.md +17 -0
- data/docs/examples/index.md +29 -0
- data/docs/examples/instrumentation-notifier.md +17 -0
- data/docs/examples/rails-service.md +17 -0
- data/docs/examples/rbs-generator.md +17 -0
- data/docs/examples/sidekiq-worker.md +17 -0
- data/docs/getting-started.md +136 -0
- data/docs/guides/composing-services.md +222 -0
- data/docs/guides/index.md +25 -0
- data/docs/guides/inputs.md +333 -0
- data/docs/guides/logging-and-results.md +202 -0
- data/docs/guides/rbs-and-types.md +16 -0
- data/docs/guides/validation.md +180 -0
- data/docs/index.md +69 -0
- data/docs/roadmap.md +33 -0
- data/exe/assistant-rbs +7 -0
- data/lib/assistant/execute_callbacks.rb +103 -0
- data/lib/assistant/execute_callbacks.rbs +30 -0
- data/lib/assistant/input_builder/accessors.rb +36 -0
- data/lib/assistant/input_builder/accessors.rbs +10 -0
- data/lib/assistant/input_builder/default_option.rb +41 -0
- data/lib/assistant/input_builder/default_option.rbs +11 -0
- data/lib/assistant/input_builder/dsl.rb +37 -0
- data/lib/assistant/input_builder/dsl.rbs +12 -0
- data/lib/assistant/input_builder/optional_option.rb +45 -0
- data/lib/assistant/input_builder/optional_option.rbs +10 -0
- data/lib/assistant/input_builder/registry.rb +27 -0
- data/lib/assistant/input_builder/registry.rbs +13 -0
- data/lib/assistant/input_builder/require_validator.rb +104 -0
- data/lib/assistant/input_builder/require_validator.rbs +24 -0
- data/lib/assistant/input_builder/type_validator.rb +47 -0
- data/lib/assistant/input_builder/type_validator.rbs +18 -0
- data/lib/assistant/input_builder.rb +28 -0
- data/lib/assistant/input_builder.rbs +15 -0
- data/lib/assistant/log_item.rb +75 -17
- data/lib/assistant/log_item.rbs +40 -0
- data/lib/assistant/log_list.rb +44 -12
- data/lib/assistant/log_list.rbs +48 -0
- data/lib/assistant/rbs_generator/cli.rb +109 -0
- data/lib/assistant/rbs_generator/cli.rbs +24 -0
- data/lib/assistant/rbs_generator/renderer.rb +67 -0
- data/lib/assistant/rbs_generator/renderer.rbs +11 -0
- data/lib/assistant/rbs_generator/writer.rb +65 -0
- data/lib/assistant/rbs_generator/writer.rbs +24 -0
- data/lib/assistant/rbs_generator.rb +38 -0
- data/lib/assistant/rbs_generator.rbs +5 -0
- data/lib/assistant/refinements/string_blankness.rb +14 -0
- data/lib/assistant/refinements/string_blankness.rbs +6 -0
- data/lib/assistant/service.rb +328 -8
- data/lib/assistant/service.rbs +86 -0
- data/lib/assistant/version.rb +5 -1
- data/lib/assistant/version.rbs +5 -0
- data/lib/assistant.rb +53 -4
- data/lib/assistant.rbs +25 -0
- data/mise.toml +6 -0
- data/sig/examples/greeter.rbs +14 -0
- metadata +128 -112
- data/.circleci/config.yml +0 -45
- data/.fasterer.yml +0 -19
- data/.rspec +0 -3
- data/.rubocop_todo.yml +0 -0
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD043 -->
|
|
2
|
+
# Changelog
|
|
3
|
+
|
|
4
|
+
All notable changes to this project will be documented in this file.
|
|
5
|
+
|
|
6
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **GitHub Pages — P2 scaffolding (Jekyll + just-the-docs)**: stood up
|
|
14
|
+
the parallel docs site per
|
|
15
|
+
[`docs/v1/08-github-pages.md`](docs/v1/08-github-pages.md). Ships
|
|
16
|
+
`_config.yml` (just-the-docs theme, Lunr search, dark-mode toggle,
|
|
17
|
+
`gh_edit_link`, `jekyll-seo-tag` + `jekyll-relative-links` plugins),
|
|
18
|
+
an optional `:docs` Bundler group in [`Gemfile`](Gemfile) pinning
|
|
19
|
+
`jekyll ~> 4.3`, `just-the-docs ~> 0.10`, and
|
|
20
|
+
`jekyll-relative-links ~> 0.7`, the
|
|
21
|
+
[`.github/workflows/docs.yml`](.github/workflows/docs.yml) workflow
|
|
22
|
+
(PR builds `bundle exec jekyll build --strict_front_matter`; pushes
|
|
23
|
+
to `main` deploy via `actions/deploy-pages@v4`), `docs:install` /
|
|
24
|
+
`docs:build` / `docs:serve` Rake tasks driving the Jekyll
|
|
25
|
+
toolchain, per-page front matter on every site page (Home, Getting
|
|
26
|
+
started, Guides + 5 sub-pages, API reference, Deprecations, Examples
|
|
27
|
+
+ 7 sub-pages, Roadmap, Changelog), and a new `docs/guides/index.md`
|
|
28
|
+
landing for the Guides section. The site builds locally with `rake
|
|
29
|
+
docs:build` and deploys to `https://ramongr.github.io/assistant/` on
|
|
30
|
+
every push to `main`. Pages source = `GitHub Actions` was enabled in
|
|
31
|
+
repo settings after the original mkdocs PR (#177) merged; no further
|
|
32
|
+
manual step is needed for the Jekyll cut-over.
|
|
33
|
+
|
|
34
|
+
_This entry supersedes the original mkdocs-based P2 scaffolding —
|
|
35
|
+
the mkdocs stack lived for one PR before being replaced with Jekyll
|
|
36
|
+
to match the gem's primary toolchain, drop the Python build
|
|
37
|
+
dependency, and avoid the `Pygments==2.19.1` pin that worked around
|
|
38
|
+
a 2.20.0 `HtmlFormatter` regression._
|
|
39
|
+
|
|
40
|
+
## [1.0.0.rc1] - 2026-06-15
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- **D2 (follow-up)**: four user-facing guides under `docs/guides/` —
|
|
45
|
+
[`inputs.md`](docs/guides/inputs.md),
|
|
46
|
+
[`validation.md`](docs/guides/validation.md),
|
|
47
|
+
[`logging-and-results.md`](docs/guides/logging-and-results.md),
|
|
48
|
+
[`composing-services.md`](docs/guides/composing-services.md).
|
|
49
|
+
Each guide is mirrored by a `test/docs/<guide>_examples_test.rb`
|
|
50
|
+
integration test so the runnable examples can't silently drift from
|
|
51
|
+
the actual behaviour. `inputs.md` includes the "Using
|
|
52
|
+
`bin/assistant-rbs` for Steep users" subsection that closes the R1
|
|
53
|
+
user-facing-note item in
|
|
54
|
+
[`docs/v1/05-quality-and-tooling.md`](docs/v1/05-quality-and-tooling.md).
|
|
55
|
+
`.yardopts` extra-files list extended to include the four new pages
|
|
56
|
+
so they ship with the rendered YARD output.
|
|
57
|
+
|
|
58
|
+
- **bin/ smoke**: new `bin-smoke` job in `.github/workflows/ci.yml`
|
|
59
|
+
exercises `bin/setup` against a cold bundle, syntax-checks the three
|
|
60
|
+
developer scripts (`bash -n bin/setup`, `ruby -c bin/{console,version}`),
|
|
61
|
+
runs `bin/version --help`, and pipes a short ruby snippet through
|
|
62
|
+
`bin/console` to confirm `Assistant::VERSION` resolves. Closes the
|
|
63
|
+
`bin/` smoke item in
|
|
64
|
+
[`docs/v1/05-quality-and-tooling.md`](docs/v1/05-quality-and-tooling.md).
|
|
65
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) gains a `bin/ developer scripts`
|
|
66
|
+
section documenting each script's purpose and noting that none of the
|
|
67
|
+
three ship in the packaged gem (only `exe/assistant-rbs` does).
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- **Release prep**: gemspec polished for the 1.0 cut. `spec.summary`
|
|
72
|
+
rewritten to match the README elevator pitch
|
|
73
|
+
(`Tiny, dependency-free soft-fail service objects for Ruby`),
|
|
74
|
+
`spec.description` expanded into a 3-sentence heredoc covering
|
|
75
|
+
soft-fail semantics, the uniform result shape, the RBS / Steep
|
|
76
|
+
posture, and the zero-runtime-deps guarantee. Added
|
|
77
|
+
`spec.metadata['documentation_uri']`
|
|
78
|
+
(`https://rubydoc.info/gems/assistant`) and
|
|
79
|
+
`spec.metadata['bug_tracker_uri']`
|
|
80
|
+
(`https://github.com/ramongr/assistant/issues`). The `spec.files`
|
|
81
|
+
glob now excludes `examples/`, `docs/v1/`, and `docs/v1.x/` from the
|
|
82
|
+
packaged gem so internal planning material and runnable samples no
|
|
83
|
+
longer ship to RubyGems (Q9 decision in
|
|
84
|
+
[`docs/v1/07-risks-and-open-questions.md`](docs/v1/07-risks-and-open-questions.md)).
|
|
85
|
+
No behaviour change; `Assistant::VERSION` is unchanged.
|
|
86
|
+
|
|
87
|
+
### Changed (Breaking)
|
|
88
|
+
|
|
89
|
+
- **M12**: `LogList#merge_logs` and every internal
|
|
90
|
+
`Assistant::InputBuilder` helper now take their name / list
|
|
91
|
+
parameter as a keyword argument (`logs:` / `name:` / `names:`)
|
|
92
|
+
instead of a leading positional. The two public DSL entry points
|
|
93
|
+
`Service.input` and `Service.inputs` are **deliberately exempt** —
|
|
94
|
+
`input :foo, type: X` reads better as a class-body declaration than
|
|
95
|
+
`input name: :foo, type: X`, so their leading positional `attr_name`
|
|
96
|
+
/ `attr_names` stays. Hard break for the rest, no runtime shim:
|
|
97
|
+
- `Service.input(:foo, type: String)` — **unchanged**
|
|
98
|
+
- `Service.inputs(%i[a b], type: Integer)` — **unchanged**
|
|
99
|
+
- `host.merge_logs(other.logs)` → `host.merge_logs(logs: other.logs)`
|
|
100
|
+
The old positional `merge_logs` raises `ArgumentError` at call time
|
|
101
|
+
("wrong number of arguments ... required keyword: logs"). For users
|
|
102
|
+
who don't compose log lists directly (i.e. who only use
|
|
103
|
+
`Service#call_service` for service composition), no source change is
|
|
104
|
+
required. Migration is mechanical and `git grep`-able; see
|
|
105
|
+
[`docs/v1/06-migration-0x-to-1.md`](docs/v1/06-migration-0x-to-1.md).
|
|
106
|
+
The full helper sweep also touches the M13-split per-concern
|
|
107
|
+
modules: `process_default_option`, `validate_default!`,
|
|
108
|
+
`warn_on_mutable_default`, `process_optional_option`,
|
|
109
|
+
`validate_optional!`, `register_input_definition`, `input_getter_meth`,
|
|
110
|
+
`input_checker_meth`, `input_type_validator_meth`, `type_validator_body`,
|
|
111
|
+
`type_mismatch_message_builder`, `input_require_validator_meth`,
|
|
112
|
+
`input_require_conditional_meth`, and the two private
|
|
113
|
+
`RequireValidator#define_required_(conditional_)?validator` helpers
|
|
114
|
+
are all keyword-only. Internal-only `Service#input_supplied?` keeps
|
|
115
|
+
its positional shape (private, not part of the documented surface).
|
|
116
|
+
RBS signatures across `lib/assistant/input_builder/*.rbs` (other than
|
|
117
|
+
`dsl.rbs`) and `lib/assistant/log_list.rbs` updated to match.
|
|
118
|
+
|
|
119
|
+
### Added
|
|
120
|
+
|
|
121
|
+
- **D2** (entry pages): shipped `docs/getting-started.md` and
|
|
122
|
+
`docs/api-reference.md`. `docs/getting-started.md` walks from
|
|
123
|
+
`gem install` to a working `CreateUser` service across three runs
|
|
124
|
+
(one `:ok`, one `:with_warnings`, one `:with_errors`) and links out
|
|
125
|
+
to the four follow-up guides. `docs/api-reference.md` is the
|
|
126
|
+
hand-written, curated reference for every Frozen symbol on
|
|
127
|
+
`Assistant`, `Assistant::Service`, `Assistant::LogItem`,
|
|
128
|
+
`Assistant::LogList`, the execute callbacks, `#call_service`,
|
|
129
|
+
the notifier, `#input_snapshot`, and the `assistant-rbs` CLI;
|
|
130
|
+
`docs/v1/01-api-surface.md` remains the source of truth for
|
|
131
|
+
stability labels. `README.md` documentation index and the
|
|
132
|
+
`.yardopts` extra-files list now include both new pages. The four
|
|
133
|
+
topic guides (`inputs.md`, `validation.md`, `logging-and-results.md`,
|
|
134
|
+
`composing-services.md`) ship in a follow-up D2 PR alongside
|
|
135
|
+
`test/docs/` example tests.
|
|
136
|
+
- **D3**: every public Frozen symbol enumerated in
|
|
137
|
+
[`docs/v1/01-api-surface.md`](docs/v1/01-api-surface.md) now carries
|
|
138
|
+
YARD documentation (`@param`, `@return`, `@raise`, `@example` where
|
|
139
|
+
meaningful). Internal helpers are documented too, so
|
|
140
|
+
`bundle exec yard stats --list-undoc` reports **100%** documented
|
|
141
|
+
public methods (52 / 52, plus 7 / 7 attributes and 9 / 9 constants).
|
|
142
|
+
Shipped together with a top-level `.yardopts` (markdown markup,
|
|
143
|
+
`lib/**/*.rb` as the source, README + repo-hygiene files as extra
|
|
144
|
+
files), the new `yard` development dependency in `assistant.gemspec`,
|
|
145
|
+
and a `rake yard` task that builds the site into `doc/` and exits
|
|
146
|
+
non-zero if coverage drops below 100%. `rake ci` now runs
|
|
147
|
+
`test + rubocop + steep + yard`.
|
|
148
|
+
- **D4**: shipped the repository-hygiene files called for in
|
|
149
|
+
[`docs/v1/03-documentation.md`](docs/v1/03-documentation.md). New
|
|
150
|
+
`CONTRIBUTING.md` documents the clone / `bin/setup` flow, the local
|
|
151
|
+
pipeline (`rake test`, `rubocop`, `steep check`, `rake ci`), branch
|
|
152
|
+
naming, commit-tag conventions, and PR template expectations. New
|
|
153
|
+
`SECURITY.md` declares 1.x as the supported line, 0.x as EOL on the
|
|
154
|
+
`1.0.0` release, gives `cerberus.ramon@gmail.com` as the private
|
|
155
|
+
report channel, and commits to a 7-day first-response /
|
|
156
|
+
30-day-fix-or-mitigation-plan SLA. New
|
|
157
|
+
`.github/PULL_REQUEST_TEMPLATE.md` enforces the
|
|
158
|
+
`Scope / What ships / Verification / Out of scope` body shape and the
|
|
159
|
+
`CHANGELOG entry / tests added / docs updated / rake ci is green`
|
|
160
|
+
checklist on every pull request.
|
|
161
|
+
- **D1**: rewrote the top-level `README.md`. Replaced the bundler-template
|
|
162
|
+
`TODO:` placeholders and `[USERNAME]/assistant` URLs with an elevator
|
|
163
|
+
pitch, status badges (CI, gem version, downloads, Ruby version,
|
|
164
|
+
license), `bundle add` / `gem install` instructions, a runnable
|
|
165
|
+
60-second `CreateUser` example covering required inputs, defaults,
|
|
166
|
+
`allow_nil:`, `validate`, and the `log_item_warning` /
|
|
167
|
+
`log_item_error` shorthands, a "why another service-object gem?"
|
|
168
|
+
comparison against Interactor and dry-transaction, a documentation
|
|
169
|
+
index pointing at `docs/v1/01-api-surface.md`, the migration guide,
|
|
170
|
+
deprecations, examples, the changelog, and the roadmap, plus a
|
|
171
|
+
refreshed Development section listing `rake test`, `rubocop`, and
|
|
172
|
+
`steep check`. (D1, v1 plan)
|
|
173
|
+
|
|
174
|
+
- `Assistant::Service#input_snapshot` — returns a frozen `Data`
|
|
175
|
+
instance whose members are the declared input names (via
|
|
176
|
+
`Service.input` / `Service.inputs`), in declaration order, with
|
|
177
|
+
values read from `@inputs` after `apply_input_defaults` has run. The
|
|
178
|
+
snapshot therefore reflects post-`default:` and post-`allow_nil:`
|
|
179
|
+
values, matching what the per-input getters expose. Only declared
|
|
180
|
+
inputs appear; extra keyword arguments accepted by `#initialize`
|
|
181
|
+
(which live in `@inputs` but have no `input :foo` declaration) are
|
|
182
|
+
intentionally excluded so the snapshot's shape mirrors the public
|
|
183
|
+
DSL. A declared input with no default and no caller-supplied value
|
|
184
|
+
surfaces as `nil`. The returned `Data` is structurally immutable
|
|
185
|
+
(no member reassignment); member values that are themselves mutable
|
|
186
|
+
(e.g. an `Array`) keep their normal mutability — the snapshot does
|
|
187
|
+
not deep-freeze. Each call returns a fresh `Data` instance backed
|
|
188
|
+
by a per-subclass `Data` class memoised on
|
|
189
|
+
`Service.input_snapshot_class` (rebuilt transparently if the
|
|
190
|
+
subclass declares more inputs after the first snapshot call).
|
|
191
|
+
Useful for passing a read-only view of inputs to helpers,
|
|
192
|
+
collaborators, or test assertions without exposing the mutable
|
|
193
|
+
`@inputs` hash.
|
|
194
|
+
|
|
195
|
+
- `Assistant::Service#call_service(klass, **inputs)` — instance-level
|
|
196
|
+
helper for composing services. Constructs an instance of `klass`
|
|
197
|
+
(asserted to be an `Assistant::Service` subclass; raises
|
|
198
|
+
`ArgumentError` otherwise), invokes `inner.run`, merges the inner
|
|
199
|
+
service's full log timeline (info + warning + error) onto the outer
|
|
200
|
+
service via `merge_logs`, and returns the inner instance. Because
|
|
201
|
+
`Service#errors` / `#warnings` / `#status` are derived by filtering
|
|
202
|
+
`@logs`, inner errors automatically downgrade the outer terminal
|
|
203
|
+
status to `:with_errors` and inner warnings surface as
|
|
204
|
+
`:with_warnings` (when no errors are present), without any branching
|
|
205
|
+
in the caller. Exceptions raised by the inner service's `#execute`
|
|
206
|
+
or by `Assistant.notifier` are not rescued; they propagate to the
|
|
207
|
+
caller, matching the base `Service#run` contract. The inner service
|
|
208
|
+
fires its own `:service_started`/`:service_validated`/
|
|
209
|
+
`:service_executed`/`:service_failed` events independently of the
|
|
210
|
+
outer lifecycle. (M-S2, v1 plan)
|
|
211
|
+
|
|
212
|
+
- `before_execute`, `after_execute { |result| }`, and
|
|
213
|
+
`around_execute { |&blk| ... }` class-level DSL on
|
|
214
|
+
`Assistant::Service` for wrapping `#execute` with reusable hooks.
|
|
215
|
+
Hooks are `instance_exec`'d on the service (so `self` is the service
|
|
216
|
+
instance) and execute after validation in declaration order; the
|
|
217
|
+
first-declared `around_execute` is the outermost layer. Hooks are
|
|
218
|
+
inherited at subclass-definition time via an array snapshot — later
|
|
219
|
+
additions on the parent do not bleed into existing subclasses. Errors
|
|
220
|
+
raised inside any hook are caught, never propagate out of `#run`,
|
|
221
|
+
and are logged via `add_log(level: :error, source: :hook, detail:
|
|
222
|
+
<hook_type>, message: "<ErrorClass>: <message>", trace: backtrace)`.
|
|
223
|
+
A hook-logged error downgrades the terminal lifecycle event to
|
|
224
|
+
`:service_failed` and the run payload to `{ errors:, result: nil,
|
|
225
|
+
status: :with_errors }`; the actual execute return value remains
|
|
226
|
+
accessible via `service.result`. (M-S1, v1 plan)
|
|
227
|
+
|
|
228
|
+
- `Assistant.notifier` and `Assistant.notifier=` — module-level
|
|
229
|
+
configuration accessor for an instrumentation callable. The default
|
|
230
|
+
notifier is a frozen no-op lambda (`Assistant::DEFAULT_NOTIFIER`);
|
|
231
|
+
the setter accepts any object responding to `#call(event, payload)`
|
|
232
|
+
or `nil` to reset to the default. Passing anything else raises
|
|
233
|
+
`ArgumentError` immediately. `Service#run` now fires four frozen
|
|
234
|
+
events around its lifecycle: `:service_started` at entry,
|
|
235
|
+
`:service_validated` after `validate_inputs` + `validate`, and
|
|
236
|
+
exactly one of `:service_executed` (no logged errors) or
|
|
237
|
+
`:service_failed` (errors present) before returning. Every payload
|
|
238
|
+
carries `{ service_class:, duration_s: }`; `duration_s` is a `Float`
|
|
239
|
+
measured against `Process::CLOCK_MONOTONIC` from the start of `#run`.
|
|
240
|
+
Notifier exceptions (`StandardError`) are caught and surfaced via
|
|
241
|
+
`Kernel.warn`; subsequent events still fire. (M-S3, v1 plan)
|
|
242
|
+
|
|
243
|
+
- `bin/assistant-rbs` (shipped as `exe/assistant-rbs`) — a CLI that
|
|
244
|
+
loads user-supplied Ruby paths and emits an `.rbs` file per
|
|
245
|
+
`Assistant::Service` subclass into a configurable output directory
|
|
246
|
+
(default `sig/`). Each generated file declares the per-input getter
|
|
247
|
+
(`def <name>: () -> Type`) and predicate (`def <name>?: () -> bool`)
|
|
248
|
+
pairs derived from `Service.input_definitions`, including multi-type
|
|
249
|
+
unions (`(A | B)`) and `allow_nil:` (`(A | B)?`). Output is marked
|
|
250
|
+
with a header sentinel and is idempotent: rerunning leaves unchanged
|
|
251
|
+
files alone (`[unchanged]`) and refuses to overwrite hand-written
|
|
252
|
+
`.rbs` files that lack the sentinel (`[skipped]`). Namespaced classes
|
|
253
|
+
are emitted with nested `module` declarations so the generated file is
|
|
254
|
+
self-contained. Use `--output DIR`, `--quiet`, and `--help`. The
|
|
255
|
+
generator only emits sigs for `Service` subclasses introduced by the
|
|
256
|
+
paths it was asked to load (snapshot diff via `ObjectSpace`).
|
|
257
|
+
An `examples/greeter.rb` + generated `sig/examples/greeter.rbs`
|
|
258
|
+
fixture is type-checked by Steep as the acceptance test. The CLI
|
|
259
|
+
itself is Experimental; the generated `.rbs` content tracks the
|
|
260
|
+
Frozen `Service.input` surface. (M11, v1 plan)
|
|
261
|
+
- Hand-written RBS signatures for the frozen public surface defined in
|
|
262
|
+
`docs/v1/01-api-surface.md`: `Assistant::VERSION`, `Assistant::LogItem`,
|
|
263
|
+
`Assistant::LogList`, `Assistant::Service` (excluding the per-input
|
|
264
|
+
methods generated by `Service.input`), `Assistant::InputBuilder` plus
|
|
265
|
+
its `Registry`, `DefaultOption`, `OptionalOption`, `Accessors`,
|
|
266
|
+
`RequireValidator`, `TypeValidator`, and `Dsl` submodules, and a
|
|
267
|
+
namespace shim for `Assistant::Refinements::StringBlankness`. Files
|
|
268
|
+
live alongside the Ruby source as `lib/**/*.rbs` and ship with the
|
|
269
|
+
gem (already covered by `git ls-files`). A `Steepfile` adds a `:lib`
|
|
270
|
+
target type-checked by Steep in CI; `steep check` runs against the
|
|
271
|
+
subset of files that do not rely on Ruby refinements or
|
|
272
|
+
`define_method`. The per-input surface generated by `Service.input`
|
|
273
|
+
is documented in the RBS comments and will be emitted by
|
|
274
|
+
`bin/assistant-rbs` (M11). Adds `steep` as a development dependency
|
|
275
|
+
and a `steep` job to `.github/workflows/ci.yml`. (M8, v1 plan)
|
|
276
|
+
- `Assistant::Service.input` now accepts a `default:` option. The provider
|
|
277
|
+
may be a literal value or a zero-arity `Proc`/`Lambda`; anything else
|
|
278
|
+
that responds to `#call` (e.g. a `Method` object) is rejected with
|
|
279
|
+
`ArgumentError` at class-definition time. Procs are invoked once per
|
|
280
|
+
`Service` instance, with no arguments. A default fires when the input
|
|
281
|
+
key is absent, or when the value is an explicit `nil` and the input is
|
|
282
|
+
not declared `allow_nil: true` — with `allow_nil: true`, an explicit
|
|
283
|
+
`nil` from the caller is honoured and the default is skipped. Defaulted values
|
|
284
|
+
are subject to the same type, `required:`, and `if:` validation as
|
|
285
|
+
caller-supplied values. Mutable literal defaults (unfrozen `Array` /
|
|
286
|
+
`Hash`) emit a `Kernel.warn` at class-definition time, since they are
|
|
287
|
+
shared across every instance of the `Service` subclass. (M1, v1 plan)
|
|
288
|
+
- `Assistant::Service.input_definitions` — per-subclass hash exposing the
|
|
289
|
+
original `input` declaration options (including `:default`) for
|
|
290
|
+
introspection. Experimental; subject to change before 1.0.0.
|
|
291
|
+
- `Assistant::Service.input` now accepts `allow_nil: true`. When set,
|
|
292
|
+
any supplied value for that key short-circuits both `valid_type_<name>?`
|
|
293
|
+
and `valid_require_<name>?` — i.e. `nil` is accepted, and type-checking
|
|
294
|
+
is effectively disabled for the input. When `allow_nil:` is omitted
|
|
295
|
+
(default), behaviour is unchanged from 0.1.0 — an absent or `nil` value
|
|
296
|
+
silently passes type checks, and a `nil` on a `required:` input is
|
|
297
|
+
still treated as missing. (M2, v1 plan)
|
|
298
|
+
- `Assistant::Service.input` now accepts an array for `type:`, e.g.
|
|
299
|
+
`input :amount, type: [Integer, Float]`. The generated
|
|
300
|
+
`valid_type_<name>?` validator passes when the input matches **any** of
|
|
301
|
+
the listed types. Single-type declarations keep the original
|
|
302
|
+
`"… is not a X but Y"` error message; multi-type produces
|
|
303
|
+
`"… is not one of [A, B] but Y"`. (M3, v1 plan)
|
|
304
|
+
- `Assistant::Service#logs` public reader exposing the full log timeline
|
|
305
|
+
(info + warning + error) in insertion order. Callers no longer need to
|
|
306
|
+
reach into `@logs` via `instance_variable_get`. (M4, v1 plan)
|
|
307
|
+
- `Assistant::LogList#log_item_info`, `#log_item_warning`, and
|
|
308
|
+
`#log_item_error` shorthands. These wrap `add_log(level: ..., …)` so
|
|
309
|
+
service authors stop hand-rolling the level keyword on every call.
|
|
310
|
+
(M5, v1 plan)
|
|
311
|
+
- `Assistant::Service.input` now accepts an `optional:` flag. `optional: true`
|
|
312
|
+
is explicit sugar for the default behaviour (no `required:` validator is
|
|
313
|
+
generated); `optional: false` is equivalent to `required: true`. Declaring
|
|
314
|
+
`required: true` and `optional: true` together raises `ArgumentError` at
|
|
315
|
+
class-definition time, as does a non-boolean `optional:` value. The flag is
|
|
316
|
+
retained in `Service.input_definitions` for introspection and composes with
|
|
317
|
+
`default:` (M1) and `allow_nil:` (M2) without surprises. (M7, v1 plan)
|
|
318
|
+
|
|
319
|
+
### Changed
|
|
320
|
+
|
|
321
|
+
- `Assistant::LogItem.new` now raises `ArgumentError` when constructed with
|
|
322
|
+
invalid attributes instead of returning an invalid object. Validation runs at
|
|
323
|
+
the end of initialization and reports every failing attribute in one message
|
|
324
|
+
(level, source, detail, message). The `#valid?` predicate family remains for
|
|
325
|
+
introspection and returns `true` for normally constructed instances.
|
|
326
|
+
`LogList#add_log` now inherits this fail-fast behaviour because it constructs
|
|
327
|
+
`LogItem` internally. (M10, v1 plan)
|
|
328
|
+
- `Assistant::InputBuilder` split into per-concern submodules under
|
|
329
|
+
`lib/assistant/input_builder/` (`Registry`, `DefaultOption`,
|
|
330
|
+
`OptionalOption`, `Accessors`, `RequireValidator`, `TypeValidator`,
|
|
331
|
+
`Dsl`). The umbrella `Assistant::InputBuilder` `include`s each
|
|
332
|
+
submodule; the public surface (`Service` extends `Assistant::InputBuilder`)
|
|
333
|
+
is unchanged. The `using Assistant::Refinements::StringBlankness`
|
|
334
|
+
refinement now activates only inside the `Accessors` submodule.
|
|
335
|
+
Tests mirror the lib layout under `test/assistant/input_builder/`.
|
|
336
|
+
Removes the temporary `Metrics/ModuleLength: Max: 150` override from
|
|
337
|
+
`.rubocop.yml`. (M13, v1 plan)
|
|
338
|
+
- For each `input :name, required: true` declaration, `Service` subclasses
|
|
339
|
+
now generate `#valid_required_<name>?` as the canonical requirement
|
|
340
|
+
validator (and `#valid_required_conditional_<name>?` when `if:` is also
|
|
341
|
+
given). The pre-existing `#valid_require_<name>?` / `#valid_require_conditional_<name>?`
|
|
342
|
+
predicates remain as deprecated aliases — they delegate to the
|
|
343
|
+
canonical method and emit a `Kernel.warn` once per textual call site
|
|
344
|
+
pointing at the canonical replacement. `Service#validate_inputs`
|
|
345
|
+
invokes only the canonical names, so internal framework code never
|
|
346
|
+
triggers the deprecation warning. See
|
|
347
|
+
[`docs/deprecations.md`](docs/deprecations.md). (M9, v1 plan)
|
|
348
|
+
- `lib/assistant.rb` now requires every core building block explicitly in
|
|
349
|
+
dependency order (`version`, `log_item`, `log_list`,
|
|
350
|
+
`refinements/string_blankness`, `input_builder`, `service`). After a bare
|
|
351
|
+
`require "assistant"`, `Assistant::LogList`, `Assistant::InputBuilder`, and
|
|
352
|
+
`Assistant::Refinements::StringBlankness` are reachable without first
|
|
353
|
+
loading `Assistant::Service`. (M6, v1 plan)
|
|
354
|
+
|
|
355
|
+
### Deprecated
|
|
356
|
+
|
|
357
|
+
- `Assistant::Service#valid_require_<name>?` (use
|
|
358
|
+
`#valid_required_<name>?` instead). Scheduled for removal in
|
|
359
|
+
`assistant 2.0`. (M9, v1 plan)
|
|
360
|
+
- `Assistant::Service#valid_require_conditional_<name>?` (use
|
|
361
|
+
`#valid_required_conditional_<name>?` instead). Scheduled for removal
|
|
362
|
+
in `assistant 2.0`. (M9, v1 plan)
|
|
363
|
+
|
|
364
|
+
### Migration
|
|
365
|
+
|
|
366
|
+
`1.0.0` is a stabilisation release. Three small breaking changes have
|
|
367
|
+
to be addressed; every one is mechanical and `git grep`-able. The full
|
|
368
|
+
recipe lives in
|
|
369
|
+
[`docs/v1/06-migration-0x-to-1.md`](docs/v1/06-migration-0x-to-1.md).
|
|
370
|
+
|
|
371
|
+
1. **`LogList#merge_logs` is keyword-only (M12, B3)** — rewrite every
|
|
372
|
+
`merge_logs(other.logs)` call site to `merge_logs(logs: other.logs)`.
|
|
373
|
+
The two public DSL entry points `Service.input` and `Service.inputs`
|
|
374
|
+
keep their leading positional `attr_name` / `attr_names`; only
|
|
375
|
+
`merge_logs` and the internal `InputBuilder` helpers changed.
|
|
376
|
+
2. **`LogItem.new` raises on invalid attrs (M10, B1)** — audit any
|
|
377
|
+
direct `LogItem.new(...)` call sites. The gem's own call sites are
|
|
378
|
+
already correct; fixtures that exercised the old "constructs but
|
|
379
|
+
`valid? == false`" path need updating. Prefer the `add_log` /
|
|
380
|
+
`log_item_*` helpers in regular code.
|
|
381
|
+
3. **`valid_require_*?` is deprecated (M9, B2)** — rename direct calls
|
|
382
|
+
to the new `valid_required_*?` form. Users who don't call these
|
|
383
|
+
predicates directly (driven internally by `validate_inputs`) need
|
|
384
|
+
no source change; the old name still works in 1.x with a one-time
|
|
385
|
+
`Kernel.warn` per call site, and is removed in 2.0.
|
|
386
|
+
|
|
387
|
+
Pin to `~> 1.0` in your `Gemfile` once the upgrade lands.
|
|
388
|
+
|
|
389
|
+
## [0.1.0] - 2026-05-07
|
|
390
|
+
|
|
391
|
+
### Added
|
|
392
|
+
|
|
393
|
+
- `LogList#log_item_error_initialize` helper, used by `InputBuilder`-generated
|
|
394
|
+
validators (previously redefined on every `input` declaration).
|
|
395
|
+
- GitHub Actions CI workflow (`.github/workflows/ci.yml`) running Minitest and
|
|
396
|
+
RuboCop.
|
|
397
|
+
- GitHub Actions release workflow (`.github/workflows/release.yml`) using
|
|
398
|
+
RubyGems trusted publishing (OIDC) on `v*.*.*` tags.
|
|
399
|
+
- Direct test coverage for `LogList#warnings`, `#errors`, `#merge_logs`,
|
|
400
|
+
`Service#success?`, `#failure?`, `#status`, `#result` memoization,
|
|
401
|
+
conditional requirement behavior, the `inputs(...)` plural DSL form, and
|
|
402
|
+
`LogItem#trace`/`#item`.
|
|
403
|
+
|
|
404
|
+
### Changed
|
|
405
|
+
|
|
406
|
+
- Standardized on Ruby 3.4 (`.ruby-version`, gemspec `required_ruby_version`,
|
|
407
|
+
RuboCop `TargetRubyVersion`).
|
|
408
|
+
- `InputBuilder` no longer requires `active_support`; the previous use of
|
|
409
|
+
`Object#present?` is replaced with plain Ruby checks. Whitespace-only
|
|
410
|
+
strings continue to be treated as missing via a scoped
|
|
411
|
+
`Assistant::Refinements::StringBlankness` refinement that adds
|
|
412
|
+
`String#whitespace?` and is activated inside `InputBuilder`. The method is
|
|
413
|
+
intentionally named to avoid colliding with ActiveSupport's `String#blank?`.
|
|
414
|
+
- `assistant.gemspec` `changelog_uri` now points at `CHANGELOG.md` instead of
|
|
415
|
+
`CODE_OF_CONDUCT.md`.
|
|
416
|
+
- Migrated the test suite from RSpec to Minitest (`test/**/*_test.rb`),
|
|
417
|
+
exposed via `rake test` (the new default rake task).
|
|
418
|
+
- Replaced the largely-dead RuboCop config (a fork of RuboCop's own internal
|
|
419
|
+
config) with a focused configuration for this gem; `rubocop-rspec` is
|
|
420
|
+
replaced with `rubocop-minitest`.
|
|
421
|
+
|
|
422
|
+
### Removed
|
|
423
|
+
|
|
424
|
+
- CircleCI configuration (`.circleci/`); replaced by GitHub Actions.
|
|
425
|
+
- Dead `@keys = []` instance variable in `Assistant::Service#initialize`.
|
|
426
|
+
- `active_support` and `active_support/core_ext/object` requires from
|
|
427
|
+
`lib/assistant/input_builder.rb`.
|
|
428
|
+
- RSpec, FactoryBot, Faker, `rspec-collection_matchers`,
|
|
429
|
+
`rspec_junit_formatter`, `rubocop-faker`, and `rubocop-rspec` development
|
|
430
|
+
dependencies; replaced by `minitest` and `rubocop-minitest`.
|
|
431
|
+
|
|
432
|
+
## [0.0.2] - 2023-11-27
|
|
433
|
+
|
|
434
|
+
- Initial public release.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD013 -->
|
|
2
|
+
# Contributing to `assistant`
|
|
3
|
+
|
|
4
|
+
Thanks for taking the time to contribute. `assistant` is a small, dependency-free
|
|
5
|
+
service-object gem and it intends to stay that way; please read this guide
|
|
6
|
+
end-to-end before opening your first pull request.
|
|
7
|
+
|
|
8
|
+
By participating you agree to abide by the project's
|
|
9
|
+
[Code of Conduct](./CODE_OF_CONDUCT.md).
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
git clone https://github.com/ramongr/assistant.git
|
|
15
|
+
cd assistant
|
|
16
|
+
bin/setup # bundle install + any future bootstrap steps
|
|
17
|
+
bundle exec rake # default task runs the test suite
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If `bin/console` is more your speed, that boots an IRB session with the gem
|
|
21
|
+
preloaded.
|
|
22
|
+
|
|
23
|
+
## `bin/` developer scripts
|
|
24
|
+
|
|
25
|
+
The three checked-in scripts under `bin/` are developer conveniences. They
|
|
26
|
+
are smoke-tested on every CI run by the `bin/ scripts smoke` job, so they
|
|
27
|
+
should always work on a fresh clone.
|
|
28
|
+
|
|
29
|
+
| Script | What it does | Usage |
|
|
30
|
+
|---------------|--------------------------------------------------------------------|------------------------------------------------|
|
|
31
|
+
| `bin/setup` | Bash wrapper that runs `bundle install` on a cold checkout. | `./bin/setup` |
|
|
32
|
+
| `bin/console` | Boots IRB with `bundler/setup` + `require 'assistant'` preloaded. | `bin/console` |
|
|
33
|
+
| `bin/version` | Bumps `Assistant::VERSION` in `lib/assistant/version.rb`. | `bin/version --patch` | `--minor` | `--major` | `--help` |
|
|
34
|
+
|
|
35
|
+
These scripts are **not packaged with the gem** — they live under `bin/`,
|
|
36
|
+
not `exe/`. The only executable that ships in the gem is
|
|
37
|
+
`exe/assistant-rbs` (M11). See [`assistant.gemspec`](./assistant.gemspec)
|
|
38
|
+
for the `spec.executables` derivation.
|
|
39
|
+
|
|
40
|
+
## Local checks
|
|
41
|
+
|
|
42
|
+
Before you push, run the full local pipeline. The CI pipeline mirrors these
|
|
43
|
+
tools and rejects pull requests that do not match.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
bundle exec rake test # Minitest
|
|
47
|
+
bundle exec rubocop # style + lint
|
|
48
|
+
bundle exec steep check # RBS / type-check
|
|
49
|
+
bundle exec rake ci # convenience aggregate (test + rubocop + steep)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
SimpleCov runs automatically as part of `rake test` and writes its report to
|
|
53
|
+
`coverage/`. Coverage is reported in CI but is **not a hard gate**; the long-
|
|
54
|
+
term targets (≥98% line, ≥95% branch) are documented in
|
|
55
|
+
[`docs/v1/05-quality-and-tooling.md`](./docs/v1/05-quality-and-tooling.md).
|
|
56
|
+
|
|
57
|
+
## Branch naming
|
|
58
|
+
|
|
59
|
+
| Branch | Use it for |
|
|
60
|
+
|------------------------------|------------------------------------------------------------|
|
|
61
|
+
| `feature/m<n>-<slug>` | A roadmap milestone from `docs/v1/02-features.md`. |
|
|
62
|
+
| `feature/m-s<n>-<slug>` | A promoted "Should" item (M-S1, M-S2, …). |
|
|
63
|
+
| `docs/<slug>` | Documentation-only changes (D1–D5, status sweeps, guides). |
|
|
64
|
+
| `chore/<slug>` | Tooling / housekeeping with no roadmap milestone. |
|
|
65
|
+
| `fix/<slug>` or `bug/<slug>` | Bug fixes that are not part of a milestone. |
|
|
66
|
+
| `refactor/<slug>` | Internal refactors with no behavior change. |
|
|
67
|
+
|
|
68
|
+
## Commit message style
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
<TAG>: <imperative summary, ≤72 chars>
|
|
72
|
+
|
|
73
|
+
<wrapped body explaining the WHAT and the WHY (not the HOW).
|
|
74
|
+
Reference roadmap milestones or issues by ID.>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`<TAG>` is one of:
|
|
78
|
+
|
|
79
|
+
- `M<n>` — a roadmap milestone (e.g. `M11: bin/assistant-rbs per-class RBS
|
|
80
|
+
generator`). Look up the number in [`docs/v1/02-features.md`](./docs/v1/02-features.md).
|
|
81
|
+
- `M-S<n>` — a promoted "Should" item.
|
|
82
|
+
- `D<n>` — a documentation milestone from
|
|
83
|
+
[`docs/v1/03-documentation.md`](./docs/v1/03-documentation.md).
|
|
84
|
+
- `chore:`, `docs:`, `refactor:`, `fix:` — when the change does not map to a
|
|
85
|
+
roadmap entry.
|
|
86
|
+
|
|
87
|
+
Wrap the body at ~72 columns. Do **not** paste tool output or file lists;
|
|
88
|
+
`git diff` already shows that.
|
|
89
|
+
|
|
90
|
+
## Pull requests
|
|
91
|
+
|
|
92
|
+
Open the PR against `main`. The PR description follows the template at
|
|
93
|
+
[`.github/PULL_REQUEST_TEMPLATE.md`](./.github/PULL_REQUEST_TEMPLATE.md) and
|
|
94
|
+
should always cover:
|
|
95
|
+
|
|
96
|
+
- **Scope** — which milestone / issue this implements (link the roadmap line).
|
|
97
|
+
- **What this PR ships** — bullet list of the public-facing changes.
|
|
98
|
+
- **Verification** — paste the green tail of `rake test`, `rubocop`, and
|
|
99
|
+
`steep check` (or `rake ci`).
|
|
100
|
+
- **Out of scope** — what was deliberately left for later.
|
|
101
|
+
|
|
102
|
+
Each pull request must:
|
|
103
|
+
|
|
104
|
+
- [ ] Add or update a `CHANGELOG.md` entry under `[Unreleased]`.
|
|
105
|
+
- [ ] Add or update tests in `test/`.
|
|
106
|
+
- [ ] Update docs in `docs/` and the relevant `docs/v1/*.md` checklist if a
|
|
107
|
+
roadmap item is closing.
|
|
108
|
+
- [ ] Pass `bundle exec rake ci` locally.
|
|
109
|
+
|
|
110
|
+
CI will run on every push; the `Steep`, `RuboCop`, `Minitest (Ruby 3.4)`, and
|
|
111
|
+
`Minitest (Ruby 4.0)` checks are required by branch protection.
|
|
112
|
+
|
|
113
|
+
## Reporting bugs
|
|
114
|
+
|
|
115
|
+
Open an issue at <https://github.com/ramongr/assistant/issues>. Please include:
|
|
116
|
+
|
|
117
|
+
- The version of `assistant` you are using.
|
|
118
|
+
- Your Ruby version.
|
|
119
|
+
- A minimal, runnable reproduction (ideally a service definition plus the
|
|
120
|
+
exact call that fails).
|
|
121
|
+
- The full `result` hash or backtrace.
|
|
122
|
+
|
|
123
|
+
For security-sensitive reports, follow [`SECURITY.md`](./SECURITY.md) instead
|
|
124
|
+
of the public tracker.
|
|
125
|
+
|
|
126
|
+
## Code of Conduct
|
|
127
|
+
|
|
128
|
+
Everyone interacting in this project's codebases, issue trackers, chat rooms,
|
|
129
|
+
and mailing lists is expected to follow the
|
|
130
|
+
[Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). Reports go to
|
|
131
|
+
`cerberus.ramon@gmail.com`.
|
data/Gemfile
CHANGED
|
@@ -6,3 +6,13 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
|
6
6
|
|
|
7
7
|
# Specify your gem's dependencies in assistant.gemspec
|
|
8
8
|
gemspec
|
|
9
|
+
|
|
10
|
+
# Documentation site (GitHub Pages) toolchain. Optional so regular
|
|
11
|
+
# contributors don't pull Jekyll on every `bundle install`. CI's Docs
|
|
12
|
+
# workflow installs this group via `BUNDLE_WITH=docs`. See
|
|
13
|
+
# `docs/v1/08-github-pages.md` and `Rakefile`'s `docs:*` tasks.
|
|
14
|
+
group :docs, optional: true do
|
|
15
|
+
gem 'jekyll', '~> 4.3'
|
|
16
|
+
gem 'jekyll-relative-links', '~> 0.7'
|
|
17
|
+
gem 'just-the-docs', '~> 0.10'
|
|
18
|
+
end
|