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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +381 -0
  3. data/lib/igniter/extensions/contracts/aggregate_pack.rb +103 -0
  4. data/lib/igniter/extensions/contracts/audit/builder.rb +132 -0
  5. data/lib/igniter/extensions/contracts/audit/event.rb +34 -0
  6. data/lib/igniter/extensions/contracts/audit/snapshot.rb +44 -0
  7. data/lib/igniter/extensions/contracts/audit_pack.rb +60 -0
  8. data/lib/igniter/extensions/contracts/branch_pack.rb +199 -0
  9. data/lib/igniter/extensions/contracts/capabilities/declaration.rb +31 -0
  10. data/lib/igniter/extensions/contracts/capabilities/error.rb +35 -0
  11. data/lib/igniter/extensions/contracts/capabilities/policy.rb +20 -0
  12. data/lib/igniter/extensions/contracts/capabilities/report.rb +47 -0
  13. data/lib/igniter/extensions/contracts/capabilities/violation.rb +30 -0
  14. data/lib/igniter/extensions/contracts/capabilities_pack.rb +146 -0
  15. data/lib/igniter/extensions/contracts/collection_pack.rb +212 -0
  16. data/lib/igniter/extensions/contracts/commerce_pack.rb +91 -0
  17. data/lib/igniter/extensions/contracts/compose_pack.rb +213 -0
  18. data/lib/igniter/extensions/contracts/content_addressing/cache.rb +59 -0
  19. data/lib/igniter/extensions/contracts/content_addressing/content_key.rb +63 -0
  20. data/lib/igniter/extensions/contracts/content_addressing/declaration.rb +47 -0
  21. data/lib/igniter/extensions/contracts/content_addressing_pack.rb +90 -0
  22. data/lib/igniter/extensions/contracts/creator/profile.rb +196 -0
  23. data/lib/igniter/extensions/contracts/creator/report.rb +85 -0
  24. data/lib/igniter/extensions/contracts/creator/scaffold.rb +461 -0
  25. data/lib/igniter/extensions/contracts/creator/scope.rb +79 -0
  26. data/lib/igniter/extensions/contracts/creator/wizard.rb +269 -0
  27. data/lib/igniter/extensions/contracts/creator/workflow.rb +189 -0
  28. data/lib/igniter/extensions/contracts/creator/workflow_step.rb +51 -0
  29. data/lib/igniter/extensions/contracts/creator/write_result.rb +48 -0
  30. data/lib/igniter/extensions/contracts/creator/write_step.rb +63 -0
  31. data/lib/igniter/extensions/contracts/creator/writer.rb +131 -0
  32. data/lib/igniter/extensions/contracts/creator_pack.rb +128 -0
  33. data/lib/igniter/extensions/contracts/dataflow/aggregate_operators.rb +119 -0
  34. data/lib/igniter/extensions/contracts/dataflow/aggregate_state.rb +60 -0
  35. data/lib/igniter/extensions/contracts/dataflow/builder.rb +66 -0
  36. data/lib/igniter/extensions/contracts/dataflow/collection_result.rb +70 -0
  37. data/lib/igniter/extensions/contracts/dataflow/diff.rb +37 -0
  38. data/lib/igniter/extensions/contracts/dataflow/item_result.rb +44 -0
  39. data/lib/igniter/extensions/contracts/dataflow/result.rb +58 -0
  40. data/lib/igniter/extensions/contracts/dataflow/session.rb +173 -0
  41. data/lib/igniter/extensions/contracts/dataflow/window_filter.rb +49 -0
  42. data/lib/igniter/extensions/contracts/dataflow_pack.rb +66 -0
  43. data/lib/igniter/extensions/contracts/debug/pack_audit.rb +181 -0
  44. data/lib/igniter/extensions/contracts/debug/pack_snapshot.rb +46 -0
  45. data/lib/igniter/extensions/contracts/debug/profile_snapshot.rb +50 -0
  46. data/lib/igniter/extensions/contracts/debug/report.rb +50 -0
  47. data/lib/igniter/extensions/contracts/debug_pack.rb +115 -0
  48. data/lib/igniter/extensions/contracts/differential/divergence.rb +37 -0
  49. data/lib/igniter/extensions/contracts/differential/formatter.rb +85 -0
  50. data/lib/igniter/extensions/contracts/differential/report.rb +83 -0
  51. data/lib/igniter/extensions/contracts/differential/runner.rb +136 -0
  52. data/lib/igniter/extensions/contracts/differential_pack.rb +61 -0
  53. data/lib/igniter/extensions/contracts/execution_report_pack.rb +38 -0
  54. data/lib/igniter/extensions/contracts/incremental/formatter.rb +60 -0
  55. data/lib/igniter/extensions/contracts/incremental/node_state.rb +30 -0
  56. data/lib/igniter/extensions/contracts/incremental/result.rb +65 -0
  57. data/lib/igniter/extensions/contracts/incremental/session.rb +146 -0
  58. data/lib/igniter/extensions/contracts/incremental_pack.rb +40 -0
  59. data/lib/igniter/extensions/contracts/invariants/builder.rb +27 -0
  60. data/lib/igniter/extensions/contracts/invariants/cases_report.rb +47 -0
  61. data/lib/igniter/extensions/contracts/invariants/error.rb +34 -0
  62. data/lib/igniter/extensions/contracts/invariants/invariant.rb +30 -0
  63. data/lib/igniter/extensions/contracts/invariants/report.rb +45 -0
  64. data/lib/igniter/extensions/contracts/invariants/suite.rb +36 -0
  65. data/lib/igniter/extensions/contracts/invariants/violation.rb +39 -0
  66. data/lib/igniter/extensions/contracts/invariants_pack.rb +88 -0
  67. data/lib/igniter/extensions/contracts/journal_pack.rb +55 -0
  68. data/lib/igniter/extensions/contracts/language/formula_pack.rb +185 -0
  69. data/lib/igniter/extensions/contracts/language/piecewise_pack.rb +166 -0
  70. data/lib/igniter/extensions/contracts/language/scale_pack.rb +147 -0
  71. data/lib/igniter/extensions/contracts/lookup_pack.rb +50 -0
  72. data/lib/igniter/extensions/contracts/mcp/creator_session.rb +105 -0
  73. data/lib/igniter/extensions/contracts/mcp/tool_argument.rb +35 -0
  74. data/lib/igniter/extensions/contracts/mcp/tool_definition.rb +33 -0
  75. data/lib/igniter/extensions/contracts/mcp/tool_result.rb +28 -0
  76. data/lib/igniter/extensions/contracts/mcp_pack.rb +335 -0
  77. data/lib/igniter/extensions/contracts/provenance/builder.rb +80 -0
  78. data/lib/igniter/extensions/contracts/provenance/lineage.rb +59 -0
  79. data/lib/igniter/extensions/contracts/provenance/node_trace.rb +53 -0
  80. data/lib/igniter/extensions/contracts/provenance/text_formatter.rb +62 -0
  81. data/lib/igniter/extensions/contracts/provenance_pack.rb +52 -0
  82. data/lib/igniter/extensions/contracts/reactive/builder.rb +43 -0
  83. data/lib/igniter/extensions/contracts/reactive/dispatch_result.rb +59 -0
  84. data/lib/igniter/extensions/contracts/reactive/engine.rb +79 -0
  85. data/lib/igniter/extensions/contracts/reactive/event.rb +36 -0
  86. data/lib/igniter/extensions/contracts/reactive/matcher.rb +20 -0
  87. data/lib/igniter/extensions/contracts/reactive/plan.rb +58 -0
  88. data/lib/igniter/extensions/contracts/reactive/subscription.rb +29 -0
  89. data/lib/igniter/extensions/contracts/reactive_pack.rb +169 -0
  90. data/lib/igniter/extensions/contracts/saga/compensation.rb +25 -0
  91. data/lib/igniter/extensions/contracts/saga/compensation_record.rb +28 -0
  92. data/lib/igniter/extensions/contracts/saga/compensation_set.rb +47 -0
  93. data/lib/igniter/extensions/contracts/saga/formatter.rb +39 -0
  94. data/lib/igniter/extensions/contracts/saga/result.rb +56 -0
  95. data/lib/igniter/extensions/contracts/saga/runner.rb +124 -0
  96. data/lib/igniter/extensions/contracts/saga_pack.rb +56 -0
  97. data/lib/igniter/extensions/contracts.rb +445 -0
  98. data/lib/igniter/extensions.rb +6 -0
  99. data/lib/igniter-extensions.rb +3 -0
  100. 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