flowengine 0.1.1 → 0.2.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 +4 -4
- data/.env.example +1 -0
- data/.envrc +4 -0
- data/.rubocop_todo.yml +3 -8
- data/README.md +151 -6
- data/Rakefile +1 -1
- data/docs/badges/coverage_badge.svg +21 -0
- data/justfile +11 -0
- data/lib/flowengine/definition.rb +17 -2
- data/lib/flowengine/dsl/flow_builder.rb +25 -1
- data/lib/flowengine/dsl/rule_helpers.rb +22 -0
- data/lib/flowengine/dsl/step_builder.rb +23 -0
- data/lib/flowengine/dsl.rb +2 -0
- data/lib/flowengine/engine.rb +68 -2
- data/lib/flowengine/errors.rb +17 -0
- data/lib/flowengine/evaluator.rb +9 -0
- data/lib/flowengine/graph/mermaid_exporter.rb +7 -0
- data/lib/flowengine/introduction.rb +14 -0
- data/lib/flowengine/llm/adapter.rb +19 -0
- data/lib/flowengine/llm/client.rb +75 -0
- data/lib/flowengine/llm/openai_adapter.rb +38 -0
- data/lib/flowengine/llm/sensitive_data_filter.rb +45 -0
- data/lib/flowengine/llm/system_prompt_builder.rb +73 -0
- data/lib/flowengine/llm.rb +14 -0
- data/lib/flowengine/node.rb +47 -2
- data/lib/flowengine/rules/all.rb +7 -0
- data/lib/flowengine/rules/any.rb +7 -0
- data/lib/flowengine/rules/base.rb +9 -0
- data/lib/flowengine/rules/contains.rb +10 -0
- data/lib/flowengine/rules/equals.rb +9 -0
- data/lib/flowengine/rules/greater_than.rb +9 -0
- data/lib/flowengine/rules/less_than.rb +9 -0
- data/lib/flowengine/rules/not_empty.rb +7 -0
- data/lib/flowengine/transition.rb +14 -0
- data/lib/flowengine/validation/adapter.rb +13 -0
- data/lib/flowengine/validation/null_adapter.rb +4 -0
- data/lib/flowengine/version.rb +2 -1
- data/lib/flowengine.rb +35 -0
- data/resources/prompts/generic-dsl-intake.j2 +60 -0
- metadata +53 -2
- data/CHANGELOG.md +0 -5
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
4
|
module Rules
|
|
5
|
+
# Rule: the answer for the given field equals the given value.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader field [Symbol] answer key
|
|
8
|
+
# @attr_reader value [Object] expected value
|
|
5
9
|
class Equals < Base
|
|
6
10
|
attr_reader :field, :value
|
|
7
11
|
|
|
12
|
+
# @param field [Symbol] answer key
|
|
13
|
+
# @param value [Object] expected value
|
|
8
14
|
def initialize(field, value)
|
|
9
15
|
super()
|
|
10
16
|
@field = field
|
|
@@ -12,10 +18,13 @@ module FlowEngine
|
|
|
12
18
|
freeze
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
# @param answers [Hash] current answers
|
|
22
|
+
# @return [Boolean] true if answers[field] == value
|
|
15
23
|
def evaluate(answers)
|
|
16
24
|
answers[field] == value
|
|
17
25
|
end
|
|
18
26
|
|
|
27
|
+
# @return [String] e.g. "marital_status == Married"
|
|
19
28
|
def to_s
|
|
20
29
|
"#{field} == #{value}"
|
|
21
30
|
end
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
4
|
module Rules
|
|
5
|
+
# Rule: the answer for the given field (coerced to integer) is greater than the threshold.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader field [Symbol] answer key
|
|
8
|
+
# @attr_reader value [Integer] threshold
|
|
5
9
|
class GreaterThan < Base
|
|
6
10
|
attr_reader :field, :value
|
|
7
11
|
|
|
12
|
+
# @param field [Symbol] answer key
|
|
13
|
+
# @param value [Integer] threshold
|
|
8
14
|
def initialize(field, value)
|
|
9
15
|
super()
|
|
10
16
|
@field = field
|
|
@@ -12,10 +18,13 @@ module FlowEngine
|
|
|
12
18
|
freeze
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
# @param answers [Hash] current answers (field value is coerced with to_i)
|
|
22
|
+
# @return [Boolean] true if answers[field].to_i > value
|
|
15
23
|
def evaluate(answers)
|
|
16
24
|
answers[field].to_i > value
|
|
17
25
|
end
|
|
18
26
|
|
|
27
|
+
# @return [String] e.g. "business_income > 100000"
|
|
19
28
|
def to_s
|
|
20
29
|
"#{field} > #{value}"
|
|
21
30
|
end
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
4
|
module Rules
|
|
5
|
+
# Rule: the answer for the given field (coerced to integer) is less than the threshold.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader field [Symbol] answer key
|
|
8
|
+
# @attr_reader value [Integer] threshold
|
|
5
9
|
class LessThan < Base
|
|
6
10
|
attr_reader :field, :value
|
|
7
11
|
|
|
12
|
+
# @param field [Symbol] answer key
|
|
13
|
+
# @param value [Integer] threshold
|
|
8
14
|
def initialize(field, value)
|
|
9
15
|
super()
|
|
10
16
|
@field = field
|
|
@@ -12,10 +18,13 @@ module FlowEngine
|
|
|
12
18
|
freeze
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
# @param answers [Hash] current answers (field value is coerced with to_i)
|
|
22
|
+
# @return [Boolean] true if answers[field].to_i < value
|
|
15
23
|
def evaluate(answers)
|
|
16
24
|
answers[field].to_i < value
|
|
17
25
|
end
|
|
18
26
|
|
|
27
|
+
# @return [String] e.g. "age < 18"
|
|
19
28
|
def to_s
|
|
20
29
|
"#{field} < #{value}"
|
|
21
30
|
end
|
|
@@ -2,15 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
4
|
module Rules
|
|
5
|
+
# Rule: the answer for the given field is present and not empty (nil or empty? => false).
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader field [Symbol] answer key
|
|
5
8
|
class NotEmpty < Base
|
|
6
9
|
attr_reader :field
|
|
7
10
|
|
|
11
|
+
# @param field [Symbol] answer key
|
|
8
12
|
def initialize(field)
|
|
9
13
|
super()
|
|
10
14
|
@field = field
|
|
11
15
|
freeze
|
|
12
16
|
end
|
|
13
17
|
|
|
18
|
+
# @param answers [Hash] current answers
|
|
19
|
+
# @return [Boolean] false if nil or empty, true otherwise
|
|
14
20
|
def evaluate(answers)
|
|
15
21
|
val = answers[field]
|
|
16
22
|
return false if val.nil?
|
|
@@ -19,6 +25,7 @@ module FlowEngine
|
|
|
19
25
|
true
|
|
20
26
|
end
|
|
21
27
|
|
|
28
|
+
# @return [String] e.g. "name is not empty"
|
|
22
29
|
def to_s
|
|
23
30
|
"#{field} is not empty"
|
|
24
31
|
end
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
|
+
# A single edge from one step to another, optionally guarded by a rule.
|
|
5
|
+
# Transitions are evaluated in order; the first whose rule is true determines the next step.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader target [Symbol] id of the step to go to when this transition applies
|
|
8
|
+
# @attr_reader rule [Rules::Base, nil] condition; nil means unconditional (always applies)
|
|
4
9
|
class Transition
|
|
5
10
|
attr_reader :target, :rule
|
|
6
11
|
|
|
12
|
+
# @param target [Symbol] next step id
|
|
13
|
+
# @param rule [Rules::Base, nil] optional condition (nil = always)
|
|
7
14
|
def initialize(target:, rule: nil)
|
|
8
15
|
@target = target
|
|
9
16
|
@rule = rule
|
|
10
17
|
freeze
|
|
11
18
|
end
|
|
12
19
|
|
|
20
|
+
# Human-readable label for the condition (e.g. for graph export).
|
|
21
|
+
#
|
|
22
|
+
# @return [String] rule#to_s or "always" when rule is nil
|
|
13
23
|
def condition_label
|
|
14
24
|
rule ? rule.to_s : "always"
|
|
15
25
|
end
|
|
16
26
|
|
|
27
|
+
# Whether this transition should be taken given current answers.
|
|
28
|
+
#
|
|
29
|
+
# @param answers [Hash] current answer state
|
|
30
|
+
# @return [Boolean] true if rule is nil or rule evaluates to true
|
|
17
31
|
def applies?(answers)
|
|
18
32
|
return true if rule.nil?
|
|
19
33
|
|
|
@@ -2,21 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
4
|
module Validation
|
|
5
|
+
# Result of validating a step answer: either valid or a list of error messages.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader errors [Array<String>] validation error messages (empty when valid)
|
|
5
8
|
class Result
|
|
6
9
|
attr_reader :errors
|
|
7
10
|
|
|
11
|
+
# @param valid [Boolean] whether the input passed validation
|
|
12
|
+
# @param errors [Array<String>] error messages (default: [])
|
|
8
13
|
def initialize(valid:, errors: [])
|
|
9
14
|
@valid = valid
|
|
10
15
|
@errors = errors.freeze
|
|
11
16
|
freeze
|
|
12
17
|
end
|
|
13
18
|
|
|
19
|
+
# @return [Boolean] true if validation passed
|
|
14
20
|
def valid?
|
|
15
21
|
@valid
|
|
16
22
|
end
|
|
17
23
|
end
|
|
18
24
|
|
|
25
|
+
# Abstract adapter for step-level validation. Implement {#validate} to plug in
|
|
26
|
+
# dry-validation, JSON Schema, or other validators; the engine does not depend on a specific one.
|
|
19
27
|
class Adapter
|
|
28
|
+
# Validates the user's input for the given step.
|
|
29
|
+
#
|
|
30
|
+
# @param _node [Node] the current step (for schema/constraints)
|
|
31
|
+
# @param _input [Object] the value submitted by the user
|
|
32
|
+
# @return [Result] valid: true/false and optional errors list
|
|
20
33
|
def validate(_node, _input)
|
|
21
34
|
raise NotImplementedError, "#{self.class}#validate must be implemented"
|
|
22
35
|
end
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module FlowEngine
|
|
4
4
|
module Validation
|
|
5
|
+
# No-op validator: always accepts any input. Used by default when no validation adapter is given.
|
|
5
6
|
class NullAdapter < Adapter
|
|
7
|
+
# @param _node [Node] ignored
|
|
8
|
+
# @param _input [Object] ignored
|
|
9
|
+
# @return [Result] always valid with no errors
|
|
6
10
|
def validate(_node, _input)
|
|
7
11
|
Result.new(valid: true, errors: [])
|
|
8
12
|
end
|
data/lib/flowengine/version.rb
CHANGED
data/lib/flowengine.rb
CHANGED
|
@@ -13,20 +13,55 @@ require_relative "flowengine/rules/any"
|
|
|
13
13
|
require_relative "flowengine/evaluator"
|
|
14
14
|
require_relative "flowengine/transition"
|
|
15
15
|
require_relative "flowengine/node"
|
|
16
|
+
require_relative "flowengine/introduction"
|
|
16
17
|
require_relative "flowengine/definition"
|
|
17
18
|
require_relative "flowengine/validation/adapter"
|
|
18
19
|
require_relative "flowengine/validation/null_adapter"
|
|
19
20
|
require_relative "flowengine/engine"
|
|
20
21
|
require_relative "flowengine/dsl"
|
|
21
22
|
require_relative "flowengine/graph/mermaid_exporter"
|
|
23
|
+
require_relative "flowengine/llm"
|
|
22
24
|
|
|
25
|
+
# Declarative flow definition and execution engine for wizards, intake forms, and
|
|
26
|
+
# multi-step decision graphs. Separates flow logic, data schema, and UI rendering.
|
|
27
|
+
#
|
|
28
|
+
# @example Define and run a flow
|
|
29
|
+
# definition = FlowEngine.define do
|
|
30
|
+
# start :earnings
|
|
31
|
+
# step :earnings do
|
|
32
|
+
# type :multi_select
|
|
33
|
+
# question "What are your main earnings?"
|
|
34
|
+
# options %w[W2 1099 BusinessOwnership]
|
|
35
|
+
# transition to: :business_details, if: contains(:earnings, "BusinessOwnership")
|
|
36
|
+
# end
|
|
37
|
+
# step :business_details do
|
|
38
|
+
# type :number_matrix
|
|
39
|
+
# question "How many business types?"
|
|
40
|
+
# fields %w[RealEstate SCorp CCorp]
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
# engine = FlowEngine::Engine.new(definition)
|
|
44
|
+
# engine.answer(["W2", "BusinessOwnership"])
|
|
45
|
+
# engine.current_step_id # => :business_details
|
|
46
|
+
#
|
|
23
47
|
module FlowEngine
|
|
48
|
+
# Builds an immutable {Definition} from the declarative DSL block.
|
|
49
|
+
#
|
|
50
|
+
# @yield context of {DSL::FlowBuilder} (start, step, and rule helpers)
|
|
51
|
+
# @return [Definition] frozen flow definition with start step and nodes
|
|
52
|
+
# @raise [DefinitionError] if no start step or no steps are defined
|
|
24
53
|
def self.define(&)
|
|
25
54
|
builder = DSL::FlowBuilder.new
|
|
26
55
|
builder.instance_eval(&)
|
|
27
56
|
builder.build
|
|
28
57
|
end
|
|
29
58
|
|
|
59
|
+
# Evaluates a string of DSL code and returns the resulting definition.
|
|
60
|
+
# Intended for loading flow definitions from files or stored text.
|
|
61
|
+
#
|
|
62
|
+
# @param text [String] Ruby source containing FlowEngine.define { ... }
|
|
63
|
+
# @return [Definition] the definition produced by evaluating the DSL
|
|
64
|
+
# @raise [DefinitionError] on syntax or evaluation errors
|
|
30
65
|
def self.load_dsl(text)
|
|
31
66
|
# rubocop:disable Security/Eval
|
|
32
67
|
eval(text, TOPLEVEL_BINDING.dup, "(dsl)", 1)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
You are a generic intake assistant for a professional services firm. You are given a Ruby DSL that defines the intake flow. You do not need to run the flow, but you need to understand the questions and it's structure.
|
|
4
|
+
The gem will follow the flow to ask the questions in the correct order and will fill out the JSON data structure that is defined by the DSL,
|
|
5
|
+
and keep asking question until all required questions are answered.
|
|
6
|
+
|
|
7
|
+
## Instructions for LLM
|
|
8
|
+
|
|
9
|
+
I'd like to add a new DSL command called `introduction` with sub-arguments `label` (something that's shown above the input field) and
|
|
10
|
+
`placeholder` which is the text that will show up inside the text area before the user starts typing.
|
|
11
|
+
|
|
12
|
+
If this field is present in the DSL, we are to collect user's free-form text into a new field `engine.introduction()`.
|
|
13
|
+
|
|
14
|
+
Before the first step begins we must check if the introduction is non-empty, and if so the gem should take that response and via a AI Wrapper class that's instantiated with the name of the LLM model and API key, and adapter for different LLM APIs, should invoke whatever adapter is passed. For now let's create only OpenAI adapter. This class will use RubyLLM or any other gem that works to call OpenAI API. The user prompt will be the context of the user entry in `engine.introduction`. The system prompt is this file.
|
|
15
|
+
|
|
16
|
+
## What is the purpose of this step?
|
|
17
|
+
|
|
18
|
+
The gem currently has:
|
|
19
|
+
|
|
20
|
+
1. DSL → Ruby objects (FlowEngine.define { ... } → Definition/Node/Transition/Rule objects)
|
|
21
|
+
2. DSL from string (FlowEngine.load_dsl(text) — evaluates Ruby source code, not JSON)
|
|
22
|
+
3. Engine state serialization (Engine#to_state / Engine.from_state — a simple hash of current_step_id, answers, history)
|
|
23
|
+
4. Mermaid export (Graph::MermaidExporter — outputs diagram syntax)
|
|
24
|
+
|
|
25
|
+
The answers the user provides are stored in memory only — in the Engine instance's @answers hash (Hash<Symbol, Object>).
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
engine = FlowEngine::Engine.new(definition)
|
|
29
|
+
engine.answer("Alice") # stores { name: "Alice" }
|
|
30
|
+
engine.answer(25) # stores { name: "Alice", age: 25 }
|
|
31
|
+
engine.answers # => { name: "Alice", age: 25 }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### How the Data is Stored
|
|
35
|
+
|
|
36
|
+
The gem provides `Engine#to_state` which returns a plain Ruby hash:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
{ current_step_id: :age, answers: { name: "Alice" }, history: [:name, :age] }`
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
And `Engine.from_state(definition, hash)` to restore from it.
|
|
43
|
+
|
|
44
|
+
### The job of the LLM
|
|
45
|
+
|
|
46
|
+
The job of the LLM is to parse the user's introduction and to identify the DSL steps that the user already provided the answers for, and fill them in.
|
|
47
|
+
If the answer can be extracted from the text, it should be stored in the engine, and that question should be skipped in the normal flow.
|
|
48
|
+
|
|
49
|
+
## Rules
|
|
50
|
+
|
|
51
|
+
- NEVER ask for sensitive information: SSN, ITIN, full address, bank account numbers, or date of birth.
|
|
52
|
+
- REJECT any sensitive information, and repeat the introduction step if it contains SSN/EIN
|
|
53
|
+
- In other words, if the user volunteers sensitive information, immediately warn them and discard it
|
|
54
|
+
- Do not communicate with the user. Your job is to parse their response and place it into the appropriate answers within the DSL.
|
|
55
|
+
|
|
56
|
+
## API KEY
|
|
57
|
+
|
|
58
|
+
Check environment variables such as OPENAI_API_KEY before calling LLM.
|
|
59
|
+
|
|
60
|
+
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flowengine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Konstantin Gredeskoul
|
|
@@ -9,6 +9,34 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ruby_llm
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
12
40
|
- !ruby/object:Gem::Dependency
|
|
13
41
|
name: rspec-its
|
|
14
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -23,6 +51,20 @@ dependencies:
|
|
|
23
51
|
- - "~>"
|
|
24
52
|
- !ruby/object:Gem::Version
|
|
25
53
|
version: '2.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rubocop
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
26
68
|
- !ruby/object:Gem::Dependency
|
|
27
69
|
name: simplecov
|
|
28
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -61,12 +103,13 @@ executables:
|
|
|
61
103
|
extensions: []
|
|
62
104
|
extra_rdoc_files: []
|
|
63
105
|
files:
|
|
106
|
+
- ".env.example"
|
|
64
107
|
- ".envrc"
|
|
65
108
|
- ".rubocop_todo.yml"
|
|
66
|
-
- CHANGELOG.md
|
|
67
109
|
- LICENSE.txt
|
|
68
110
|
- README.md
|
|
69
111
|
- Rakefile
|
|
112
|
+
- docs/badges/coverage_badge.svg
|
|
70
113
|
- docs/floweingine-architecture.png
|
|
71
114
|
- docs/flowengine-example.png
|
|
72
115
|
- exe/flowengine
|
|
@@ -81,6 +124,13 @@ files:
|
|
|
81
124
|
- lib/flowengine/errors.rb
|
|
82
125
|
- lib/flowengine/evaluator.rb
|
|
83
126
|
- lib/flowengine/graph/mermaid_exporter.rb
|
|
127
|
+
- lib/flowengine/introduction.rb
|
|
128
|
+
- lib/flowengine/llm.rb
|
|
129
|
+
- lib/flowengine/llm/adapter.rb
|
|
130
|
+
- lib/flowengine/llm/client.rb
|
|
131
|
+
- lib/flowengine/llm/openai_adapter.rb
|
|
132
|
+
- lib/flowengine/llm/sensitive_data_filter.rb
|
|
133
|
+
- lib/flowengine/llm/system_prompt_builder.rb
|
|
84
134
|
- lib/flowengine/node.rb
|
|
85
135
|
- lib/flowengine/rules/all.rb
|
|
86
136
|
- lib/flowengine/rules/any.rb
|
|
@@ -94,6 +144,7 @@ files:
|
|
|
94
144
|
- lib/flowengine/validation/adapter.rb
|
|
95
145
|
- lib/flowengine/validation/null_adapter.rb
|
|
96
146
|
- lib/flowengine/version.rb
|
|
147
|
+
- resources/prompts/generic-dsl-intake.j2
|
|
97
148
|
- sig/flowengine.rbs
|
|
98
149
|
homepage: https://github.com/kigster/flowengine
|
|
99
150
|
licenses:
|