igniter-extensions 0.5.2
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 +7 -0
- data/README.md +381 -0
- data/lib/igniter/extensions/contracts/aggregate_pack.rb +103 -0
- data/lib/igniter/extensions/contracts/audit/builder.rb +132 -0
- data/lib/igniter/extensions/contracts/audit/event.rb +34 -0
- data/lib/igniter/extensions/contracts/audit/snapshot.rb +44 -0
- data/lib/igniter/extensions/contracts/audit_pack.rb +60 -0
- data/lib/igniter/extensions/contracts/branch_pack.rb +199 -0
- data/lib/igniter/extensions/contracts/capabilities/declaration.rb +31 -0
- data/lib/igniter/extensions/contracts/capabilities/error.rb +35 -0
- data/lib/igniter/extensions/contracts/capabilities/policy.rb +20 -0
- data/lib/igniter/extensions/contracts/capabilities/report.rb +47 -0
- data/lib/igniter/extensions/contracts/capabilities/violation.rb +30 -0
- data/lib/igniter/extensions/contracts/capabilities_pack.rb +146 -0
- data/lib/igniter/extensions/contracts/collection_pack.rb +212 -0
- data/lib/igniter/extensions/contracts/commerce_pack.rb +91 -0
- data/lib/igniter/extensions/contracts/compose_pack.rb +213 -0
- data/lib/igniter/extensions/contracts/content_addressing/cache.rb +59 -0
- data/lib/igniter/extensions/contracts/content_addressing/content_key.rb +63 -0
- data/lib/igniter/extensions/contracts/content_addressing/declaration.rb +47 -0
- data/lib/igniter/extensions/contracts/content_addressing_pack.rb +90 -0
- data/lib/igniter/extensions/contracts/creator/profile.rb +196 -0
- data/lib/igniter/extensions/contracts/creator/report.rb +85 -0
- data/lib/igniter/extensions/contracts/creator/scaffold.rb +461 -0
- data/lib/igniter/extensions/contracts/creator/scope.rb +79 -0
- data/lib/igniter/extensions/contracts/creator/wizard.rb +269 -0
- data/lib/igniter/extensions/contracts/creator/workflow.rb +189 -0
- data/lib/igniter/extensions/contracts/creator/workflow_step.rb +51 -0
- data/lib/igniter/extensions/contracts/creator/write_result.rb +48 -0
- data/lib/igniter/extensions/contracts/creator/write_step.rb +63 -0
- data/lib/igniter/extensions/contracts/creator/writer.rb +131 -0
- data/lib/igniter/extensions/contracts/creator_pack.rb +128 -0
- data/lib/igniter/extensions/contracts/dataflow/aggregate_operators.rb +119 -0
- data/lib/igniter/extensions/contracts/dataflow/aggregate_state.rb +60 -0
- data/lib/igniter/extensions/contracts/dataflow/builder.rb +66 -0
- data/lib/igniter/extensions/contracts/dataflow/collection_result.rb +70 -0
- data/lib/igniter/extensions/contracts/dataflow/diff.rb +37 -0
- data/lib/igniter/extensions/contracts/dataflow/item_result.rb +44 -0
- data/lib/igniter/extensions/contracts/dataflow/result.rb +58 -0
- data/lib/igniter/extensions/contracts/dataflow/session.rb +173 -0
- data/lib/igniter/extensions/contracts/dataflow/window_filter.rb +49 -0
- data/lib/igniter/extensions/contracts/dataflow_pack.rb +66 -0
- data/lib/igniter/extensions/contracts/debug/pack_audit.rb +181 -0
- data/lib/igniter/extensions/contracts/debug/pack_snapshot.rb +46 -0
- data/lib/igniter/extensions/contracts/debug/profile_snapshot.rb +50 -0
- data/lib/igniter/extensions/contracts/debug/report.rb +50 -0
- data/lib/igniter/extensions/contracts/debug_pack.rb +115 -0
- data/lib/igniter/extensions/contracts/differential/divergence.rb +37 -0
- data/lib/igniter/extensions/contracts/differential/formatter.rb +85 -0
- data/lib/igniter/extensions/contracts/differential/report.rb +83 -0
- data/lib/igniter/extensions/contracts/differential/runner.rb +136 -0
- data/lib/igniter/extensions/contracts/differential_pack.rb +61 -0
- data/lib/igniter/extensions/contracts/execution_report_pack.rb +38 -0
- data/lib/igniter/extensions/contracts/incremental/formatter.rb +60 -0
- data/lib/igniter/extensions/contracts/incremental/node_state.rb +30 -0
- data/lib/igniter/extensions/contracts/incremental/result.rb +65 -0
- data/lib/igniter/extensions/contracts/incremental/session.rb +146 -0
- data/lib/igniter/extensions/contracts/incremental_pack.rb +40 -0
- data/lib/igniter/extensions/contracts/invariants/builder.rb +27 -0
- data/lib/igniter/extensions/contracts/invariants/cases_report.rb +47 -0
- data/lib/igniter/extensions/contracts/invariants/error.rb +34 -0
- data/lib/igniter/extensions/contracts/invariants/invariant.rb +30 -0
- data/lib/igniter/extensions/contracts/invariants/report.rb +45 -0
- data/lib/igniter/extensions/contracts/invariants/suite.rb +36 -0
- data/lib/igniter/extensions/contracts/invariants/violation.rb +39 -0
- data/lib/igniter/extensions/contracts/invariants_pack.rb +88 -0
- data/lib/igniter/extensions/contracts/journal_pack.rb +55 -0
- data/lib/igniter/extensions/contracts/language/formula_pack.rb +185 -0
- data/lib/igniter/extensions/contracts/language/piecewise_pack.rb +166 -0
- data/lib/igniter/extensions/contracts/language/scale_pack.rb +147 -0
- data/lib/igniter/extensions/contracts/lookup_pack.rb +50 -0
- data/lib/igniter/extensions/contracts/mcp/creator_session.rb +105 -0
- data/lib/igniter/extensions/contracts/mcp/tool_argument.rb +35 -0
- data/lib/igniter/extensions/contracts/mcp/tool_definition.rb +33 -0
- data/lib/igniter/extensions/contracts/mcp/tool_result.rb +28 -0
- data/lib/igniter/extensions/contracts/mcp_pack.rb +335 -0
- data/lib/igniter/extensions/contracts/provenance/builder.rb +80 -0
- data/lib/igniter/extensions/contracts/provenance/lineage.rb +59 -0
- data/lib/igniter/extensions/contracts/provenance/node_trace.rb +53 -0
- data/lib/igniter/extensions/contracts/provenance/text_formatter.rb +62 -0
- data/lib/igniter/extensions/contracts/provenance_pack.rb +52 -0
- data/lib/igniter/extensions/contracts/reactive/builder.rb +43 -0
- data/lib/igniter/extensions/contracts/reactive/dispatch_result.rb +59 -0
- data/lib/igniter/extensions/contracts/reactive/engine.rb +79 -0
- data/lib/igniter/extensions/contracts/reactive/event.rb +36 -0
- data/lib/igniter/extensions/contracts/reactive/matcher.rb +20 -0
- data/lib/igniter/extensions/contracts/reactive/plan.rb +58 -0
- data/lib/igniter/extensions/contracts/reactive/subscription.rb +29 -0
- data/lib/igniter/extensions/contracts/reactive_pack.rb +169 -0
- data/lib/igniter/extensions/contracts/saga/compensation.rb +25 -0
- data/lib/igniter/extensions/contracts/saga/compensation_record.rb +28 -0
- data/lib/igniter/extensions/contracts/saga/compensation_set.rb +47 -0
- data/lib/igniter/extensions/contracts/saga/formatter.rb +39 -0
- data/lib/igniter/extensions/contracts/saga/result.rb +56 -0
- data/lib/igniter/extensions/contracts/saga/runner.rb +124 -0
- data/lib/igniter/extensions/contracts/saga_pack.rb +56 -0
- data/lib/igniter/extensions/contracts.rb +445 -0
- data/lib/igniter/extensions.rb +6 -0
- data/lib/igniter-extensions.rb +3 -0
- metadata +152 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Language
|
|
7
|
+
module FormulaPack
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def manifest
|
|
11
|
+
Igniter::Contracts::PackManifest.new(
|
|
12
|
+
name: :extensions_language_formula,
|
|
13
|
+
registry_contracts: [Igniter::Contracts::PackManifest.dsl_keyword(:formula)]
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def install_into(kernel)
|
|
18
|
+
kernel.dsl_keywords.register(:formula, formula_keyword)
|
|
19
|
+
kernel
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def formula_keyword
|
|
23
|
+
Igniter::Contracts::DslKeyword.new(:formula) do |name, builder:, trace: nil, &block|
|
|
24
|
+
raise ArgumentError, "formula :#{name} requires a block" unless block
|
|
25
|
+
|
|
26
|
+
trace_name = (trace || :"#{name}_trace").to_sym
|
|
27
|
+
definition = Definition.new(name: name)
|
|
28
|
+
definition.instance_eval(&block)
|
|
29
|
+
definition.validate!
|
|
30
|
+
|
|
31
|
+
builder.add_operation(
|
|
32
|
+
kind: :compute,
|
|
33
|
+
name: trace_name,
|
|
34
|
+
depends_on: definition.dependencies,
|
|
35
|
+
callable: lambda do |**values|
|
|
36
|
+
definition.resolve(values)
|
|
37
|
+
end
|
|
38
|
+
)
|
|
39
|
+
builder.add_operation(
|
|
40
|
+
kind: :compute,
|
|
41
|
+
name: name,
|
|
42
|
+
depends_on: [trace_name],
|
|
43
|
+
callable: lambda do |**values|
|
|
44
|
+
values.fetch(trace_name).fetch(:value)
|
|
45
|
+
end
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Definition
|
|
51
|
+
attr_reader :dependencies
|
|
52
|
+
|
|
53
|
+
def initialize(name:)
|
|
54
|
+
@name = name.to_sym
|
|
55
|
+
@steps = []
|
|
56
|
+
@dependencies = []
|
|
57
|
+
@has_start = false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def base(value)
|
|
61
|
+
raise ArgumentError, "formula :#{@name} can define base only once" if @has_start
|
|
62
|
+
|
|
63
|
+
@has_start = true
|
|
64
|
+
@steps << { operation: :base, value: Float(value) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def from(name)
|
|
68
|
+
raise ArgumentError, "formula :#{@name} can define from only once" if @has_start
|
|
69
|
+
|
|
70
|
+
source_name = name.to_sym
|
|
71
|
+
@has_start = true
|
|
72
|
+
@dependencies << source_name
|
|
73
|
+
@steps << { operation: :from, source: source_name }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def add(value_or_source)
|
|
77
|
+
add_numeric_or_source_step(:add, value_or_source)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def subtract(value_or_source)
|
|
81
|
+
add_numeric_or_source_step(:subtract, value_or_source)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def multiply_by(value)
|
|
85
|
+
@steps << { operation: :multiply_by, value: Float(value) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def divide_by(value)
|
|
89
|
+
raise ArgumentError, "formula :#{@name} divide_by cannot use zero" if Float(value).zero?
|
|
90
|
+
|
|
91
|
+
@steps << { operation: :divide_by, value: Float(value) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def clamp(min, max)
|
|
95
|
+
minimum = Float(min)
|
|
96
|
+
maximum = Float(max)
|
|
97
|
+
raise ArgumentError, "formula :#{@name} clamp min cannot exceed max" if minimum > maximum
|
|
98
|
+
|
|
99
|
+
@steps << { operation: :clamp, min: minimum, max: maximum }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def round(precision = 0)
|
|
103
|
+
@steps << { operation: :round, precision: Integer(precision) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate!
|
|
107
|
+
raise ArgumentError, "formula :#{@name} requires base or from" unless @has_start
|
|
108
|
+
raise ArgumentError, "formula :#{@name} requires at least one step" if @steps.empty?
|
|
109
|
+
|
|
110
|
+
@dependencies.uniq!
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def resolve(values)
|
|
114
|
+
current = nil
|
|
115
|
+
trace = []
|
|
116
|
+
|
|
117
|
+
@steps.each do |step|
|
|
118
|
+
before = current
|
|
119
|
+
current = apply_step(current, step, values)
|
|
120
|
+
trace << step.merge(before: before, after: current)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
value: current,
|
|
125
|
+
dependencies: dependencies,
|
|
126
|
+
steps: trace.freeze
|
|
127
|
+
}
|
|
128
|
+
rescue ArgumentError, TypeError
|
|
129
|
+
{
|
|
130
|
+
value: 0.0,
|
|
131
|
+
dependencies: dependencies,
|
|
132
|
+
error: :invalid_numeric_formula,
|
|
133
|
+
steps: trace.freeze
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def add_numeric_or_source_step(operation, value_or_source)
|
|
140
|
+
if value_or_source.is_a?(Symbol)
|
|
141
|
+
@dependencies << value_or_source
|
|
142
|
+
@steps << { operation: operation, source: value_or_source }
|
|
143
|
+
else
|
|
144
|
+
@steps << { operation: operation, value: Float(value_or_source) }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def apply_step(current, step, values)
|
|
149
|
+
case step.fetch(:operation)
|
|
150
|
+
when :base
|
|
151
|
+
step.fetch(:value)
|
|
152
|
+
when :from
|
|
153
|
+
numeric_value(values.fetch(step.fetch(:source)))
|
|
154
|
+
when :add
|
|
155
|
+
current + operand(step, values)
|
|
156
|
+
when :subtract
|
|
157
|
+
current - operand(step, values)
|
|
158
|
+
when :multiply_by
|
|
159
|
+
current * step.fetch(:value)
|
|
160
|
+
when :divide_by
|
|
161
|
+
current / step.fetch(:value)
|
|
162
|
+
when :clamp
|
|
163
|
+
[[current, step.fetch(:min)].max, step.fetch(:max)].min
|
|
164
|
+
when :round
|
|
165
|
+
current.round(step.fetch(:precision))
|
|
166
|
+
else
|
|
167
|
+
current
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def operand(step, values)
|
|
172
|
+
return numeric_value(values.fetch(step.fetch(:source))) if step.key?(:source)
|
|
173
|
+
|
|
174
|
+
step.fetch(:value)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def numeric_value(value)
|
|
178
|
+
Float(value)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Language
|
|
7
|
+
module PiecewisePack
|
|
8
|
+
UNSET = Object.new.freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def manifest
|
|
13
|
+
Igniter::Contracts::PackManifest.new(
|
|
14
|
+
name: :extensions_language_piecewise,
|
|
15
|
+
registry_contracts: [Igniter::Contracts::PackManifest.dsl_keyword(:piecewise)]
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def install_into(kernel)
|
|
20
|
+
kernel.dsl_keywords.register(:piecewise, piecewise_keyword)
|
|
21
|
+
kernel
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def piecewise_keyword
|
|
25
|
+
Igniter::Contracts::DslKeyword.new(:piecewise) do |name, on:, builder:, depends_on: [], decision: nil, &block|
|
|
26
|
+
raise ArgumentError, "piecewise :#{name} requires a block" unless block
|
|
27
|
+
|
|
28
|
+
selector_name = on.to_sym
|
|
29
|
+
decision_name = (decision || :"#{name}_decision").to_sym
|
|
30
|
+
dependency_names = [selector_name, *Array(depends_on).map(&:to_sym)].uniq
|
|
31
|
+
definition = Definition.new(name: name, selector_name: selector_name)
|
|
32
|
+
definition.instance_eval(&block)
|
|
33
|
+
definition.validate!
|
|
34
|
+
|
|
35
|
+
builder.add_operation(
|
|
36
|
+
kind: :compute,
|
|
37
|
+
name: decision_name,
|
|
38
|
+
depends_on: dependency_names,
|
|
39
|
+
callable: lambda do |**values|
|
|
40
|
+
definition.resolve(values)
|
|
41
|
+
end
|
|
42
|
+
)
|
|
43
|
+
builder.add_operation(
|
|
44
|
+
kind: :compute,
|
|
45
|
+
name: name,
|
|
46
|
+
depends_on: [decision_name],
|
|
47
|
+
callable: lambda do |**values|
|
|
48
|
+
values.fetch(decision_name).fetch(:value)
|
|
49
|
+
end
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def invoke_value(callable_or_value, kwargs)
|
|
55
|
+
return callable_or_value unless callable_or_value.respond_to?(:call)
|
|
56
|
+
|
|
57
|
+
parameters = callable_or_value.parameters
|
|
58
|
+
accepts_any_keywords = parameters.any? { |kind, _name| kind == :keyrest }
|
|
59
|
+
return callable_or_value.call(**kwargs) if accepts_any_keywords
|
|
60
|
+
|
|
61
|
+
accepted = parameters.select { |kind, _name| %i[key keyreq].include?(kind) }.map(&:last)
|
|
62
|
+
callable_or_value.call(**kwargs.slice(*accepted))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class Definition
|
|
66
|
+
def initialize(name:, selector_name:)
|
|
67
|
+
@name = name.to_sym
|
|
68
|
+
@selector_name = selector_name.to_sym
|
|
69
|
+
@cases = []
|
|
70
|
+
@default_case = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def eq(match, id: nil, value: UNSET, &block)
|
|
74
|
+
add_case(:eq, match, id: id, value: value, block: block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def between(range, id: nil, value: UNSET, &block)
|
|
78
|
+
raise ArgumentError, "piecewise :#{@name} between requires a Range" unless range.is_a?(Range)
|
|
79
|
+
|
|
80
|
+
add_case(:between, range, id: id, value: value, block: block)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def in(values, id: nil, value: UNSET, &block)
|
|
84
|
+
array = Array(values)
|
|
85
|
+
raise ArgumentError, "piecewise :#{@name} in requires a non-empty list" if array.empty?
|
|
86
|
+
|
|
87
|
+
add_case(:in, array.freeze, id: id, value: value, block: block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def matches(pattern, id: nil, value: UNSET, &block)
|
|
91
|
+
raise ArgumentError, "piecewise :#{@name} matches requires a Regexp" unless pattern.is_a?(Regexp)
|
|
92
|
+
|
|
93
|
+
add_case(:matches, pattern, id: id, value: value, block: block)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def default(id: :default, value: UNSET, &block)
|
|
97
|
+
raise ArgumentError, "piecewise :#{@name} can define only one default" if @default_case
|
|
98
|
+
|
|
99
|
+
@default_case = case_payload(:default, :default, id: id, value: value, block: block)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def validate!
|
|
103
|
+
raise ArgumentError, "piecewise :#{@name} requires at least one case" if @cases.empty?
|
|
104
|
+
raise ArgumentError, "piecewise :#{@name} requires a default case" unless @default_case
|
|
105
|
+
|
|
106
|
+
duplicate_ids = (@cases.map { |entry| entry.fetch(:id) } + [@default_case.fetch(:id)])
|
|
107
|
+
.group_by { |id| id }
|
|
108
|
+
.select { |_id, group| group.length > 1 }
|
|
109
|
+
.keys
|
|
110
|
+
return if duplicate_ids.empty?
|
|
111
|
+
|
|
112
|
+
raise ArgumentError, "piecewise :#{@name} has duplicate case ids: #{duplicate_ids.join(", ")}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def resolve(kwargs)
|
|
116
|
+
selector_value = kwargs.fetch(@selector_name)
|
|
117
|
+
selected = @cases.find { |entry| match?(entry, selector_value) } || @default_case
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
case: selected.fetch(:id),
|
|
121
|
+
value: PiecewisePack.invoke_value(selected.fetch(:value), kwargs),
|
|
122
|
+
matcher: selected.fetch(:matcher),
|
|
123
|
+
matched_on: selected.fetch(:match),
|
|
124
|
+
selector: @selector_name,
|
|
125
|
+
selector_value: selector_value
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def add_case(matcher, match, id:, value:, block:)
|
|
132
|
+
@cases << case_payload(matcher, match, id: id || :"case_#{@cases.length + 1}", value: value, block: block)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def case_payload(matcher, match, id:, value:, block:)
|
|
136
|
+
raise ArgumentError, "piecewise :#{@name} case cannot combine value: with a block" if !value.equal?(UNSET) && block
|
|
137
|
+
raise ArgumentError, "piecewise :#{@name} case requires value: or a block" if value.equal?(UNSET) && !block
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
id: id.to_sym,
|
|
141
|
+
matcher: matcher,
|
|
142
|
+
match: match,
|
|
143
|
+
value: block || value
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def match?(entry, selector_value)
|
|
148
|
+
case entry.fetch(:matcher)
|
|
149
|
+
when :eq
|
|
150
|
+
selector_value == entry.fetch(:match)
|
|
151
|
+
when :between
|
|
152
|
+
entry.fetch(:match).cover?(selector_value)
|
|
153
|
+
when :in
|
|
154
|
+
entry.fetch(:match).include?(selector_value)
|
|
155
|
+
when :matches
|
|
156
|
+
!!(selector_value.to_s =~ entry.fetch(:match))
|
|
157
|
+
else
|
|
158
|
+
false
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Language
|
|
7
|
+
module ScalePack
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def manifest
|
|
11
|
+
Igniter::Contracts::PackManifest.new(
|
|
12
|
+
name: :extensions_language_scale,
|
|
13
|
+
registry_contracts: [Igniter::Contracts::PackManifest.dsl_keyword(:scale)]
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def install_into(kernel)
|
|
18
|
+
kernel.dsl_keywords.register(:scale, scale_keyword)
|
|
19
|
+
kernel
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def scale_keyword
|
|
23
|
+
Igniter::Contracts::DslKeyword.new(:scale) do |name, from:, builder:, trace: nil, &block|
|
|
24
|
+
raise ArgumentError, "scale :#{name} requires a block" unless block
|
|
25
|
+
|
|
26
|
+
source_name = from.to_sym
|
|
27
|
+
trace_name = (trace || :"#{name}_trace").to_sym
|
|
28
|
+
definition = Definition.new(name: name, source_name: source_name)
|
|
29
|
+
definition.instance_eval(&block)
|
|
30
|
+
definition.validate!
|
|
31
|
+
|
|
32
|
+
builder.add_operation(
|
|
33
|
+
kind: :compute,
|
|
34
|
+
name: trace_name,
|
|
35
|
+
depends_on: [source_name],
|
|
36
|
+
callable: lambda do |**values|
|
|
37
|
+
definition.resolve(values.fetch(source_name))
|
|
38
|
+
end
|
|
39
|
+
)
|
|
40
|
+
builder.add_operation(
|
|
41
|
+
kind: :compute,
|
|
42
|
+
name: name,
|
|
43
|
+
depends_on: [trace_name],
|
|
44
|
+
callable: lambda do |**values|
|
|
45
|
+
values.fetch(trace_name).fetch(:value)
|
|
46
|
+
end
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Definition
|
|
52
|
+
def initialize(name:, source_name:)
|
|
53
|
+
@name = name.to_sym
|
|
54
|
+
@source_name = source_name.to_sym
|
|
55
|
+
@steps = []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def divide_by(value)
|
|
59
|
+
raise ArgumentError, "scale :#{@name} divide_by cannot use zero" if Float(value).zero?
|
|
60
|
+
|
|
61
|
+
add_step(:divide_by, value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def multiply_by(value)
|
|
65
|
+
add_step(:multiply_by, value)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add(value)
|
|
69
|
+
add_step(:add, value)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def subtract(value)
|
|
73
|
+
add_step(:subtract, value)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clamp(min, max)
|
|
77
|
+
minimum = Float(min)
|
|
78
|
+
maximum = Float(max)
|
|
79
|
+
raise ArgumentError, "scale :#{@name} clamp min cannot exceed max" if minimum > maximum
|
|
80
|
+
|
|
81
|
+
@steps << { operation: :clamp, min: minimum, max: maximum }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def round(precision = 0)
|
|
85
|
+
@steps << { operation: :round, precision: Integer(precision) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def validate!
|
|
89
|
+
raise ArgumentError, "scale :#{@name} requires at least one step" if @steps.empty?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def resolve(source_value)
|
|
93
|
+
current = Float(source_value)
|
|
94
|
+
trace = []
|
|
95
|
+
|
|
96
|
+
@steps.each do |step|
|
|
97
|
+
before = current
|
|
98
|
+
current = apply_step(current, step)
|
|
99
|
+
trace << step.merge(before: before, after: current)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
value: current,
|
|
104
|
+
source: @source_name,
|
|
105
|
+
source_value: source_value,
|
|
106
|
+
steps: trace.freeze
|
|
107
|
+
}
|
|
108
|
+
rescue ArgumentError, TypeError
|
|
109
|
+
{
|
|
110
|
+
value: 0.0,
|
|
111
|
+
source: @source_name,
|
|
112
|
+
source_value: source_value,
|
|
113
|
+
error: :invalid_numeric_source,
|
|
114
|
+
steps: trace.freeze
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def add_step(operation, value)
|
|
121
|
+
@steps << { operation: operation, value: Float(value) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def apply_step(current, step)
|
|
125
|
+
case step.fetch(:operation)
|
|
126
|
+
when :divide_by
|
|
127
|
+
current / step.fetch(:value)
|
|
128
|
+
when :multiply_by
|
|
129
|
+
current * step.fetch(:value)
|
|
130
|
+
when :add
|
|
131
|
+
current + step.fetch(:value)
|
|
132
|
+
when :subtract
|
|
133
|
+
current - step.fetch(:value)
|
|
134
|
+
when :clamp
|
|
135
|
+
[[current, step.fetch(:min)].max, step.fetch(:max)].min
|
|
136
|
+
when :round
|
|
137
|
+
current.round(step.fetch(:precision))
|
|
138
|
+
else
|
|
139
|
+
current
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module LookupPack
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def manifest
|
|
10
|
+
Igniter::Contracts::PackManifest.new(
|
|
11
|
+
name: :extensions_lookup,
|
|
12
|
+
registry_contracts: [Igniter::Contracts::PackManifest.dsl_keyword(:lookup)]
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def install_into(kernel)
|
|
17
|
+
kernel.dsl_keywords.register(:lookup, lookup_keyword)
|
|
18
|
+
kernel
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def lookup_keyword
|
|
22
|
+
Igniter::Contracts::DslKeyword.new(:lookup) do |name, from:, builder:, key: nil, dig: nil, default: Igniter::Contracts::PathAccess::NO_DEFAULT|
|
|
23
|
+
source_name = from.to_sym
|
|
24
|
+
path = Igniter::Contracts::PathAccess.normalize_path(
|
|
25
|
+
keyword_name: :lookup,
|
|
26
|
+
key: key,
|
|
27
|
+
dig: dig
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
builder.add_operation(
|
|
31
|
+
kind: :compute,
|
|
32
|
+
name: name,
|
|
33
|
+
depends_on: [source_name],
|
|
34
|
+
callable: lambda do |**values|
|
|
35
|
+
source = values.fetch(source_name)
|
|
36
|
+
Igniter::Contracts::PathAccess.fetch_path(
|
|
37
|
+
source,
|
|
38
|
+
path,
|
|
39
|
+
source_name: source_name,
|
|
40
|
+
keyword_name: :lookup,
|
|
41
|
+
default: default
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Mcp
|
|
7
|
+
class CreatorSession
|
|
8
|
+
ATTRIBUTES = %i[
|
|
9
|
+
name
|
|
10
|
+
kind
|
|
11
|
+
namespace
|
|
12
|
+
profile
|
|
13
|
+
capabilities
|
|
14
|
+
scope
|
|
15
|
+
root
|
|
16
|
+
mode
|
|
17
|
+
pack
|
|
18
|
+
target_profile
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
attr_reader(*ATTRIBUTES)
|
|
22
|
+
|
|
23
|
+
def initialize(name: nil, kind: nil, namespace: "MyCompany::IgniterPacks", profile: nil, capabilities: nil,
|
|
24
|
+
scope: nil, root: nil, mode: :skip_existing, pack: nil, target_profile: nil)
|
|
25
|
+
@name = name
|
|
26
|
+
@kind = kind
|
|
27
|
+
@namespace = namespace
|
|
28
|
+
@profile = profile
|
|
29
|
+
@capabilities = capabilities
|
|
30
|
+
@scope = scope
|
|
31
|
+
@root = root
|
|
32
|
+
@mode = mode
|
|
33
|
+
@pack = pack
|
|
34
|
+
@target_profile = target_profile
|
|
35
|
+
freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.from_h(payload, target_profile: nil)
|
|
39
|
+
new(
|
|
40
|
+
name: payload[:name] || payload["name"],
|
|
41
|
+
kind: payload[:kind] || payload["kind"],
|
|
42
|
+
namespace: payload[:namespace] || payload["namespace"] || "MyCompany::IgniterPacks",
|
|
43
|
+
profile: payload[:profile] || payload["profile"],
|
|
44
|
+
capabilities: payload[:capabilities] || payload["capabilities"],
|
|
45
|
+
scope: payload[:scope] || payload["scope"],
|
|
46
|
+
root: payload[:root] || payload["root"],
|
|
47
|
+
mode: payload[:mode] || payload["mode"] || :skip_existing,
|
|
48
|
+
pack: payload[:pack] || payload["pack"],
|
|
49
|
+
target_profile: target_profile
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def apply(**updates)
|
|
54
|
+
self.class.new(
|
|
55
|
+
**ATTRIBUTES.to_h { |attribute| [attribute, updates.fetch(attribute, public_send(attribute))] }
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def wizard
|
|
60
|
+
CreatorPack.wizard(
|
|
61
|
+
name: name,
|
|
62
|
+
kind: kind,
|
|
63
|
+
namespace: namespace,
|
|
64
|
+
profile: profile,
|
|
65
|
+
capabilities: capabilities,
|
|
66
|
+
scope: scope,
|
|
67
|
+
root: root,
|
|
68
|
+
mode: mode,
|
|
69
|
+
pack: pack,
|
|
70
|
+
target_profile: target_profile
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def workflow_payload
|
|
75
|
+
wizard.workflow.to_h
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def write_plan_payload
|
|
79
|
+
wizard.writer.plan.to_h
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def write_payload
|
|
83
|
+
wizard.writer.write.to_h
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_h
|
|
87
|
+
wizard.to_h.merge(
|
|
88
|
+
session: {
|
|
89
|
+
name: name,
|
|
90
|
+
kind: kind,
|
|
91
|
+
namespace: namespace,
|
|
92
|
+
profile: profile,
|
|
93
|
+
capabilities: Array(capabilities).map(&:to_sym),
|
|
94
|
+
scope: scope,
|
|
95
|
+
root: root,
|
|
96
|
+
mode: mode,
|
|
97
|
+
target_profile_fingerprint: target_profile&.fingerprint
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Contracts
|
|
6
|
+
module Mcp
|
|
7
|
+
class ToolArgument
|
|
8
|
+
attr_reader :name, :type, :summary, :required, :default, :enum
|
|
9
|
+
|
|
10
|
+
def initialize(name:, type:, summary:, required: false, default: nil, enum: nil)
|
|
11
|
+
@name = name.to_sym
|
|
12
|
+
@type = type.to_sym
|
|
13
|
+
@summary = summary
|
|
14
|
+
@required = required == true
|
|
15
|
+
@default = default
|
|
16
|
+
@enum = Array(enum).map(&:to_sym).freeze
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
payload = {
|
|
22
|
+
name: name,
|
|
23
|
+
type: type,
|
|
24
|
+
summary: summary,
|
|
25
|
+
required: required
|
|
26
|
+
}
|
|
27
|
+
payload[:default] = default unless default.nil?
|
|
28
|
+
payload[:enum] = enum unless enum.empty?
|
|
29
|
+
payload
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|