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,461 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Creator
7
+ class Scaffold
8
+ SUPPORTED_KINDS = %i[feature operational bundle].freeze
9
+
10
+ attr_reader :name, :kind, :namespace, :profile, :scope
11
+
12
+ def initialize(name:, kind:, namespace:, profile:, scope:)
13
+ @name = normalize_name(name)
14
+ @kind = normalize_kind(kind)
15
+ @namespace = normalize_namespace(namespace)
16
+ @profile = profile
17
+ @scope = scope
18
+ freeze
19
+ end
20
+
21
+ def pack_class_name
22
+ "#{camelize(name)}Pack"
23
+ end
24
+
25
+ def pack_constant
26
+ "#{namespace}::#{pack_class_name}"
27
+ end
28
+
29
+ def namespace_path
30
+ namespace.split("::").map { |part| underscore(part) }.join("/")
31
+ end
32
+
33
+ def pack_file_path
34
+ "#{scope.root}/#{namespace_path}/#{name}_pack.rb"
35
+ end
36
+
37
+ def spec_file_path
38
+ "#{scope.spec_root}/#{namespace_path}/#{name}_pack_spec.rb"
39
+ end
40
+
41
+ def example_file_path
42
+ "#{scope.example_root}/#{name}_pack.rb"
43
+ end
44
+
45
+ def readme_file_path
46
+ scope.readme_path
47
+ end
48
+
49
+ def files
50
+ {
51
+ pack_file_path => pack_template,
52
+ spec_file_path => spec_template,
53
+ example_file_path => example_template,
54
+ readme_file_path => readme_template
55
+ }
56
+ end
57
+
58
+ def summary
59
+ {
60
+ name: name,
61
+ kind: kind,
62
+ namespace: namespace,
63
+ profile: profile.to_h,
64
+ scope: scope.to_h,
65
+ pack_constant: pack_constant,
66
+ files: files.keys
67
+ }
68
+ end
69
+
70
+ def to_h
71
+ summary.merge(files: files)
72
+ end
73
+
74
+ private
75
+
76
+ def normalize_name(name)
77
+ name.to_s.strip.gsub(/_pack\z/, "").downcase
78
+ end
79
+
80
+ def normalize_kind(kind)
81
+ value = kind.to_sym
82
+ return value if SUPPORTED_KINDS.include?(value)
83
+
84
+ raise ArgumentError, "unsupported creator scaffold kind #{kind.inspect}"
85
+ end
86
+
87
+ def normalize_namespace(namespace)
88
+ value = namespace.to_s.strip
89
+ return value unless value.empty?
90
+
91
+ "MyCompany::IgniterPacks"
92
+ end
93
+
94
+ def camelize(value)
95
+ value.split("_").map(&:capitalize).join
96
+ end
97
+
98
+ def underscore(value)
99
+ value.gsub(/([a-z\d])([A-Z])/, "\\1_\\2").downcase
100
+ end
101
+
102
+ def pack_template
103
+ case kind
104
+ when :feature then feature_pack_template
105
+ when :operational then operational_pack_template
106
+ when :bundle then bundle_pack_template
107
+ end
108
+ end
109
+
110
+ def spec_template
111
+ case kind
112
+ when :feature then feature_spec_template
113
+ when :operational then operational_spec_template
114
+ when :bundle then bundle_spec_template
115
+ end
116
+ end
117
+
118
+ def example_template
119
+ case kind
120
+ when :feature then feature_example_template
121
+ when :operational then operational_example_template
122
+ when :bundle then bundle_example_template
123
+ end
124
+ end
125
+
126
+ def readme_template
127
+ <<~MARKDOWN
128
+ # #{pack_class_name}
129
+
130
+ #{pack_class_name} is a #{kind} pack built on top of `igniter-contracts`.
131
+
132
+ ## Files
133
+
134
+ - `#{pack_file_path}` — pack implementation
135
+ - `#{spec_file_path}` — package-owned spec
136
+ - `#{example_file_path}` — runnable example
137
+
138
+ ## Authoring Profile
139
+
140
+ - `#{profile.name}`
141
+ - capabilities: `#{profile.capabilities.join("`, `")}`
142
+ - registry seams: `#{profile.registry_capabilities.join("`, `")}`
143
+ #{runtime_dependency_hints_markdown}
144
+ #{development_dependency_hints_markdown}
145
+ #{development_hints_markdown}
146
+
147
+ ## Target Scope
148
+
149
+ - `#{scope.name}`
150
+ - pack root: `#{scope.root}`
151
+ - spec root: `#{scope.spec_root}`
152
+ - example root: `#{scope.example_root}`
153
+ #{packaging_hints_markdown}
154
+
155
+ ## Recommended Workflow
156
+
157
+ 1. Fill in the pack implementation using only public `Igniter::Contracts` APIs.
158
+ 2. Run the generated spec and example.
159
+ 3. Use `Igniter::Extensions::Contracts.audit_pack(#{pack_constant}, environment)` to verify completeness.
160
+ 4. Only then decide whether the pack remains app-local or becomes a distributable gem.
161
+ MARKDOWN
162
+ end
163
+
164
+ def feature_pack_template
165
+ <<~RUBY
166
+ # frozen_string_literal: true
167
+
168
+ module #{namespace}
169
+ module #{pack_class_name}
170
+ class << self
171
+ def manifest
172
+ Igniter::Contracts::PackManifest.new(#{feature_manifest_body})
173
+ end
174
+
175
+ def install_into(kernel)
176
+ kernel.nodes.register(:#{name}, Igniter::Contracts::NodeType.new(kind: :#{name}, metadata: { category: :custom }))
177
+ kernel.dsl_keywords.register(:#{name}, #{name}_keyword)
178
+ kernel.validators.register(:#{name}_sources, method(:validate_#{name}_sources))
179
+ kernel.runtime_handlers.register(:#{name}, method(:handle_#{name}))
180
+ kernel
181
+ end
182
+
183
+ def #{name}_keyword
184
+ Igniter::Contracts::DslKeyword.new(:#{name}) do |name, from:, builder:|
185
+ builder.add_operation(kind: :#{name}, name: name, from: from.to_sym)
186
+ end
187
+ end
188
+
189
+ def validate_#{name}_sources(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
190
+ []
191
+ end
192
+
193
+ def handle_#{name}(operation:, state:, **)
194
+ state.fetch(operation.attributes.fetch(:from).to_sym)
195
+ end
196
+ end
197
+ end
198
+ end
199
+ RUBY
200
+ end
201
+
202
+ def operational_pack_template
203
+ <<~RUBY
204
+ # frozen_string_literal: true
205
+
206
+ module #{namespace}
207
+ module #{pack_class_name}
208
+ class << self
209
+ def manifest
210
+ Igniter::Contracts::PackManifest.new(#{operational_manifest_body})
211
+ end
212
+
213
+ def install_into(kernel)
214
+ kernel.effects.register(:#{name}, method(:apply_#{name}))
215
+ kernel.executors.register(:#{name}_inline, method(:execute_#{name}_inline))
216
+ kernel
217
+ end
218
+
219
+ def apply_#{name}(invocation:)
220
+ invocation.payload
221
+ end
222
+
223
+ def execute_#{name}_inline(invocation:)
224
+ invocation.runtime.execute(
225
+ invocation.compiled_graph,
226
+ inputs: invocation.inputs,
227
+ profile: invocation.profile
228
+ )
229
+ end
230
+ end
231
+ end
232
+ end
233
+ RUBY
234
+ end
235
+
236
+ def bundle_pack_template
237
+ <<~RUBY
238
+ # frozen_string_literal: true
239
+
240
+ module #{namespace}
241
+ module #{pack_class_name}
242
+ class << self
243
+ def manifest
244
+ Igniter::Contracts::PackManifest.new(
245
+ name: :#{name},
246
+ #{bundle_manifest_body}
247
+ )
248
+ end
249
+
250
+ def install_into(kernel)
251
+ #{bundle_diagnostic_install_lines}
252
+ kernel
253
+ end
254
+
255
+ #{bundle_diagnostic_contributor_template}
256
+ end
257
+ end
258
+ end
259
+ RUBY
260
+ end
261
+
262
+ def feature_spec_template
263
+ <<~RUBY
264
+ # frozen_string_literal: true
265
+
266
+ RSpec.describe #{pack_constant} do
267
+ it "installs a feature node pack through public contracts APIs" do
268
+ environment = Igniter::Contracts.with(described_class)
269
+
270
+ result = environment.run(inputs: { source: "value" }) do
271
+ input :source
272
+ #{name} :result, from: :source
273
+ output :result
274
+ end
275
+
276
+ expect(result.output(:result)).to eq("value")
277
+ end
278
+ end
279
+ RUBY
280
+ end
281
+
282
+ def operational_spec_template
283
+ <<~RUBY
284
+ # frozen_string_literal: true
285
+
286
+ RSpec.describe #{pack_constant} do
287
+ it "installs effect and executor seams through public contracts APIs" do
288
+ environment = Igniter::Contracts.with(described_class)
289
+
290
+ compiled = environment.compile do
291
+ input :amount
292
+ output :amount
293
+ end
294
+
295
+ result = environment.execute_with(:#{name}_inline, compiled, inputs: { amount: 10 })
296
+
297
+ expect(environment.apply_effect(:#{name}, payload: { amount: 10 })).to eq(amount: 10)
298
+ expect(result.output(:amount)).to eq(10)
299
+ end
300
+ end
301
+ RUBY
302
+ end
303
+
304
+ def bundle_spec_template
305
+ <<~RUBY
306
+ # frozen_string_literal: true
307
+
308
+ RSpec.describe #{pack_constant} do
309
+ it "installs a bundle pack without depending on contracts internals" do
310
+ profile = Igniter::Contracts.build_profile(described_class)
311
+
312
+ expect(profile.pack_names).to include(:#{name})
313
+ end
314
+ end
315
+ RUBY
316
+ end
317
+
318
+ def feature_example_template
319
+ <<~RUBY
320
+ # frozen_string_literal: true
321
+
322
+ require "igniter/contracts"
323
+
324
+ environment = Igniter::Contracts.with(#{pack_constant})
325
+
326
+ result = environment.run(inputs: { source: "hello" }) do
327
+ input :source
328
+ #{name} :result, from: :source
329
+ output :result
330
+ end
331
+
332
+ audit = Igniter::Extensions::Contracts.audit_pack(#{pack_constant}, environment)
333
+
334
+ puts "creator_example_output=\#{result.output(:result)}"
335
+ puts "creator_example_audit_ok=\#{audit.ok?}"
336
+ RUBY
337
+ end
338
+
339
+ def operational_example_template
340
+ <<~RUBY
341
+ # frozen_string_literal: true
342
+
343
+ require "igniter/contracts"
344
+
345
+ environment = Igniter::Contracts.with(#{pack_constant})
346
+
347
+ compiled = environment.compile do
348
+ input :amount
349
+ output :amount
350
+ end
351
+
352
+ effect_result = environment.apply_effect(:#{name}, payload: { amount: 10 })
353
+ execution_result = environment.execute_with(:#{name}_inline, compiled, inputs: { amount: 15 })
354
+
355
+ puts "creator_effect_payload=\#{effect_result.inspect}"
356
+ puts "creator_execution_output=\#{execution_result.output(:amount)}"
357
+ RUBY
358
+ end
359
+
360
+ def bundle_example_template
361
+ <<~RUBY
362
+ # frozen_string_literal: true
363
+
364
+ require "igniter/contracts"
365
+
366
+ profile = Igniter::Contracts.build_profile(#{pack_constant})
367
+
368
+ puts "creator_bundle_profile=\#{profile.pack_names.join(',')}"
369
+ RUBY
370
+ end
371
+
372
+ def runtime_dependency_hints_markdown
373
+ return "" if profile.runtime_dependency_hints.empty?
374
+
375
+ " - runtime dependency packs: `#{profile.runtime_dependency_hints.join("`, `")}`"
376
+ end
377
+
378
+ def development_dependency_hints_markdown
379
+ return "" if profile.development_dependency_hints.empty?
380
+
381
+ " - development tool packs: `#{profile.development_dependency_hints.join("`, `")}`"
382
+ end
383
+
384
+ def development_hints_markdown
385
+ return "" if profile.development_hints.empty?
386
+
387
+ " - development hints: `#{profile.development_hints.join("`; `")}`"
388
+ end
389
+
390
+ def packaging_hints_markdown
391
+ return "" if scope.packaging_hints.empty?
392
+
393
+ " - packaging hints: `#{scope.packaging_hints.join("`; `")}`"
394
+ end
395
+
396
+ def bundle_manifest_body
397
+ lines = ["metadata: { category: :bundle }"]
398
+ append_runtime_dependency_manifest_line(lines)
399
+ lines << "registry_contracts: [Igniter::Contracts::PackManifest.diagnostic(:#{name}_summary)]" if profile.capability?(:diagnostic)
400
+ lines.join(",\n ")
401
+ end
402
+
403
+ def feature_manifest_body
404
+ lines = [
405
+ "name: :#{name}",
406
+ "node_contracts: [Igniter::Contracts::PackManifest.node(:#{name})]",
407
+ "registry_contracts: [Igniter::Contracts::PackManifest.validator(:#{name}_sources)]"
408
+ ]
409
+ append_runtime_dependency_manifest_line(lines)
410
+ lines.join(",\n ")
411
+ end
412
+
413
+ def operational_manifest_body
414
+ lines = [
415
+ "name: :#{name}",
416
+ <<~RUBY.chomp
417
+ registry_contracts: [
418
+ Igniter::Contracts::PackManifest.effect(:#{name}),
419
+ Igniter::Contracts::PackManifest.executor(:#{name}_inline)
420
+ ]
421
+ RUBY
422
+ ]
423
+ append_runtime_dependency_manifest_line(lines)
424
+ lines.join(",\n ")
425
+ end
426
+
427
+ def append_runtime_dependency_manifest_line(lines)
428
+ return lines if profile.runtime_dependency_hints.empty?
429
+
430
+ lines << "requires_packs: [#{profile.runtime_dependency_hints.join(", ")}]"
431
+ end
432
+
433
+ def bundle_diagnostic_install_lines
434
+ return "# no diagnostics contributor for this profile" unless profile.capability?(:diagnostic)
435
+
436
+ "kernel.diagnostics_contributors.register(:#{name}_summary, #{name}_summary)"
437
+ end
438
+
439
+ def bundle_diagnostic_contributor_template
440
+ return "" unless profile.capability?(:diagnostic)
441
+
442
+ <<~RUBY.chomp
443
+ def #{name}_summary
444
+ Module.new do
445
+ module_function
446
+
447
+ def augment(report:, result:, profile:) # rubocop:disable Lint/UnusedMethodArgument
448
+ report.add_section(:#{name}_summary, {
449
+ output_names: result.outputs.keys.sort,
450
+ pack_names: profile.pack_names.sort
451
+ })
452
+ end
453
+ end
454
+ end
455
+ RUBY
456
+ end
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Extensions
5
+ module Contracts
6
+ module Creator
7
+ class Scope
8
+ PRESETS = {
9
+ app_local: {
10
+ root: "app/lib",
11
+ spec_root: "spec/lib",
12
+ example_root: "examples",
13
+ readme_path: "docs/igniter-packs/README.md",
14
+ packaging_hints: [
15
+ "keep the pack close to the host app while the API is still moving",
16
+ "prefer app-local namespacing before extracting a shared gem"
17
+ ]
18
+ },
19
+ monorepo_package: {
20
+ root: "lib",
21
+ spec_root: "spec",
22
+ example_root: "examples",
23
+ readme_path: "README.md",
24
+ packaging_hints: [
25
+ "add package-owned specs and a runnable example before promoting the pack",
26
+ "keep the public entrypoint independent from igniter-core and contracts internals"
27
+ ]
28
+ },
29
+ standalone_gem: {
30
+ root: "lib",
31
+ spec_root: "spec",
32
+ example_root: "examples",
33
+ readme_path: "README.md",
34
+ packaging_hints: [
35
+ "publish only after the pack has a stable public entrypoint and package-owned example",
36
+ "treat the pack as a distributable gem with a small explicit surface"
37
+ ]
38
+ }
39
+ }.freeze
40
+
41
+ attr_reader :name, :root, :spec_root, :example_root, :readme_path, :packaging_hints
42
+
43
+ def self.available
44
+ PRESETS.keys
45
+ end
46
+
47
+ def self.build(scope)
48
+ preset = PRESETS.fetch(scope.to_sym) do
49
+ raise ArgumentError, "unknown creator scope #{scope.inspect}"
50
+ end
51
+
52
+ new(name: scope, **preset)
53
+ end
54
+
55
+ def initialize(name:, root:, spec_root:, example_root:, readme_path:, packaging_hints:)
56
+ @name = name.to_sym
57
+ @root = root
58
+ @spec_root = spec_root
59
+ @example_root = example_root
60
+ @readme_path = readme_path
61
+ @packaging_hints = packaging_hints.dup.freeze
62
+ freeze
63
+ end
64
+
65
+ def to_h
66
+ {
67
+ name: name,
68
+ root: root,
69
+ spec_root: spec_root,
70
+ example_root: example_root,
71
+ readme_path: readme_path,
72
+ packaging_hints: packaging_hints
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end