inquirex 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f99f7adba97c905c55490b57467fe062251719bbb98d698fe49e6dd04360fcf7
4
- data.tar.gz: 2c6850590153b96ee376ddaa0314c2de3cf729f6ea2d7c8f3d0ccb20d2359683
3
+ metadata.gz: 3422d46566a55c973dc712fb60918cde34029d3dc739e36bb1eb2c40110f1f25
4
+ data.tar.gz: eb6e54e12a407b21547ac97ca7153b93af516713c0dbcaa83a2323f909f8f816
5
5
  SHA512:
6
- metadata.gz: 18b95d28461f971551997bb177c6ead5c4165562f1d627460d58656440a0884bf0972eef2d97a31e3883f34720349be82e24e2fa567b7181eea0f14361182355
7
- data.tar.gz: ed93da41fe8d2f66098b0165224e472d794d6f7ab9313dd8ec9a8a80b0d299ef5623d957361d83a27680b1ce3c025dd294fc6690c855c02a32df90348726718c
6
+ metadata.gz: 42b3c45e598ebadec67d48e251605e74022cfe1ba0923c21b5998b0c0c2ef8005f2895e43c501dfd9a8613d05821a95653330c5395ff7755fa1892a74761873c
7
+ data.tar.gz: c85a9f013fb304ddfc7cbb9742a24167c7da7cefe18fa83425cfc83f1ac5184eeb7960a9ee3592d95ebd82890f7b9568aaea2b1eb488d8840a17106cbe091f3f
data/CHANGELOG.md CHANGED
@@ -3,20 +3,12 @@
3
3
  ## [0.6.0] - 2026-07-19
4
4
 
5
5
  - Post-completion `action` DSL verb with an extensible effect registry
6
- - `send_email` effect: builds `Mail::Message` objects (multipart text/HTML)
7
- from `{{field}}` templates into `Answers#outbox`; delivery is the host
8
- application's responsibility. The `mail` gem is a soft dependency.
6
+ - `send_email` effect: builds `Mail::Message` objects (multipart text/HTML) from `{{field}}` templates into `Answers#outbox`; delivery is the host application's responsibility. The `mail` gem is a soft dependency.
9
7
  - `run { |answers, outbox| ... }` escape-hatch effect (stripped from JSON)
10
- - `webhook` effect: POSTs the answers envelope to a static https URL whose
11
- host must be covered by the new top-level `allowed_domains` declaration;
12
- enforcement runs in `Definition#validate!`, so tampered JSON definitions
13
- fail at rehydration
8
+ - `webhook` effect: POSTs the answers envelope to a static https URL whose host must be covered by the new top-level `allowed_domains` declaration; enforcement runs in `Definition#validate!`, so tampered JSON definitions fail at rehydration
14
9
  - `Inquirex::Actions::Runner` and `Inquirex::Actions.run(definition, answers)`
15
10
  - Actions serialize to/from JSON with their rule gates
16
- - `CompletionMetadata` (OpenStruct; `engine` and `engine_version` required)
17
- stamped by the engine at flow completion, enrichable by front-ends via the
18
- new `Engine#after_completion` hook, persisted in engine state, and merged
19
- into answers by `Engine#answers_with_metadata`
11
+ - `CompletionMetadata` (OpenStruct; `engine` and `engine_version` required) stamped by the engine at flow completion, enrichable by front-ends via the new `Engine#after_completion` hook, persisted in engine state, and merged into answers by `Engine#answers_with_metadata`
20
12
 
21
13
  ## [0.1.0] - 2026-04-13
22
14
 
data/README.md CHANGED
@@ -31,7 +31,7 @@ It provides a rich DSL for creating dynamic user intake forms, with complexity r
31
31
  >
32
32
  > For technically inclined, Inquirex turns user forms into a directed graph, where nodes are either questions or statements (or UI transitions), while edges are AST-based logical conditions that can be stacked and joined in arbitrarily complex ways, allowing you to move from one question to any other based on the previous answer. See the details below.
33
33
 
34
- ***Don't want to deal with figuring it out? Head to [Qualified.At](https://qualified.at/onboarding) and walk through the demo onboarding form, that exists specifically to show you how quickly you can have the same conceptually on your site, tailored to YOUR users.***
34
+ ***Don't want to deal with figuring it out? Head to [Qualified.At](https://qualified.at) and walk through the demo onboarding form, that exists specifically to show you how quickly you can have the same conceptually on your site, tailored to YOUR users.***
35
35
 
36
36
  ## Summary
37
37
 
@@ -435,11 +435,7 @@ Behavior:
435
435
  - Use `finished?` to detect completion
436
436
  - Use `total(:price)` / `totals` to read running totals
437
437
  - Use `to_state` / `.from_state` for persistence/resume (totals included)
438
- - Use `prefill!(hash)` to merge externally-supplied answers into the state,
439
- e.g. fields extracted by an LLM from a free-text answer (see
440
- [inquirex-llm](#extension-gems)). Existing answers are preserved; `nil`
441
- and empty values are ignored so they don't spuriously satisfy
442
- `not_empty` rules. The engine auto-advances past any newly-skippable step.
438
+ - Use `prefill!(hash)` to merge externally-supplied answers into the state, e.g. fields extracted by an LLM from a free-text answer (see [inquirex-llm](#extension-gems)). Existing answers are preserved; `nil` and empty values are ignored so they don't spuriously satisfy `not_empty` rules. The engine auto-advances past any newly-skippable step.
443
439
 
444
440
  ```ruby
445
441
  engine = Inquirex::Engine.new(definition)
@@ -468,10 +464,7 @@ engine = Inquirex::Engine.new(definition, validator: my_validator)
468
464
 
469
465
  ## Completion Metadata
470
466
 
471
- When a flow finishes, the engine guarantees a `CompletionMetadata` — an
472
- OpenStruct describing how the answers were collected. Only `engine` and
473
- `engine_version` are required members; rendering front-ends attach richer
474
- environment details from an `after_completion` hook:
467
+ When a flow finishes, the engine guarantees a `CompletionMetadata` — an OpenStruct describing how the answers were collected. Only `engine` and `engine_version` are required members; rendering front-ends attach richer environment details from an `after_completion` hook:
475
468
 
476
469
  ```ruby
477
470
  engine.after_completion do |eng|
@@ -485,21 +478,11 @@ engine.after_completion do |eng|
485
478
  end
486
479
  ```
487
480
 
488
- If no hook supplies one, the engine stamps the minimal core version
489
- (`engine: "inquirex"`, `engine_version: Inquirex::VERSION`). The metadata
490
- persists through `Engine#to_state` / `Engine.from_state`, and
491
- `engine.answers_with_metadata` merges it into the answers hash under
492
- `:completion_metadata` — which also makes it available to post-completion
493
- actions: webhook payloads carry it, and email templates can interpolate
494
- `{{completion_metadata.engine}}`.
481
+ If no hook supplies one, the engine stamps the minimal core version (`engine: "inquirex"`, `engine_version: Inquirex::VERSION`). The metadata persists through `Engine#to_state` / `Engine.from_state`, and `engine.answers_with_metadata` merges it into the answers hash under `:completion_metadata` — which also makes it available to post-completion actions: webhook payloads carry it, and email templates can interpolate `{{completion_metadata.engine}}`.
495
482
 
496
483
  ## Post-Completion Actions
497
484
 
498
- After all questions are answered, `action` declarations run server-side with
499
- the collected answers. The flagship effect is `send_email`, which **builds**
500
- `Mail::Message` objects (the same object ActionMailer wraps) and attaches them
501
- to `answers.outbox` — **nothing is delivered**; the host application decides
502
- how and when to send.
485
+ After all questions are answered, `action` declarations run server-side with the collected answers. The flagship effect is `send_email`, which **builds** `Mail::Message` objects (the same object ActionMailer wraps) and attaches them to `answers.outbox` — **nothing is delivered**; the host application decides how and when to send.
503
486
 
504
487
  ```ruby
505
488
  Inquirex.define id: "tax-intake-2025" do
@@ -554,31 +537,14 @@ end
554
537
 
555
538
  Key semantics:
556
539
 
557
- - **Templating is `{{field}}` interpolation only** — dot-notation keys resolved
558
- against `Answers#to_flat_h`, deliberately inert (no code execution), so
559
- definitions stored in a database render safely. Values interpolated into
560
- `html:` bodies are HTML-escaped automatically; `text:` bodies stay verbatim.
561
- The built-in `{{answers_summary}}` expands to all collected answers.
562
- - **`if:` gates** reuse the serializable rule AST (`not_empty(:email)`, ...).
563
- A false rule records `:skipped` — declare no actions (or gate them) when a
564
- flow should only save answers.
565
- - **`run { |answers, outbox| ... }`** is the full-Ruby escape hatch; like all
566
- lambdas it is stripped from JSON.
567
- - **Failures are isolated**: a raising effect records `:failed` in
568
- `outbox.results` and never blocks other actions.
540
+ - **Templating is `{{field}}` interpolation only** — dot-notation keys resolved against `Answers#to_flat_h`, deliberately inert (no code execution), so definitions stored in a database render safely. Values interpolated into `html:` bodies are HTML-escaped automatically; `text:` bodies stay verbatim. The built-in `{{answers_summary}}` expands to all collected answers.
541
+ - **`if:` gates** reuse the serializable rule AST (`not_empty(:email)`, ...). A false rule records `:skipped` — declare no actions (or gate them) when a flow should only save answers.
542
+ - **`run { |answers, outbox| ... }`** is the full-Ruby escape hatch; like all lambdas it is stripped from JSON.
543
+ - **Failures are isolated**: a raising effect records `:failed` in `outbox.results` and never blocks other actions.
569
544
  - **Images** in HTML bodies must be external URLs; attachments are unsupported.
570
- - The `mail` gem is a soft dependency, needed only when a message is built
571
- (Rails hosts already have it via ActionMailer).
572
- - **`webhook url:`** POSTs `{"answers": {...}}` as JSON to a static URL. The
573
- URL's host must be covered by `allowed_domains`, declared at the top of the
574
- definition so the flow's egress surface is auditable at a glance. The check
575
- runs inside `Definition.new` — a JSON definition whose webhook URL was
576
- tampered with fails at *rehydration*, before anything executes. Also
577
- enforced: https only (plain http just for localhost), no userinfo, no
578
- `{{field}}` templates in URLs (the destination must be static), redirects
579
- are not followed, and non-2xx responses record `:failed`.
580
- - Effects are extensible: `Inquirex::Actions.register(:save_record, MyEffect)`
581
- gives a new verb both DSL and JSON wire support.
545
+ - The `mail` gem is a soft dependency, needed only when a message is built (Rails hosts already have it via ActionMailer).
546
+ - **`webhook url:`** POSTs `{"answers": {...}}` as JSON to a static URL. The URL's host must be covered by `allowed_domains`, declared at the top of the definition so the flow's egress surface is auditable at a glance. The check runs inside `Definition.new` — a JSON definition whose webhook URL was tampered with fails at *rehydration*, before anything executes. Also enforced: https only (plain http just for localhost), no userinfo, no `{{field}}` templates in URLs (the destination must be static), redirects are not followed, and non-2xx responses record `:failed`.
547
+ - Effects are extensible: `Inquirex::Actions.register(:save_record, MyEffect)` gives a new verb both DSL and JSON wire support.
582
548
 
583
549
  ## Serialization
584
550
 
@@ -597,11 +563,8 @@ Serialized structure includes:
597
563
  - Steps and transitions
598
564
  - Rule AST payloads
599
565
  - Widget hints
600
- - Post-completion actions (`actions`) with their rules and effects; `run`
601
- blocks are stripped, and an action left with no serializable effects is
602
- omitted entirely
603
- - The `allowed_domains` egress allowlist, re-enforced against webhook URLs
604
- every time a definition is rehydrated
566
+ - Post-completion actions (`actions`) with their rules and effects; `run` blocks are stripped, and an action left with no serializable effects is omitted entirely
567
+ - The `allowed_domains` egress allowlist, re-enforced against webhook URLs every time a definition is rehydrated
605
568
 
606
569
  Important serialization details:
607
570
 
data/lib/.DS_Store ADDED
Binary file
@@ -11,6 +11,7 @@ module Inquirex
11
11
  history: ->(v) { Array(v).map { |e| e&.to_sym } },
12
12
  answers: ->(v) { symbolize_answers(v) },
13
13
  totals: ->(v) { symbolize_answers(v) },
14
+ suggestions: ->(v) { symbolize_answers(v) },
14
15
  completion_metadata: ->(v) { symbolize_answers(v) }
15
16
  }.freeze
16
17
 
@@ -21,6 +21,15 @@ module Inquirex
21
21
  # @return [CompletionMetadata, nil] nil until the flow finishes
22
22
  attr_accessor :completion_metadata
23
23
 
24
+ # Answer suggestions produced by prefill! for multi-select steps, keyed by
25
+ # step id. A suggestion pre-populates the step's choices in a renderer but
26
+ # — unlike an answer — never satisfies skip_if rules: multi-select
27
+ # extraction is treated as a hint the user confirms and may extend, not a
28
+ # deterministic fact. Cleared per step once the user answers it.
29
+ #
30
+ # @return [Hash{Symbol => Array}]
31
+ attr_reader :suggestions
32
+
24
33
  # Exceptions raised by after_completion hooks, in the order they were
25
34
  # raised. Hooks are isolated from one another, so a raising hook is
26
35
  # recorded here rather than propagated — callers that care can inspect
@@ -41,6 +50,7 @@ module Inquirex
41
50
  @completion_metadata = nil
42
51
  @after_completion_hooks = []
43
52
  @completion_hook_errors = []
53
+ @suggestions = {}
44
54
  @history << @current_step_id
45
55
  skip_display_steps_if_needed
46
56
  end
@@ -81,6 +91,7 @@ module Inquirex
81
91
  raise Errors::ValidationError, "Validation failed: #{result.errors.join(", ")}" unless result.valid?
82
92
 
83
93
  @answers[@current_step_id] = value
94
+ @suggestions.delete(@current_step_id)
84
95
  apply_accumulations(current_step, value)
85
96
  advance_step
86
97
  end
@@ -115,12 +126,28 @@ module Inquirex
115
126
  next if value.respond_to?(:empty?) && value.empty?
116
127
 
117
128
  sym = key.to_sym
118
- @answers[sym] = value unless @answers.key?(sym)
129
+ if multi_select_step?(sym)
130
+ # Multi-select extraction is a hint, not a fact: the user may have
131
+ # more selections in mind than the text revealed. Record it as a
132
+ # suggestion so renderers pre-check the choices while the question
133
+ # is still asked; skip_if rules see no answer and do not fire.
134
+ @suggestions[sym] = Array(value) unless @answers.key?(sym)
135
+ else
136
+ @answers[sym] = value unless @answers.key?(sym)
137
+ end
119
138
  end
120
139
  skip_if_needed unless finished?
121
140
  @answers
122
141
  end
123
142
 
143
+ # The prefill suggestion for a step, or nil when none was recorded.
144
+ #
145
+ # @param step_id [Symbol, String] step id
146
+ # @return [Array, nil] suggested selections for a multi-select step
147
+ def suggestion_for(step_id)
148
+ @suggestions[step_id.to_sym]
149
+ end
150
+
124
151
  # Registers a hook to run when the flow finishes. The block receives the
125
152
  # engine; front-ends typically use it to attach a rich
126
153
  # completion_metadata (host, user, ips, terminal, ...). Optional — after
@@ -163,6 +190,7 @@ module Inquirex
163
190
  answers: @answers,
164
191
  history: @history,
165
192
  totals: @totals,
193
+ suggestions: @suggestions,
166
194
  completion_metadata: @completion_metadata&.to_h
167
195
  }
168
196
  end
@@ -202,6 +230,16 @@ module Inquirex
202
230
  @completion_metadata = CompletionMetadata.from_h(state[:completion_metadata])
203
231
  @after_completion_hooks = []
204
232
  @completion_hook_errors = []
233
+ @suggestions = state[:suggestions] || {}
234
+ end
235
+
236
+ # Whether +step_id+ names a multi-select step in the definition. Unknown
237
+ # ids (e.g. an extract schema field that matches no step) are not.
238
+ #
239
+ # @param step_id [Symbol]
240
+ # @return [Boolean]
241
+ def multi_select_step?(step_id)
242
+ @definition.step_ids.include?(step_id) && @definition.step(step_id).type == :multi_enum
205
243
  end
206
244
 
207
245
  def init_totals
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Inquirex
4
- VERSION = "0.6.0"
4
+ VERSION = "0.6.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inquirex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -36,23 +36,10 @@ executables: []
36
36
  extensions: []
37
37
  extra_rdoc_files: []
38
38
  files:
39
- - ".relaxed_rubocop.yml"
40
- - ".rub"
41
- - ".ruby-version"
42
- - ".secrets.baseline"
43
- - Brewfile
44
39
  - CHANGELOG.md
45
40
  - LICENSE.txt
46
41
  - README.md
47
- - Rakefile
48
- - docs/badges/coverage_badge.svg
49
- - examples/01_readme_example.rb
50
- - examples/02_readme_mermaid.png
51
- - examples/02_readme_mermaid.rb
52
- - examples/03_send_email_actions.rb
53
- - examples/README.md
54
- - justfile
55
- - lefthook.yml
42
+ - lib/.DS_Store
56
43
  - lib/inquirex.rb
57
44
  - lib/inquirex/accumulator.rb
58
45
  - lib/inquirex/actions.rb
@@ -92,7 +79,6 @@ files:
92
79
  - lib/inquirex/version.rb
93
80
  - lib/inquirex/widget_hint.rb
94
81
  - lib/inquirex/widget_registry.rb
95
- - sig/inquirex.rbs
96
82
  homepage: https://github.com/inquirex/inquirex
97
83
  licenses:
98
84
  - MIT
data/.relaxed_rubocop.yml DELETED
@@ -1,153 +0,0 @@
1
- # Relaxed.Ruby.Style
2
- ## Version 2.5
3
-
4
- Style/Alias:
5
- Enabled: false
6
- StyleGuide: https://relaxed.ruby.style/#stylealias
7
-
8
- Style/AsciiComments:
9
- Enabled: false
10
- StyleGuide: https://relaxed.ruby.style/#styleasciicomments
11
-
12
- Style/BeginBlock:
13
- Enabled: false
14
- StyleGuide: https://relaxed.ruby.style/#stylebeginblock
15
-
16
- Style/BlockDelimiters:
17
- Enabled: false
18
- StyleGuide: https://relaxed.ruby.style/#styleblockdelimiters
19
-
20
- Style/CommentAnnotation:
21
- Enabled: false
22
- StyleGuide: https://relaxed.ruby.style/#stylecommentannotation
23
-
24
- Style/Documentation:
25
- Enabled: false
26
- StyleGuide: https://relaxed.ruby.style/#styledocumentation
27
-
28
- Layout/DotPosition:
29
- Enabled: false
30
- StyleGuide: https://relaxed.ruby.style/#layoutdotposition
31
-
32
- Style/DoubleNegation:
33
- Enabled: false
34
- StyleGuide: https://relaxed.ruby.style/#styledoublenegation
35
-
36
- Style/EndBlock:
37
- Enabled: false
38
- StyleGuide: https://relaxed.ruby.style/#styleendblock
39
-
40
- Style/FormatString:
41
- Enabled: false
42
- StyleGuide: https://relaxed.ruby.style/#styleformatstring
43
-
44
- Style/IfUnlessModifier:
45
- Enabled: false
46
- StyleGuide: https://relaxed.ruby.style/#styleifunlessmodifier
47
-
48
- Style/Lambda:
49
- Enabled: false
50
- StyleGuide: https://relaxed.ruby.style/#stylelambda
51
-
52
- Style/ModuleFunction:
53
- Enabled: false
54
- StyleGuide: https://relaxed.ruby.style/#stylemodulefunction
55
-
56
- Style/MultilineBlockChain:
57
- Enabled: false
58
- StyleGuide: https://relaxed.ruby.style/#stylemultilineblockchain
59
-
60
- Style/NegatedIf:
61
- Enabled: false
62
- StyleGuide: https://relaxed.ruby.style/#stylenegatedif
63
-
64
- Style/NegatedWhile:
65
- Enabled: false
66
- StyleGuide: https://relaxed.ruby.style/#stylenegatedwhile
67
-
68
- Style/NumericPredicate:
69
- Enabled: false
70
- StyleGuide: https://relaxed.ruby.style/#stylenumericpredicate
71
-
72
- Style/ParallelAssignment:
73
- Enabled: false
74
- StyleGuide: https://relaxed.ruby.style/#styleparallelassignment
75
-
76
- Style/PercentLiteralDelimiters:
77
- Enabled: false
78
- StyleGuide: https://relaxed.ruby.style/#stylepercentliteraldelimiters
79
-
80
- Style/PerlBackrefs:
81
- Enabled: false
82
- StyleGuide: https://relaxed.ruby.style/#styleperlbackrefs
83
-
84
- Style/Semicolon:
85
- Enabled: false
86
- StyleGuide: https://relaxed.ruby.style/#stylesemicolon
87
-
88
- Style/SignalException:
89
- Enabled: false
90
- StyleGuide: https://relaxed.ruby.style/#stylesignalexception
91
-
92
- Style/SingleLineBlockParams:
93
- Enabled: false
94
- StyleGuide: https://relaxed.ruby.style/#stylesinglelineblockparams
95
-
96
- Style/SingleLineMethods:
97
- Enabled: false
98
- StyleGuide: https://relaxed.ruby.style/#stylesinglelinemethods
99
-
100
- Layout/SpaceBeforeBlockBraces:
101
- Enabled: false
102
- StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces
103
-
104
- Layout/SpaceInsideParens:
105
- Enabled: false
106
- StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens
107
-
108
- Style/SpecialGlobalVars:
109
- Enabled: false
110
- StyleGuide: https://relaxed.ruby.style/#stylespecialglobalvars
111
-
112
- Style/StringLiterals:
113
- Enabled: false
114
- StyleGuide: https://relaxed.ruby.style/#stylestringliterals
115
-
116
- Style/TrailingCommaInArguments:
117
- Enabled: false
118
- StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarguments
119
-
120
- Style/TrailingCommaInArrayLiteral:
121
- Enabled: false
122
- StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarrayliteral
123
-
124
- Style/TrailingCommaInHashLiteral:
125
- Enabled: false
126
- StyleGuide: https://relaxed.ruby.style/#styletrailingcommainhashliteral
127
-
128
- Style/SymbolArray:
129
- Enabled: false
130
- StyleGuide: http://relaxed.ruby.style/#stylesymbolarray
131
-
132
- Style/WhileUntilModifier:
133
- Enabled: false
134
- StyleGuide: https://relaxed.ruby.style/#stylewhileuntilmodifier
135
-
136
- Style/WordArray:
137
- Enabled: false
138
- StyleGuide: https://relaxed.ruby.style/#stylewordarray
139
-
140
- Lint/AmbiguousRegexpLiteral:
141
- Enabled: false
142
- StyleGuide: https://relaxed.ruby.style/#lintambiguousregexpliteral
143
-
144
- Lint/AssignmentInCondition:
145
- Enabled: false
146
- StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
147
-
148
- Layout/LineLength:
149
- Enabled: false
150
-
151
- Metrics:
152
- Enabled: false
153
-
data/.rub DELETED
File without changes
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 4.0.5
data/.secrets.baseline DELETED
@@ -1,127 +0,0 @@
1
- {
2
- "version": "1.5.0",
3
- "plugins_used": [
4
- {
5
- "name": "ArtifactoryDetector"
6
- },
7
- {
8
- "name": "AWSKeyDetector"
9
- },
10
- {
11
- "name": "AzureStorageKeyDetector"
12
- },
13
- {
14
- "name": "Base64HighEntropyString",
15
- "limit": 4.5
16
- },
17
- {
18
- "name": "BasicAuthDetector"
19
- },
20
- {
21
- "name": "CloudantDetector"
22
- },
23
- {
24
- "name": "DiscordBotTokenDetector"
25
- },
26
- {
27
- "name": "GitHubTokenDetector"
28
- },
29
- {
30
- "name": "GitLabTokenDetector"
31
- },
32
- {
33
- "name": "HexHighEntropyString",
34
- "limit": 3.0
35
- },
36
- {
37
- "name": "IbmCloudIamDetector"
38
- },
39
- {
40
- "name": "IbmCosHmacDetector"
41
- },
42
- {
43
- "name": "IPPublicDetector"
44
- },
45
- {
46
- "name": "JwtTokenDetector"
47
- },
48
- {
49
- "name": "KeywordDetector",
50
- "keyword_exclude": ""
51
- },
52
- {
53
- "name": "MailchimpDetector"
54
- },
55
- {
56
- "name": "NpmDetector"
57
- },
58
- {
59
- "name": "OpenAIDetector"
60
- },
61
- {
62
- "name": "PrivateKeyDetector"
63
- },
64
- {
65
- "name": "PypiTokenDetector"
66
- },
67
- {
68
- "name": "SendGridDetector"
69
- },
70
- {
71
- "name": "SlackDetector"
72
- },
73
- {
74
- "name": "SoftlayerDetector"
75
- },
76
- {
77
- "name": "SquareOAuthDetector"
78
- },
79
- {
80
- "name": "StripeDetector"
81
- },
82
- {
83
- "name": "TelegramBotTokenDetector"
84
- },
85
- {
86
- "name": "TwilioKeyDetector"
87
- }
88
- ],
89
- "filters_used": [
90
- {
91
- "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
92
- },
93
- {
94
- "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
95
- "min_level": 2
96
- },
97
- {
98
- "path": "detect_secrets.filters.heuristic.is_indirect_reference"
99
- },
100
- {
101
- "path": "detect_secrets.filters.heuristic.is_likely_id_string"
102
- },
103
- {
104
- "path": "detect_secrets.filters.heuristic.is_lock_file"
105
- },
106
- {
107
- "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
108
- },
109
- {
110
- "path": "detect_secrets.filters.heuristic.is_potential_uuid"
111
- },
112
- {
113
- "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
114
- },
115
- {
116
- "path": "detect_secrets.filters.heuristic.is_sequential_string"
117
- },
118
- {
119
- "path": "detect_secrets.filters.heuristic.is_swagger_file"
120
- },
121
- {
122
- "path": "detect_secrets.filters.heuristic.is_templated_secret"
123
- }
124
- ],
125
- "results": {},
126
- "generated_at": "2026-04-13T21:29:03Z"
127
- }
data/Brewfile DELETED
@@ -1,7 +0,0 @@
1
- # Please run `brew bundle` before running `lefthook install`
2
-
3
- brew 'lefthook'
4
- brew 'codespell'
5
- brew 'mdformat'
6
- brew 'detect-secrets'
7
-
data/Rakefile DELETED
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
- require "timeout"
6
- require "yard"
7
-
8
- def shell(*args)
9
- puts "running: #{args.join(" ")}"
10
- system(args.join(" "))
11
- end
12
-
13
- task :clean do
14
- shell("rm -rf pkg/ tmp/ coverage/ doc/ ")
15
- end
16
-
17
- task gem: [:build] do
18
- shell("gem install pkg/*")
19
- end
20
-
21
- task permissions: [:clean] do
22
- shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
23
- shell("find . -type d -exec chmod o+x,g+x {} \\;")
24
- end
25
-
26
- task build: :permissions
27
-
28
- YARD::Rake::YardocTask.new(:doc) do |t|
29
- t.files = %w[lib/**/*.rb exe/*.rb - README.md LICENSE.txt CHANGELOG.md]
30
- t.options.unshift("--title", '"FlowEngine — DSL + AST for buildiong complex flows in Ruby."')
31
- t.after = -> { exec("open doc/index.html") } if RUBY_PLATFORM =~ /darwin/
32
- end
33
-
34
- RSpec::Core::RakeTask.new(:spec)
35
-
36
- task default: :spec
@@ -1,21 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
3
- <linearGradient id="b" x2="0" y2="100%">
4
- <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
5
- <stop offset="1" stop-opacity=".1"/>
6
- </linearGradient>
7
- <mask id="a">
8
- <rect width="99" height="20" rx="3" fill="#fff"/>
9
- </mask>
10
- <g mask="url(#a)">
11
- <path fill="#555" d="M0 0h63v20H0z"/>
12
- <path fill="#4c1" d="M63 0h36v20H63z"/>
13
- <path fill="url(#b)" d="M0 0h99v20H0z"/>
14
- </g>
15
- <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
16
- <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
17
- <text x="31.5" y="14">coverage</text>
18
- <text x="80" y="15" fill="#010101" fill-opacity=".3">96%</text>
19
- <text x="80" y="14">96%</text>
20
- </g>
21
- </svg>
@@ -1,55 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "inquirex"
6
-
7
- DEFINITION = Inquirex.define id: "tax-intake-2025", version: "1.0.0" do
8
- meta title: "Tax Preparation Intake", subtitle: "Let's understand your situation"
9
- start :filing_status
10
-
11
- ask :filing_status do
12
- type :enum
13
- question "What is your filing status?"
14
- options single: "Single", married_jointly: "Married Filing Jointly"
15
- widget target: :desktop, type: :radio_group, columns: 2
16
- widget target: :mobile, type: :dropdown
17
- transition to: :dependents
18
- end
19
-
20
- ask :dependents do
21
- type :integer
22
- question "How many dependents?"
23
- default 0
24
- transition to: :business_income
25
- end
26
-
27
- confirm :business_income do
28
- question "Do you have business income?"
29
- transition to: :business_count, if_rule: equals(:business_income, true)
30
- transition to: :done
31
- end
32
-
33
- ask :business_count do
34
- type :integer
35
- question "How many businesses?"
36
- transition to: :done
37
- end
38
-
39
- say :done do
40
- text "Thanks for completing the intake."
41
- end
42
- end
43
-
44
- engine = Inquirex::Engine.new(DEFINITION)
45
-
46
- engine.answer("single") # filing_status
47
- engine.answer(2) # dependents
48
- engine.answer(false) # business_income
49
- engine.advance # done (display step)
50
- engine.finished? # => true
51
-
52
- if __FILE__ == $0
53
- require 'json'
54
- puts JSON.pretty_generate(DEFINITION.to_h)
55
- end
Binary file
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require_relative '01_readme_example'
4
- require 'inquirex'
5
- require 'stringio'
6
-
7
- output = StringIO.new
8
- exporter = Inquirex::Graph::MermaidExporter.new(DEFINITION)
9
- output << exporter.export
10
-
11
- source = <<~HTML
12
- <html>
13
- <head>
14
- <meta charset="utf-8">
15
- </head>
16
- <body>
17
- <meta charset="utf-8">
18
- <div class="mermaid">
19
- #{output.string}
20
- </div>
21
- <script type="module">
22
- import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
23
- mermaid.initialize({ startOnLoad: true });
24
- </script>
25
- </html>
26
- HTML
27
-
28
- if __FILE__ == $0
29
- File.write('mermaid.html', source)
30
- system("open mermaid.html")
31
- sleep 10
32
-
33
- at_exit do
34
- File.delete('mermaid.html')
35
- end
36
- end
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 03: Post-completion actions building emails into the outbox.
5
- #
6
- # The `action` verb runs server-side after the flow finishes. send_email
7
- # builds Mail::Message objects — nothing is delivered here; a host Rails app
8
- # would iterate answers.outbox and deliver via its own ActionMailer config.
9
- #
10
- # Run:
11
- # bundle exec ruby examples/03_send_email_actions.rb
12
-
13
- require "bundler/setup"
14
- require "inquirex"
15
-
16
- DEFINITION = Inquirex.define id: "lead-intake", version: "1.0.0" do
17
- # Egress allowlist: webhook effects may only POST to these hosts.
18
- allowed_domains "*.agentica.group"
19
-
20
- meta title: "Lead Intake", subtitle: "Tell us about your project"
21
- start :name
22
-
23
- ask :name do
24
- type :string
25
- question "What is your name?"
26
- transition to: :email
27
- end
28
-
29
- ask :email do
30
- type :email
31
- question "Where can we reach you? (leave blank to skip the receipt)"
32
- transition to: :budget
33
- end
34
-
35
- ask :budget do
36
- type :currency
37
- question "What is your approximate budget?"
38
- end
39
-
40
- # Sent only when the visitor left an email address.
41
- action :client_receipt, if: not_empty(:email) do
42
- send_email to: "{{email}}",
43
- from: "forms@agentica.group",
44
- subject: "Thanks {{name}} — we got your inquiry",
45
- text: <<~TEXT,
46
- Hi {{name}},
47
-
48
- We received your answers and will reply within one business day.
49
-
50
- {{answers_summary}}
51
- TEXT
52
- html: <<~HTML
53
- <p>Hi {{name}},</p>
54
- <p>We received your answers and will reply within one business day.</p>
55
- {{answers_summary}}
56
- HTML
57
- end
58
-
59
- # Always notify the site owner, and demonstrate the Ruby escape hatch.
60
- action :admin_alert do
61
- send_email to: "owner@agentica.group",
62
- from: "forms@agentica.group",
63
- subject: "New lead: {{name}} (budget {{budget}})",
64
- html: "{{answers_summary}}"
65
- run { |answers, _outbox| puts ">> run{} saw budget: #{answers.budget}" }
66
- end
67
-
68
- # Webhook to an allowed_domains host. Gated by an always-false rule so this
69
- # example runs offline — it records :skipped instead of POSTing. Drop the
70
- # if: to exercise it for real.
71
- action :crm_push, if: equals(:name, "__network_demo__") do
72
- webhook url: "https://hooks.agentica.group/inquirex",
73
- headers: { "X-Api-Key" => "demo" }
74
- end
75
- end
76
-
77
- answers = Inquirex::Actions.run(
78
- DEFINITION,
79
- name: "Ada Lovelace",
80
- email: "ada@lovelace.io",
81
- budget: 12_500.00
82
- )
83
-
84
- puts "Built #{answers.outbox.size} message(s):"
85
- answers.outbox.each do |mail|
86
- puts "-" * 60
87
- puts "To: #{mail.to.join(", ")}"
88
- puts "Subject: #{mail.subject}"
89
- puts "Parts: #{mail.multipart? ? "text + html" : mail.content_type}"
90
- end
91
-
92
- puts "-" * 60
93
- puts "Results: #{answers.outbox.results.map { |r| "#{r.action_id}=#{r.status}" }.join(", ")}"
94
-
95
- # The definition (rules and all) round-trips through JSON — run{} is stripped:
96
- restored = Inquirex::Definition.from_json(DEFINITION.to_json)
97
- puts "Actions after JSON round-trip: #{restored.actions.map(&:id).inspect}"
data/examples/README.md DELETED
@@ -1,33 +0,0 @@
1
- # Examples
2
-
3
- This folder contains just a couple examples to demonstrate the API and the functionality.
4
-
5
- For a fuller list of more comprehensive examples, please see the [examples in the `inquirex-tty` gem](https://github.com/inquirex/inquirex-tty/tree/main/examples).
6
-
7
- ## Running Examples
8
-
9
- These examples are meant to be run as executables from the root level of the gem folder.
10
-
11
- ### First Example
12
-
13
- This example should print to stdout the DSL definition in JSON:
14
-
15
- ```bash
16
- # First example — creates a DSL definition and prints it to stdout in JSON format
17
- ./examples/01_readme_example.rb | jq
18
- ```
19
-
20
- ### Second Example
21
-
22
- This is a more interesting example. It builds on the first, takes that definition and uses the mermaid converter to write a tiny HTML file with a mermaid diagram inside, which represents the definition defined in the first file.
23
-
24
- ```bash
25
- # creates a mermaid.html file at the root of the project
26
- # opens the browser with the file and waits 10 seconds
27
- # deletes the HTML file and exits
28
- ./examples/02_readme_mermaid.rb
29
- ```
30
-
31
- The mermaid file generated should look like this:
32
-
33
- <img src="02_readme_mermaid.png" width="300px" text-align="left"></img>
data/justfile DELETED
@@ -1,70 +0,0 @@
1
- # Tell 'just' to run bash, source our setup script, then execute the recipe
2
- set shell := ["bash", "-c"]
3
-
4
- version := `grep VERSION lib/inquirex/version.rb | awk '{print $3}' | tr -d '"' | tr -d '\n'`
5
- rbenv := 'eval "$(rbenv init bash)"; bundle exec '
6
- repo := 'git@github.com:inquirex/inquirex.git'
7
-
8
- [no-exit-message]
9
- recipes:
10
- just --choose
11
-
12
- # Sync all dependencies
13
- install:
14
- bin/setup
15
-
16
- build: install
17
-
18
- # Lint and reformat files
19
- lint:
20
- {{ rbenv }} rubocop
21
-
22
- # Lint and reformat files (-a) — pass -A as an argument
23
- format *args:
24
- {{ rbenv }} rubocop -a {{ args }}
25
-
26
- # Run all the tests
27
- test *args:
28
- export ENVIRONMENT=test; {{ rbenv }} rspec {{args}}
29
-
30
- # Run tests with coverage
31
- test-coverage *args:
32
- export ENVIRONMENT=test; export COVERAGE=true; {{ rbenv }} rspec {{ args }}
33
-
34
- ci: lint test-coverage
35
-
36
- alias check-all := ci
37
-
38
- clean:
39
- #!/usr/bin/env bash
40
- @find . -name .DS_Store -delete -print || true
41
- @rm -rf tmp/*
42
-
43
- # Run all lefthook pre-commit hooks
44
- lefthook:
45
- {{ rbenv }} lefthook run pre-commit --all-files
46
-
47
- # Print current gem version
48
- version:
49
- @echo "{{ version }}"
50
-
51
- # Clobber
52
- clobber:
53
- {{ rbenv }} rake clobber
54
-
55
- # Generate documentation
56
- doc:
57
- #!/usr/bin/env bash
58
- {{ rbenv }} rake doc
59
-
60
- # Create
61
- publish: build
62
- {{ rbenv }} rake release[remote]
63
-
64
- # Tag v{{ version }}, publish the GH release, & refresh the Homebrew tap.
65
- release:
66
- git fetch --tags
67
- git tag -f "v{{ version }}"
68
- git push -f --tags
69
- gh release delete -y "v{{ version }}" --repo {{ repo }} 2>/dev/null || true
70
- gh release create "v{{ version }}" --generate-notes --repo {{ repo }}
data/lefthook.yml DELETED
@@ -1,35 +0,0 @@
1
- output:
2
- - summary
3
- - failure
4
-
5
- pre-commit:
6
- parallel: true
7
- jobs:
8
- - name: lint
9
- run: bundle exec rubocop -c .rubocop.yml {staged_files}
10
- glob: "*.{rb,Gemfile}"
11
- stage_fixed: true
12
-
13
- - name: check for conflict markers and whitespace issues
14
- run: git --no-pager diff --check
15
-
16
- # If tests take >1 second, move this (or just the long-running tests) to pre-push.
17
- - name: run tests
18
- run: just test
19
-
20
- - name: fix rubocop formatting issues
21
- run: bundle exec rubocop -a {staged_files}
22
- glob: "*.{rb,Gemfile,gemspec}"
23
- stage_fixed: true
24
-
25
- - name: spell check
26
- run: codespell {staged_files}
27
- glob: "*.{rb,md,gemspec}"
28
-
29
- - name: format markdown
30
- run: mdformat {staged_files}
31
- glob: "*.md"
32
- stage_fixed: true
33
-
34
- - name: scan for secrets
35
- run: detect-secrets-hook --baseline .secrets.baseline
data/sig/inquirex.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Inquirex
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end