inquirex 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -6
- data/README.md +1 -6
- data/lib/inquirex/completion_metadata.rb +1 -1
- data/lib/inquirex/engine.rb +113 -2
- data/lib/inquirex/headless.rb +111 -0
- data/lib/inquirex/version.rb +1 -1
- data/lib/inquirex.rb +1 -0
- metadata +103 -82
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7434e2fb6848ea3155939c7a1c49a6447652aa9ae08285bc1a3d3ed76d5019e
|
|
4
|
+
data.tar.gz: e01afc532faf0001fd6fb011a58c2f871a30e100581171df58a42eebab7be730
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: afa3f0101993f5ccfdf3e9a646bbd7fb8f6540cb93168f05e01541dccdf5ede38f5cf55922d09a3fe44e6551a7c9344b020394908a3ca3e094858f4eb1d17fa6
|
|
7
|
+
data.tar.gz: 248c2fc2a30480493e1b38ecefa3d619a8f533d7a641c5cc62acf04179b9e4c21761ee3cad6862c6ff34cb365ad9f3c94414b2c14f99194c633ae11895a3ec74
|
data/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v0.10.0](https://github.com/inquirex/inquirex/tree/v0.10.0) (2026-08-21)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/inquirex/inquirex/compare/v0.9.4...v0.10.0)
|
|
6
|
+
|
|
7
|
+
**Merged pull requests:**
|
|
8
|
+
|
|
9
|
+
- Set the lockstep version to 0.10.0 [#10](https://github.com/inquirex/inquirex/pull/10) ([kigster](https://github.com/kigster))
|
|
10
|
+
- Correct the lockstep set to six and bump to 0.9.6 [#9](https://github.com/inquirex/inquirex/pull/9) ([kigster](https://github.com/kigster))
|
|
11
|
+
- Numeric steps can declare bounds: min, max and step_size [#8](https://github.com/inquirex/inquirex/pull/8) ([kigster](https://github.com/kigster))
|
|
12
|
+
|
|
13
|
+
## [v0.9.4](https://github.com/inquirex/inquirex/tree/v0.9.4) (2026-08-06)
|
|
14
|
+
|
|
15
|
+
[Full Changelog](https://github.com/inquirex/inquirex/compare/v0.8.0...v0.9.4)
|
|
16
|
+
|
|
17
|
+
**Merged pull requests:**
|
|
18
|
+
|
|
19
|
+
- Validate DSL before eval, replace actions with send_email, fix extraction prefill [#6](https://github.com/inquirex/inquirex/pull/6) ([kigster](https://github.com/kigster))
|
|
20
|
+
|
|
21
|
+
## [v0.8.0](https://github.com/inquirex/inquirex/tree/v0.8.0) (2026-08-01)
|
|
22
|
+
|
|
23
|
+
[Full Changelog](https://github.com/inquirex/inquirex/compare/v0.7.0...v0.8.0)
|
|
24
|
+
|
|
3
25
|
## [v0.7.0](https://github.com/inquirex/inquirex/tree/v0.7.0) (2026-07-31)
|
|
4
26
|
|
|
5
27
|
[Full Changelog](https://github.com/inquirex/inquirex/compare/v0.6.1...v0.7.0)
|
|
6
28
|
|
|
7
29
|
**Merged pull requests:**
|
|
8
30
|
|
|
9
|
-
- Add required false and user-initiated Engine
|
|
31
|
+
- Add required false and user-initiated Engine#skip [#4](https://github.com/inquirex/inquirex/pull/4) ([kigster](https://github.com/kigster))
|
|
10
32
|
|
|
11
33
|
## [v0.6.1](https://github.com/inquirex/inquirex/tree/v0.6.1) (2026-07-20)
|
|
12
34
|
|
|
@@ -18,8 +40,8 @@
|
|
|
18
40
|
|
|
19
41
|
**Merged pull requests:**
|
|
20
42
|
|
|
21
|
-
- Support for actions such as email send [
|
|
22
|
-
- Add action DSL verb building emails into Answers
|
|
43
|
+
- Support for actions such as email send [#3](https://github.com/inquirex/inquirex/pull/3) ([kigster](https://github.com/kigster))
|
|
44
|
+
- Add action DSL verb building emails into Answers#outbox [#2](https://github.com/inquirex/inquirex/pull/2) ([kigster](https://github.com/kigster))
|
|
23
45
|
|
|
24
46
|
## [v0.5.0](https://github.com/inquirex/inquirex/tree/v0.5.0) (2026-07-16)
|
|
25
47
|
|
|
@@ -43,12 +65,10 @@
|
|
|
43
65
|
|
|
44
66
|
**Merged pull requests:**
|
|
45
67
|
|
|
46
|
-
- Add accumulators for pricing and scoring [
|
|
68
|
+
- Add accumulators for pricing and scoring [#1](https://github.com/inquirex/inquirex/pull/1) ([kigster](https://github.com/kigster))
|
|
47
69
|
|
|
48
70
|
## [v0.2.0](https://github.com/inquirex/inquirex/tree/v0.2.0) (2026-04-13)
|
|
49
71
|
|
|
50
72
|
[Full Changelog](https://github.com/inquirex/inquirex/compare/a07eeaaa4d459139164e97c622d1d3038b28a5f5...v0.2.0)
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
74
|
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/README.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
[](https://badge.fury.io/rb/inquirex) [](https://github.com/inquirex/inquirex/actions/workflows/main.yml) 
|
|
1
|
+
[](https://dl.circleci.com/status-badge/redirect/gh/inquirex/inquirex/tree/main) [](https://badge.fury.io/rb/inquirex) [](https://github.com/inquirex/inquirex/actions/workflows/main.yml) 
|
|
2
2
|
|
|
3
3
|
# Inquirex
|
|
4
4
|
|
|
5
5
|
`inquirex` family of libraries (part Ruby, part JavaScript), is a declarative, rules-driven questionnaire engine for building conditional intake forms, qualification wizards, and branching surveys, which can be AI-enabled, and rendered on your site inside a "copilot" widget window or in a TUI (Terminal UI) all the same.
|
|
6
6
|
|
|
7
7
|
> [!IMPORTANT]
|
|
8
|
-
>
|
|
9
8
|
> Today the ecosystem contains:
|
|
10
9
|
>
|
|
11
10
|
> - [`inquirex`](https://github.com/inquirex/inquirex): the base gem that defines the graph via DSL and provides most of the backend features
|
|
@@ -28,7 +27,6 @@ There are plenty of form building gems, hard-coded branching controllers, React
|
|
|
28
27
|
It provides a rich DSL for creating dynamic user intake forms, with complexity ranging from a simple straight-line forms to multi-branch, conditional forms with dozens of potential branches, *UI widget hints* for various rendering platforms, with *accumulators* that allow computing sums or products based on user's answers (which allow you to compute — for user or for yourself — that the service you are requesting will cost between $X & $Y).
|
|
29
28
|
|
|
30
29
|
> [!NOTE]
|
|
31
|
-
>
|
|
32
30
|
> 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
31
|
|
|
34
32
|
***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.***
|
|
@@ -67,7 +65,6 @@ So, if you ever wanted to ask users who arrive at your site a few simple questio
|
|
|
67
65
|
## Examples
|
|
68
66
|
|
|
69
67
|
> [!NOTE]
|
|
70
|
-
>
|
|
71
68
|
> There are a couple of examples shown in this `README` file, and also in the [`./examples`](./examples) folder. You can read about running them in a [`README.md`](./examples/README.md) inside that folder.
|
|
72
69
|
|
|
73
70
|
## Installation
|
|
@@ -374,7 +371,6 @@ engine.totals # => { price: 700.0, complexity: 4 }
|
|
|
374
371
|
Accumulators serialize predictably, keeping the contract with `inquirex-js` explicit.
|
|
375
372
|
|
|
376
373
|
> [!NOTE]
|
|
377
|
-
>
|
|
378
374
|
> You can convert between Ruby DSL and JSON using the [`inquirex-tty`](https://github.com/inquirex/inquirex-tty) gem.
|
|
379
375
|
|
|
380
376
|
```json
|
|
@@ -554,7 +550,6 @@ On the wire, step JSON carries `"required": false` (omitted when true, like othe
|
|
|
554
550
|
```
|
|
555
551
|
|
|
556
552
|
> [!NOTE]
|
|
557
|
-
>
|
|
558
553
|
> **`skip_if` is not the same thing.** A `skip_if` rule is *flow logic*: the definition elides the step from the path automatically, no default kicks in, and the step is **not** added to `engine.skipped` — it was never presented, so the user cannot have declined it. `Engine#skip` is a *user action* on a question that was presented. See [`docs/design/required-and-skip.md`](docs/design/required-and-skip.md) for the full design.
|
|
559
554
|
|
|
560
555
|
## Completion Metadata
|
|
@@ -32,7 +32,7 @@ module Inquirex
|
|
|
32
32
|
# @param engine [String] rendering front-end name (e.g. "inquirex-tty")
|
|
33
33
|
# @param engine_version [String] rendering front-end version
|
|
34
34
|
# @param extra [Hash] any additional, optional members
|
|
35
|
-
def initialize(engine:, engine_version:, **extra)
|
|
35
|
+
def initialize(engine:, engine_version:, **extra)
|
|
36
36
|
super
|
|
37
37
|
end
|
|
38
38
|
|
data/lib/inquirex/engine.rb
CHANGED
|
@@ -62,6 +62,8 @@ module Inquirex
|
|
|
62
62
|
@completion_hook_errors = []
|
|
63
63
|
@suggestions = {}
|
|
64
64
|
@skipped = []
|
|
65
|
+
@snapshots = []
|
|
66
|
+
@rewound_answers = {}
|
|
65
67
|
@history << @current_step_id
|
|
66
68
|
skip_display_steps_if_needed
|
|
67
69
|
end
|
|
@@ -119,6 +121,7 @@ module Inquirex
|
|
|
119
121
|
raise Errors::ValidationError, "Validation failed: #{result.errors.join(", ")}" unless result.valid?
|
|
120
122
|
|
|
121
123
|
node = current_step
|
|
124
|
+
push_snapshot
|
|
122
125
|
@answers[@current_step_id] = value
|
|
123
126
|
@suggestions.delete(@current_step_id)
|
|
124
127
|
apply_accumulations(node, value)
|
|
@@ -133,6 +136,7 @@ module Inquirex
|
|
|
133
136
|
raise Errors::AlreadyFinishedError, "Flow is already finished" if finished?
|
|
134
137
|
|
|
135
138
|
node = current_step
|
|
139
|
+
push_snapshot
|
|
136
140
|
capture_transcript(Transcript.display_entry(node)) if node.display?
|
|
137
141
|
advance_step
|
|
138
142
|
end
|
|
@@ -166,6 +170,7 @@ module Inquirex
|
|
|
166
170
|
if current_step.required?
|
|
167
171
|
|
|
168
172
|
node = current_step
|
|
173
|
+
push_snapshot
|
|
169
174
|
default = resolve_default(node)
|
|
170
175
|
unless default.nil?
|
|
171
176
|
@answers[@current_step_id] = default
|
|
@@ -177,6 +182,74 @@ module Inquirex
|
|
|
177
182
|
advance_step
|
|
178
183
|
end
|
|
179
184
|
|
|
185
|
+
# Undoes the user's last answer or skip: the engine returns to the most
|
|
186
|
+
# recent collecting step the user acted on, with answers, totals (including
|
|
187
|
+
# the :text transcript), skipped, suggestions, history and completion
|
|
188
|
+
# metadata exactly as they were before that step was answered.
|
|
189
|
+
#
|
|
190
|
+
# Display steps passed with #advance in between are stepped over, since
|
|
191
|
+
# there is nothing on them to change. Steps elided by skip_if or by a
|
|
192
|
+
# prefilled answer were never presented and so are never returned to.
|
|
193
|
+
#
|
|
194
|
+
# The undone answer stays available through #previous_answer, so a
|
|
195
|
+
# renderer can re-ask the question with it pre-filled.
|
|
196
|
+
#
|
|
197
|
+
# Rebuilding the earlier state by replaying answers is not equivalent: a
|
|
198
|
+
# Proc default or a prefill! suggestion does not re-derive. So #answer,
|
|
199
|
+
# #skip and #advance each push a #to_state snapshot before they mutate,
|
|
200
|
+
# and this restores one.
|
|
201
|
+
#
|
|
202
|
+
# The snapshot stack is deliberately excluded from #to_state: it is
|
|
203
|
+
# session-local undo, not flow state, and persisting it would nest every
|
|
204
|
+
# earlier state inside each saved one. An engine rebuilt by .from_state
|
|
205
|
+
# therefore starts with nothing to go back to.
|
|
206
|
+
#
|
|
207
|
+
# @example Re-ask the previous question with its answer filled in
|
|
208
|
+
# engine.answer("married")
|
|
209
|
+
# engine.back # => true
|
|
210
|
+
# engine.previous_answer(:filing_status) # => "married"
|
|
211
|
+
#
|
|
212
|
+
# @return [Boolean] true when the engine moved back; false, with nothing
|
|
213
|
+
# changed, when no collecting step has been answered yet
|
|
214
|
+
def back
|
|
215
|
+
index = @snapshots.rindex { |state| collecting_step?(state[:current_step_id]) }
|
|
216
|
+
return false unless index
|
|
217
|
+
|
|
218
|
+
@rewound_answers.merge!(@answers)
|
|
219
|
+
load_state(@snapshots[index])
|
|
220
|
+
@snapshots.slice!(index..)
|
|
221
|
+
true
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Whether #back would move the engine.
|
|
225
|
+
#
|
|
226
|
+
# @return [Boolean]
|
|
227
|
+
def can_back?
|
|
228
|
+
@snapshots.any? { |state| collecting_step?(state[:current_step_id]) }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# The answer to pre-fill when a step is asked again after #back: the
|
|
232
|
+
# step's current answer when it has one, otherwise the last answer #back
|
|
233
|
+
# took away from it.
|
|
234
|
+
#
|
|
235
|
+
# @param step_id [Symbol, String] step id
|
|
236
|
+
# @return [Object, nil] nil when the step was never answered
|
|
237
|
+
def previous_answer(step_id)
|
|
238
|
+
sym = step_id.to_sym
|
|
239
|
+
@answers.fetch(sym) { @rewound_answers[sym] }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# The current step's default as a concrete value, resolved exactly as
|
|
243
|
+
# #skip records it: a Proc default is called with the answers so far.
|
|
244
|
+
# Renderers pre-fill with it; a headless run under `yes` answers with it.
|
|
245
|
+
#
|
|
246
|
+
# @return [Object, nil] nil when the flow is finished or the step has no default
|
|
247
|
+
def current_default
|
|
248
|
+
return nil if finished?
|
|
249
|
+
|
|
250
|
+
resolve_default(current_step)
|
|
251
|
+
end
|
|
252
|
+
|
|
180
253
|
# Whether the user explicitly skipped the given step via #skip.
|
|
181
254
|
#
|
|
182
255
|
# @param step_id [Symbol, String] step id
|
|
@@ -311,17 +384,55 @@ module Inquirex
|
|
|
311
384
|
def restore_state(definition, state, validator)
|
|
312
385
|
@definition = definition
|
|
313
386
|
@validator = validator
|
|
387
|
+
@after_completion_hooks = []
|
|
388
|
+
@completion_hook_errors = []
|
|
389
|
+
@snapshots = []
|
|
390
|
+
@rewound_answers = {}
|
|
391
|
+
load_state(state)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Loads the flow-state fields of a #to_state hash, leaving the session
|
|
395
|
+
# (validator, hooks, the #back stack) alone. Shared by .from_state and
|
|
396
|
+
# #back so the two cannot disagree about what a state contains.
|
|
397
|
+
#
|
|
398
|
+
# @param state [Hash] symbol-keyed state, as #to_state returns it
|
|
399
|
+
# @return [void]
|
|
400
|
+
def load_state(state)
|
|
314
401
|
@current_step_id = state[:current_step_id]
|
|
315
402
|
@answers = state[:answers] || {}
|
|
316
403
|
@history = state[:history] || []
|
|
317
404
|
@totals = state[:totals] || init_totals
|
|
318
405
|
@completion_metadata = CompletionMetadata.from_h(state[:completion_metadata])
|
|
319
|
-
@after_completion_hooks = []
|
|
320
|
-
@completion_hook_errors = []
|
|
321
406
|
@suggestions = state[:suggestions] || {}
|
|
322
407
|
@skipped = state[:skipped] || []
|
|
323
408
|
end
|
|
324
409
|
|
|
410
|
+
# Records the state before #answer, #skip or #advance changes it. The
|
|
411
|
+
# copy is deep: those methods mutate the answers, totals, history,
|
|
412
|
+
# suggestions and skipped collections in place.
|
|
413
|
+
#
|
|
414
|
+
# @return [void]
|
|
415
|
+
def push_snapshot
|
|
416
|
+
@snapshots << deep_copy(to_state)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# @param value [Object]
|
|
420
|
+
# @return [Object] a copy sharing no Hash, Array or String with +value+
|
|
421
|
+
def deep_copy(value)
|
|
422
|
+
case value
|
|
423
|
+
when Hash then value.to_h { |k, v| [k, deep_copy(v)] }
|
|
424
|
+
when Array then value.map { |v| deep_copy(v) }
|
|
425
|
+
when String then value.dup
|
|
426
|
+
else value
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# @param step_id [Symbol, nil]
|
|
431
|
+
# @return [Boolean] whether +step_id+ names a step that collects an answer
|
|
432
|
+
def collecting_step?(step_id)
|
|
433
|
+
!step_id.nil? && @definition.step(step_id).collecting?
|
|
434
|
+
end
|
|
435
|
+
|
|
325
436
|
# Whether +step_id+ names a multi-select step in the definition. Unknown
|
|
326
437
|
# ids (e.g. an extract schema field that matches no step) are not.
|
|
327
438
|
#
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inquirex
|
|
4
|
+
# Runs a flow to the end from a hash of preset answers, with no terminal,
|
|
5
|
+
# no prompt and no LLM: the driver behind `inquirex-tty run --answers` and
|
|
6
|
+
# `--yes`, and the way to run a flow in tests and CI.
|
|
7
|
+
#
|
|
8
|
+
# Answers reach the engine through Engine#answer and Engine#skip only,
|
|
9
|
+
# never Engine#prefill!. prefill! records a multi-select as a suggestion
|
|
10
|
+
# that no skip_if rule can see, so a flow whose multi-select is gated on
|
|
11
|
+
# its own answer would stop on that question with nobody to answer it.
|
|
12
|
+
#
|
|
13
|
+
# Step by step:
|
|
14
|
+
#
|
|
15
|
+
# - a display step advances silently;
|
|
16
|
+
# - a step present in +answers+ is answered with that value, as given (an
|
|
17
|
+
# LLM step included: the value stands in for the adapter's output, and
|
|
18
|
+
# nothing is splatted into other answers);
|
|
19
|
+
# - otherwise, with +yes+, a step with a default is answered with it (a
|
|
20
|
+
# Proc default resolved against the answers so far);
|
|
21
|
+
# - otherwise an optional step is skipped, recording its default as
|
|
22
|
+
# Engine#skip does;
|
|
23
|
+
# - otherwise, with +yes+, a required LLM step is answered with nil;
|
|
24
|
+
# - otherwise the run stops with MissingAnswer naming the step.
|
|
25
|
+
#
|
|
26
|
+
# No LLM adapter is ever called: core has none, and a run under +yes+ is
|
|
27
|
+
# meant to be offline and repeatable.
|
|
28
|
+
#
|
|
29
|
+
# @example Run a flow from an answers file's contents
|
|
30
|
+
# results = Inquirex::Headless.new(definition, answers: { filing_status: "single" }, yes: true).call
|
|
31
|
+
# results[:answers] # => { filing_status: "single", dependents: 0, ... }
|
|
32
|
+
# results[:path_taken] # => [:filing_status, :dependents, :done]
|
|
33
|
+
class Headless
|
|
34
|
+
# Raised when a step needs an answer that neither +answers+ nor (with
|
|
35
|
+
# +yes+) a default supplies.
|
|
36
|
+
class MissingAnswer < Errors::EngineError
|
|
37
|
+
# @return [Symbol] the step left without an answer
|
|
38
|
+
attr_reader :step_id
|
|
39
|
+
|
|
40
|
+
# @param step_id [Symbol]
|
|
41
|
+
def initialize(step_id)
|
|
42
|
+
@step_id = step_id
|
|
43
|
+
super("No answer for required step #{step_id.inspect}: give one in the answers, or a default with yes")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The engine the run drives. Register Engine#after_completion hooks on it
|
|
48
|
+
# before #call to attach completion metadata.
|
|
49
|
+
#
|
|
50
|
+
# @return [Engine]
|
|
51
|
+
attr_reader :engine
|
|
52
|
+
|
|
53
|
+
# @param definition [Definition] the flow to run
|
|
54
|
+
# @param answers [Hash{Symbol, String => Object}] preset answers by step id;
|
|
55
|
+
# string keys, as JSON and YAML produce them, are accepted
|
|
56
|
+
# @param yes [Boolean] take each unanswered step's default
|
|
57
|
+
def initialize(definition, answers: {}, yes: false)
|
|
58
|
+
@engine = Engine.new(definition)
|
|
59
|
+
@answers = answers.to_h { |step_id, value| [step_id.to_sym, value] }
|
|
60
|
+
@yes = yes
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Drives the engine until the flow finishes.
|
|
64
|
+
#
|
|
65
|
+
# @return [Hash] +:path_taken+ (step ids, in order), +:answers+
|
|
66
|
+
# (Engine#answers_with_metadata) and +:steps_completed+. A caller adds
|
|
67
|
+
# whatever it knows that the engine does not, such as the flow file or a
|
|
68
|
+
# completion time.
|
|
69
|
+
# @raise [MissingAnswer] when a step can be given no answer
|
|
70
|
+
# @raise [Errors::ValidationError] when the validator rejects an answer
|
|
71
|
+
def call
|
|
72
|
+
take(@engine.current_step) until @engine.finished?
|
|
73
|
+
results
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Moves the engine past one step.
|
|
79
|
+
#
|
|
80
|
+
# @param node [Node]
|
|
81
|
+
# @return [void]
|
|
82
|
+
def take(node)
|
|
83
|
+
return @engine.advance if node.display?
|
|
84
|
+
return @engine.answer(@answers[node.id]) if @answers.key?(node.id)
|
|
85
|
+
|
|
86
|
+
# Only under yes: resolving a Proc default runs host code.
|
|
87
|
+
default = @yes ? @engine.current_default : nil
|
|
88
|
+
return @engine.answer(default) unless default.nil?
|
|
89
|
+
return @engine.skip unless node.required?
|
|
90
|
+
return @engine.answer(nil) if @yes && llm_step?(node)
|
|
91
|
+
|
|
92
|
+
raise MissingAnswer, node.id
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# LLM steps come from inquirex-llm, which core does not load, so they are
|
|
96
|
+
# recognised by the predicate their node class answers.
|
|
97
|
+
#
|
|
98
|
+
# @param node [Node]
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
def llm_step?(node) = node.respond_to?(:llm_verb?) && node.llm_verb?
|
|
101
|
+
|
|
102
|
+
# @return [Hash]
|
|
103
|
+
def results
|
|
104
|
+
{
|
|
105
|
+
path_taken: @engine.history,
|
|
106
|
+
answers: @engine.answers_with_metadata,
|
|
107
|
+
steps_completed: @engine.history.length
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/inquirex/version.rb
CHANGED
data/lib/inquirex.rb
CHANGED
|
@@ -49,6 +49,7 @@ require_relative "inquirex/safe_source"
|
|
|
49
49
|
# Engine
|
|
50
50
|
require_relative "inquirex/engine/state_serializer"
|
|
51
51
|
require_relative "inquirex/engine"
|
|
52
|
+
require_relative "inquirex/headless"
|
|
52
53
|
|
|
53
54
|
# Graph utilities
|
|
54
55
|
require_relative "inquirex/graph/mermaid_exporter"
|
metadata
CHANGED
|
@@ -1,107 +1,128 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inquirex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- Konstantin Gredeskoul
|
|
7
|
+
- Konstantin Gredeskoul
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ostruct
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
-
|
|
17
|
+
- ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: "0.6"
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
-
|
|
25
|
+
- ~>
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: "0.6"
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: prism
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
-
|
|
33
|
+
- ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: "1.7"
|
|
36
|
+
-
|
|
37
|
+
- "<"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: "2"
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
-
|
|
45
|
+
- ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: "1.7"
|
|
48
|
+
-
|
|
49
|
+
- "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: "2"
|
|
52
|
+
description: "Inquirex lets you define multi-step questionnaires as directed graphs with conditional branching, using a conversational DSL (ask, say, mention) and an AST-based rule system (contains, equals, greater_than, all, any). The engine walks the graph, collects structured answers, and serializes everything to JSON — making it the ideal backbone for cross-platform intake forms where the frontend is a chat widget, a terminal, or a mobile app. Framework-agnostic, no third-party dependencies, thread-safe immutable definitions."
|
|
33
53
|
email:
|
|
34
|
-
- kigster@gmail.com
|
|
54
|
+
- kigster@gmail.com
|
|
35
55
|
executables: []
|
|
36
56
|
extensions: []
|
|
37
57
|
extra_rdoc_files: []
|
|
38
58
|
files:
|
|
39
|
-
- CHANGELOG.md
|
|
40
|
-
- LICENSE.txt
|
|
41
|
-
- README.md
|
|
42
|
-
- lib/inquirex.rb
|
|
43
|
-
- lib/inquirex/accumulator.rb
|
|
44
|
-
- lib/inquirex/answers.rb
|
|
45
|
-
- lib/inquirex/completion_metadata.rb
|
|
46
|
-
- lib/inquirex/definition.rb
|
|
47
|
-
- lib/inquirex/dsl.rb
|
|
48
|
-
- lib/inquirex/dsl/flow_builder.rb
|
|
49
|
-
- lib/inquirex/dsl/rule_helpers.rb
|
|
50
|
-
- lib/inquirex/dsl/send_email_builder.rb
|
|
51
|
-
- lib/inquirex/dsl/step_builder.rb
|
|
52
|
-
- lib/inquirex/engine.rb
|
|
53
|
-
- lib/inquirex/engine/state_serializer.rb
|
|
54
|
-
- lib/inquirex/errors.rb
|
|
55
|
-
- lib/inquirex/evaluator.rb
|
|
56
|
-
- lib/inquirex/graph/mermaid_exporter.rb
|
|
57
|
-
- lib/inquirex/
|
|
58
|
-
- lib/inquirex/
|
|
59
|
-
- lib/inquirex/rules/
|
|
60
|
-
- lib/inquirex/rules/
|
|
61
|
-
- lib/inquirex/rules/
|
|
62
|
-
- lib/inquirex/rules/
|
|
63
|
-
- lib/inquirex/rules/
|
|
64
|
-
- lib/inquirex/rules/
|
|
65
|
-
- lib/inquirex/rules/
|
|
66
|
-
- lib/inquirex/
|
|
67
|
-
- lib/inquirex/safe_source
|
|
68
|
-
- lib/inquirex/safe_source/
|
|
69
|
-
- lib/inquirex/safe_source/
|
|
70
|
-
- lib/inquirex/
|
|
71
|
-
- lib/inquirex/
|
|
72
|
-
- lib/inquirex/
|
|
73
|
-
- lib/inquirex/
|
|
74
|
-
- lib/inquirex/
|
|
75
|
-
- lib/inquirex/validation/
|
|
76
|
-
- lib/inquirex/
|
|
77
|
-
- lib/inquirex/
|
|
78
|
-
- lib/inquirex/
|
|
79
|
-
|
|
59
|
+
- CHANGELOG.md
|
|
60
|
+
- LICENSE.txt
|
|
61
|
+
- README.md
|
|
62
|
+
- lib/inquirex.rb
|
|
63
|
+
- lib/inquirex/accumulator.rb
|
|
64
|
+
- lib/inquirex/answers.rb
|
|
65
|
+
- lib/inquirex/completion_metadata.rb
|
|
66
|
+
- lib/inquirex/definition.rb
|
|
67
|
+
- lib/inquirex/dsl.rb
|
|
68
|
+
- lib/inquirex/dsl/flow_builder.rb
|
|
69
|
+
- lib/inquirex/dsl/rule_helpers.rb
|
|
70
|
+
- lib/inquirex/dsl/send_email_builder.rb
|
|
71
|
+
- lib/inquirex/dsl/step_builder.rb
|
|
72
|
+
- lib/inquirex/engine.rb
|
|
73
|
+
- lib/inquirex/engine/state_serializer.rb
|
|
74
|
+
- lib/inquirex/errors.rb
|
|
75
|
+
- lib/inquirex/evaluator.rb
|
|
76
|
+
- lib/inquirex/graph/mermaid_exporter.rb
|
|
77
|
+
- lib/inquirex/headless.rb
|
|
78
|
+
- lib/inquirex/node.rb
|
|
79
|
+
- lib/inquirex/rules/all.rb
|
|
80
|
+
- lib/inquirex/rules/any.rb
|
|
81
|
+
- lib/inquirex/rules/base.rb
|
|
82
|
+
- lib/inquirex/rules/contains.rb
|
|
83
|
+
- lib/inquirex/rules/equals.rb
|
|
84
|
+
- lib/inquirex/rules/greater_than.rb
|
|
85
|
+
- lib/inquirex/rules/less_than.rb
|
|
86
|
+
- lib/inquirex/rules/not_empty.rb
|
|
87
|
+
- lib/inquirex/safe_source.rb
|
|
88
|
+
- lib/inquirex/safe_source/call_spec.rb
|
|
89
|
+
- lib/inquirex/safe_source/validator.rb
|
|
90
|
+
- lib/inquirex/safe_source/vocabulary.rb
|
|
91
|
+
- lib/inquirex/send_email.rb
|
|
92
|
+
- lib/inquirex/template.rb
|
|
93
|
+
- lib/inquirex/transcript.rb
|
|
94
|
+
- lib/inquirex/transition.rb
|
|
95
|
+
- lib/inquirex/validation/adapter.rb
|
|
96
|
+
- lib/inquirex/validation/null_adapter.rb
|
|
97
|
+
- lib/inquirex/version.rb
|
|
98
|
+
- lib/inquirex/widget_hint.rb
|
|
99
|
+
- lib/inquirex/widget_registry.rb
|
|
100
|
+
homepage: "https://github.com/inquirex/inquirex"
|
|
80
101
|
licenses:
|
|
81
|
-
- MIT
|
|
102
|
+
- MIT
|
|
82
103
|
metadata:
|
|
83
|
-
allowed_push_host: https://rubygems.org
|
|
84
|
-
homepage_uri: https://qualified.at
|
|
85
|
-
source_code_uri: https://github.com/inquirex/inquirex
|
|
86
|
-
changelog_uri: https://github.com/inquirex/inquirex/blob/main/CHANGELOG.md
|
|
87
|
-
rubygems_mfa_required:
|
|
104
|
+
allowed_push_host: "https://rubygems.org"
|
|
105
|
+
homepage_uri: "https://qualified.at"
|
|
106
|
+
source_code_uri: "https://github.com/inquirex/inquirex"
|
|
107
|
+
changelog_uri: "https://github.com/inquirex/inquirex/blob/main/CHANGELOG.md"
|
|
108
|
+
rubygems_mfa_required: "true"
|
|
88
109
|
rdoc_options: []
|
|
89
110
|
require_paths:
|
|
90
|
-
- lib
|
|
111
|
+
- lib
|
|
91
112
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
113
|
requirements:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
-
|
|
115
|
+
- ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 4.0.0
|
|
96
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
119
|
requirements:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
-
|
|
121
|
+
- ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: "0"
|
|
101
124
|
requirements: []
|
|
102
|
-
rubygems_version: 4.0.
|
|
125
|
+
rubygems_version: 4.1.0.beta2
|
|
103
126
|
specification_version: 4
|
|
104
|
-
summary: A declarative, rules-driven questionnaire engine for building conditionally-branching
|
|
105
|
-
intake forms, qualification wizards, and surveys in pure Ruby, with additional plugins
|
|
106
|
-
providing LLM/AI support. See inquirex-llm.
|
|
127
|
+
summary: "A declarative, rules-driven questionnaire engine for building conditionally-branching intake forms, qualification wizards, and surveys in pure Ruby, with additional plugins providing LLM/AI support. See inquirex-llm."
|
|
107
128
|
test_files: []
|