suma 0.5.7 → 0.5.9

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/nightly-benchmark.yml +25 -0
  3. data/.github/workflows/release-provenance.yml +41 -0
  4. data/.rubocop_todo.yml +84 -0
  5. data/CLAUDE.md +44 -0
  6. data/README.adoc +136 -11
  7. data/Rakefile +13 -1
  8. data/bin/benchmark_corpus +138 -0
  9. data/bin/release_provenance +40 -0
  10. data/lib/suma/build_plan.rb +71 -0
  11. data/lib/suma/change_classification.rb +37 -0
  12. data/lib/suma/change_impact.rb +61 -0
  13. data/lib/suma/change_manifest.rb +147 -0
  14. data/lib/suma/cli/build.rb +23 -0
  15. data/lib/suma/cli/compare.rb +49 -2
  16. data/lib/suma/cli/core.rb +83 -0
  17. data/lib/suma/cli/export.rb +140 -10
  18. data/lib/suma/cli/extract_terms.rb +11 -0
  19. data/lib/suma/cli/generate_register.rb +12 -0
  20. data/lib/suma/cli/resolve.rb +45 -0
  21. data/lib/suma/cli/validate.rb +90 -0
  22. data/lib/suma/cli.rb +1 -0
  23. data/lib/suma/closure_validator.rb +317 -0
  24. data/lib/suma/corpus_graph.rb +141 -0
  25. data/lib/suma/express_schema.rb +22 -28
  26. data/lib/suma/null_cache.rb +4 -0
  27. data/lib/suma/owl_projection.rb +611 -0
  28. data/lib/suma/processor.rb +55 -6
  29. data/lib/suma/provenance.rb +159 -0
  30. data/lib/suma/publication.rb +108 -0
  31. data/lib/suma/register_manifest_generator.rb +76 -8
  32. data/lib/suma/schema_cache.rb +72 -13
  33. data/lib/suma/schema_category.rb +31 -2
  34. data/lib/suma/schema_collection.rb +58 -5
  35. data/lib/suma/schema_exporter.rb +5 -5
  36. data/lib/suma/schema_naming.rb +29 -33
  37. data/lib/suma/schema_profile.rb +134 -0
  38. data/lib/suma/term_classification.rb +9 -2
  39. data/lib/suma/term_extractor.rb +76 -35
  40. data/lib/suma/urn_resolver.rb +368 -0
  41. data/lib/suma/version.rb +1 -1
  42. data/lib/suma/worker_pool.rb +62 -0
  43. data/lib/suma.rb +12 -0
  44. metadata +19 -2
data/lib/suma/cli/core.rb CHANGED
@@ -21,6 +21,27 @@ module Suma
21
21
 
22
22
  desc "build METANORMA_SITE_MANIFEST",
23
23
  "Build collection specified in site manifest (`metanorma*.yml`)"
24
+ # Mirrors so Core accepts the switches Cli::Build reparses; the inner
25
+ # class owns their declarations.
26
+ option :compile, type: :boolean, default: true,
27
+ desc: "Compile or skip compile of collection"
28
+ option :schemas_all_path, type: :string, aliases: "-s",
29
+ desc: "Generate file that contains all schemas " \
30
+ "in the collection."
31
+ option :staged, type: :boolean, default: false,
32
+ desc: "Memory-bounded staged build"
33
+ option :incremental, type: :boolean, default: false,
34
+ desc: "Re-render only changed schemas and dependents"
35
+ option :state_file, type: :string,
36
+ desc: "Path to the incremental build state file"
37
+ option :jobs, type: :numeric, default: 1,
38
+ desc: "Concurrent schema export workers"
39
+ option :provenance, type: :string,
40
+ desc: "Write a build provenance manifest"
41
+ option :sign, type: :string, banner: "METHOD",
42
+ desc: "Sign the provenance manifest: minisign or sigstore"
43
+ option :sign_key, type: :string,
44
+ desc: "Signing key (sigstore signs keylessly when omitted)"
24
45
  def build(_site_manifest)
25
46
  Cli::Build.start
26
47
  end
@@ -45,12 +66,18 @@ module Suma
45
66
  "(used for section references)"
46
67
  option :language_code, type: :string, default: "eng", aliases: "-l",
47
68
  desc: "Language code for the Glossarist"
69
+ option :edition, type: :numeric,
70
+ desc: "Owning edition recorded on each concept"
71
+ option :language_codes, type: :array,
72
+ desc: "Additional linked localization languages"
48
73
  def extract_terms(schema_manifest_file, glossarist_output_path)
49
74
  TermExtractor.new(
50
75
  schema_manifest_file,
51
76
  glossarist_output_path,
52
77
  urn: options[:urn],
53
78
  language_code: options[:language_code],
79
+ edition: options[:edition],
80
+ language_codes: options[:language_codes],
54
81
  ).call
55
82
  end
56
83
 
@@ -66,6 +93,14 @@ module Suma
66
93
  desc: "Language code for section names"
67
94
  option :owner, type: :string, default: Suma::RegisterManifestGenerator::DEFAULT_OWNER,
68
95
  desc: "Owner of the dataset (e.g. 'ISO/TC 184/SC 4')"
96
+ option :profile, type: :string, default: "iso10303",
97
+ desc: "Corpus profile (iso10303 or generic)"
98
+ option :year, type: :numeric,
99
+ desc: "Edition year for the register"
100
+ option :supersedes, type: :string,
101
+ desc: "URN/ref of the superseded edition"
102
+ option :edition, type: :numeric,
103
+ desc: "Edition number (tagged as edition:N)"
69
104
  def generate_register(schema_manifest_file, output_path)
70
105
  RegisterManifestGenerator.new(
71
106
  schema_manifest_file,
@@ -75,6 +110,10 @@ module Suma
75
110
  ref: options[:ref],
76
111
  language_code: options[:language_code],
77
112
  owner: options[:owner],
113
+ profile: options[:profile],
114
+ year: options[:year],
115
+ supersedes: options[:supersedes],
116
+ edition: options[:edition],
78
117
  ).generate
79
118
  end
80
119
 
@@ -87,6 +126,31 @@ module Suma
87
126
  desc "export *FILES",
88
127
  "Export EXPRESS schemas from manifest files or " \
89
128
  "standalone EXPRESS files"
129
+ # The inner class reparses ARGV and owns the canonical declarations;
130
+ # these mirrors exist only so Core accepts the switches it passes
131
+ # through (Core checks unknown options before dispatch).
132
+ option :output, type: :string, aliases: "-o", required: true,
133
+ desc: "Output directory path"
134
+ option :annotations, type: :boolean, default: false,
135
+ desc: "Include annotations (remarks/comments)"
136
+ option :zip, type: :boolean, default: false,
137
+ desc: "Create ZIP archive of exported schemas"
138
+ option :publication, type: :string, banner: "SCHEMA_ID",
139
+ desc: "Export an AP publication bundle: the " \
140
+ "lead schema plus its transitive " \
141
+ "USE-FROM/REFERENCE closure (manifest " \
142
+ "input only)"
143
+ option :profile, type: :string, default: "iso10303",
144
+ desc: "Corpus profile (iso10303 or generic)"
145
+ option :format, type: :string, default: "express", banner: "FORMAT",
146
+ desc: "Output format: express (default) or owl"
147
+ option :corpus, type: :boolean, default: false,
148
+ desc: "OWL only: emit one ontology for the whole " \
149
+ "manifest instead of one per schema"
150
+ option :urn, type: :string, banner: "URN",
151
+ desc: "OWL only: base URN for IRIs, before the " \
152
+ ":tech component " \
153
+ "(e.g. urn:iso:std:iso:10303:-2:ed-2:en)"
90
154
  def export(*_files)
91
155
  Cli::Export.start
92
156
  end
@@ -100,6 +164,25 @@ module Suma
100
164
  desc "validate SUBCOMMAND ...ARGS", "Validate express documents"
101
165
  subcommand "validate", Cli::Validate
102
166
 
167
+ desc "resolve [URN]", "Resolve URNs to canonical JSON + HTML artifacts"
168
+ # Mirrors so Core accepts the switches Cli::Resolve reparses; the
169
+ # inner class owns their declarations.
170
+ option :manifest, type: :string, required: true,
171
+ desc: "Schema manifest used for schema/entity URNs"
172
+ option :urn, type: :string,
173
+ desc: "Corpus URN base (defaults to the register URN)"
174
+ option :concepts, type: :string,
175
+ desc: "Directory of Glossarist concept YAML files"
176
+ option :register, type: :string,
177
+ desc: "Glossarist register.yaml (dataset URN)"
178
+ option :output, type: :string, aliases: "-o", required: true,
179
+ desc: "Artifact output directory"
180
+ option :all, type: :boolean, default: false,
181
+ desc: "Emit artifacts for every known URN"
182
+ def resolve(_urn = nil)
183
+ Cli::Resolve.start
184
+ end
185
+
103
186
  desc "check_svg_quality [PATH]",
104
187
  "Check SVG quality and sort by severity (critical files first)"
105
188
  option :pattern, type: :string, default: Cli::CheckSvgQuality::DEFAULT_PATTERN,
@@ -14,6 +14,11 @@ module Suma
14
14
  # +Suma::SchemaCategory+, the single source of truth. The exporter
15
15
  # itself never classifies — it consumes loaded ExpressSchema
16
16
  # objects whose output paths were set by this adapter.
17
+ #
18
+ # With +--publication SCHEMA_ID+, exports an application-protocol
19
+ # publication bundle instead: the lead schema plus the transitive
20
+ # USE-FROM/REFERENCE closure (+Suma::Publication+), written flat
21
+ # under +<output>/<lead_id>/+.
17
22
  class Export < Thor
18
23
  desc "export *FILES",
19
24
  "Export EXPRESS schemas from manifest files or " \
@@ -24,6 +29,22 @@ module Suma
24
29
  desc: "Include annotations (remarks/comments)"
25
30
  option :zip, type: :boolean, default: false,
26
31
  desc: "Create ZIP archive of exported schemas"
32
+ option :publication, type: :string, banner: "SCHEMA_ID",
33
+ desc: "Export an AP publication bundle: the " \
34
+ "lead schema plus its transitive " \
35
+ "USE-FROM/REFERENCE closure (manifest " \
36
+ "input only)"
37
+ option :profile, type: :string, default: "iso10303",
38
+ desc: "Corpus profile (iso10303 or generic)"
39
+ option :format, type: :string, default: "express", banner: "FORMAT",
40
+ desc: "Output format: express (default) or owl"
41
+ option :corpus, type: :boolean, default: false,
42
+ desc: "OWL only: emit one ontology for the whole " \
43
+ "manifest instead of one per schema"
44
+ option :urn, type: :string, banner: "URN",
45
+ desc: "OWL only: base URN for IRIs, before the " \
46
+ ":tech component " \
47
+ "(e.g. urn:iso:std:iso:10303:-2:ed-2:en)"
27
48
 
28
49
  def export(*files)
29
50
  require "expressir"
@@ -47,8 +68,87 @@ module Suma
47
68
  end
48
69
 
49
70
  def run(files, options)
50
- schemas = files.flat_map { |file| build_schemas(file) }
71
+ format = options[:format].to_s.downcase
72
+ case format
73
+ when "express"
74
+ run_express(files, options)
75
+ when "owl"
76
+ run_owl(files, options)
77
+ else
78
+ raise ArgumentError, "Unsupported format: #{options[:format]}. " \
79
+ "Use express or owl."
80
+ end
81
+ end
82
+
83
+ def run_express(files, options)
84
+ schemas = if options[:publication]
85
+ publication_bundle(files, options)
86
+ else
87
+ files.flat_map { |file| build_schemas(file, options) }
88
+ end
89
+ run_exporter(schemas, options)
90
+ end
91
+
92
+ # OWL projection: one Turtle file per schema (under its category
93
+ # directory, mirroring the EXPRESS export layout). With --corpus a
94
+ # top-level ontology importing every schema ontology is added
95
+ # alongside them.
96
+ def run_owl(files, options)
97
+ validate_owl_input!(files, options)
98
+ projection = OwlProjection.new(files.first, urn: options[:urn])
99
+ if options[:corpus]
100
+ write_schema_ontologies(projection, files.first)
101
+ write_ontology(projection, output_root.join("corpus.ttl"),
102
+ projection.to_turtle)
103
+ else
104
+ write_schema_ontologies(projection, files.first)
105
+ end
106
+ end
107
+
108
+ def validate_owl_input!(files, options)
109
+ unless bundle_input?(files)
110
+ raise ArgumentError,
111
+ "--format owl requires exactly one schema manifest file " \
112
+ "(.yml/.yaml) as input"
113
+ end
114
+ if options[:publication]
115
+ raise ArgumentError,
116
+ "--publication bundles EXPRESS files and cannot be " \
117
+ "combined with --format owl"
118
+ end
119
+ return if options[:urn]
120
+
121
+ raise ArgumentError, "--format owl requires --urn BASE_URN"
122
+ end
123
+
124
+ def write_schema_ontologies(projection, manifest_file)
125
+ manifest = Expressir::SchemaManifest.from_file(manifest_file)
126
+ manifest.schemas.each do |entry|
127
+ target = output_root
128
+ .join(SchemaCategory.for_schema(
129
+ id: entry.id, path: entry.path,
130
+ profile: options[:profile]
131
+ ).directory)
132
+ .join("#{entry.id}.ttl")
133
+ write_ontology(projection, target,
134
+ projection.to_turtle(schema_id: entry.id),
135
+ schema_id: entry.id)
136
+ end
137
+ end
138
+
139
+ def write_ontology(projection, target, turtle, schema_id: nil)
140
+ problems = projection.validate(schema_id: schema_id)
141
+ unless problems.empty?
142
+ raise Error, "OWL projection of #{target} is invalid: " \
143
+ "#{problems.join('; ')}"
144
+ end
51
145
 
146
+ FileUtils.mkdir_p(target.dirname)
147
+ File.write(target, turtle)
148
+ Utils.log "Wrote OWL ontology: #{target}"
149
+ end
150
+
151
+ def run_exporter(schemas, options)
52
152
  exporter = SchemaExporter.new(
53
153
  schemas: schemas,
54
154
  output_path: options[:output],
@@ -57,16 +157,41 @@ module Suma
57
157
  create_zip: options[:zip],
58
158
  },
59
159
  )
60
-
61
160
  exporter.export
62
161
  end
63
162
 
64
- def build_schemas(file)
163
+ # Build the publication bundle schemas, rooted at
164
+ # <output>/<lead_id>/ so the AP volume lands in its own directory.
165
+ def publication_bundle(files, options)
166
+ validate_bundle_input!(files)
167
+ lead_id = options[:publication]
168
+ publication = Publication.new(
169
+ lead_id: lead_id,
170
+ schema_manifest_file: files.first,
171
+ profile: options[:profile],
172
+ )
173
+ publication.schemas(output_path: output_root.join(lead_id).to_s)
174
+ end
175
+
176
+ def validate_bundle_input!(files)
177
+ return if bundle_input?(files)
178
+
179
+ raise ArgumentError,
180
+ "--publication requires exactly one schema manifest file " \
181
+ "(.yml/.yaml) as input"
182
+ end
183
+
184
+ def bundle_input?(files)
185
+ files.length == 1 &&
186
+ %w[.yml .yaml].include?(File.extname(files.first).downcase)
187
+ end
188
+
189
+ def build_schemas(file, options)
65
190
  case File.extname(file).downcase
66
191
  when ".yml", ".yaml"
67
- build_from_manifest(file)
192
+ build_from_manifest(file, options)
68
193
  when ".exp"
69
- [build_standalone(file)]
194
+ [build_standalone(file, options)]
70
195
  else
71
196
  raise ArgumentError, "Unsupported file type: #{file}. " \
72
197
  "Only .yml, .yaml, and .exp files are " \
@@ -74,27 +199,32 @@ module Suma
74
199
  end
75
200
  end
76
201
 
77
- def build_from_manifest(file)
202
+ def build_from_manifest(file, options)
78
203
  manifest = Expressir::SchemaManifest.from_file(file)
79
- manifest.schemas.map { |entry| build_from_manifest_entry(entry) }
204
+ manifest.schemas.map do |entry|
205
+ build_from_manifest_entry(entry, options)
206
+ end
80
207
  end
81
208
 
82
- def build_from_manifest_entry(entry)
83
- category = SchemaCategory.for_schema(id: entry.id, path: entry.path)
209
+ def build_from_manifest_entry(entry, options)
210
+ category = SchemaCategory.for_schema(id: entry.id, path: entry.path,
211
+ profile: options[:profile])
84
212
  ExpressSchema.new(
85
213
  id: entry.id,
86
214
  path: entry.path.to_s,
87
215
  output_path: output_root.join(category.directory).to_s,
88
216
  is_standalone_file: false,
217
+ profile: options[:profile],
89
218
  )
90
219
  end
91
220
 
92
- def build_standalone(exp_file)
221
+ def build_standalone(exp_file, options)
93
222
  ExpressSchema.new(
94
223
  id: nil,
95
224
  path: File.expand_path(exp_file),
96
225
  output_path: output_root.to_s,
97
226
  is_standalone_file: true,
227
+ profile: options[:profile],
98
228
  )
99
229
  end
100
230
 
@@ -13,6 +13,14 @@ module Suma
13
13
  option :urn, type: :string, required: true, aliases: "-u",
14
14
  desc: "URN for the dataset source " \
15
15
  "(used for section references)"
16
+ option :profile, type: :string, default: "iso10303",
17
+ desc: "Corpus profile (iso10303 or generic)"
18
+ option :edition, type: :numeric,
19
+ desc: "Owning edition recorded on each concept"
20
+ option :language_codes, type: :array,
21
+ desc: "Additional linked localization " \
22
+ "languages (the primary language is " \
23
+ "always included)"
16
24
 
17
25
  def extract_terms(schema_manifest_file, output_path)
18
26
  TermExtractor.new(
@@ -20,6 +28,9 @@ module Suma
20
28
  output_path,
21
29
  language_code: options[:language_code],
22
30
  urn: options[:urn],
31
+ profile: options[:profile],
32
+ edition: options[:edition],
33
+ language_codes: options[:language_codes],
23
34
  ).call
24
35
  end
25
36
  end
@@ -17,6 +17,14 @@ module Suma
17
17
  desc: "Human-readable reference label"
18
18
  option :owner, type: :string, default: Suma::RegisterManifestGenerator::DEFAULT_OWNER,
19
19
  desc: "Owner of the dataset (e.g. 'ISO/TC 184/SC 4')"
20
+ option :profile, type: :string, default: "iso10303",
21
+ desc: "Corpus profile (iso10303 or generic)"
22
+ option :year, type: :numeric,
23
+ desc: "Edition year for the register"
24
+ option :supersedes, type: :string,
25
+ desc: "URN/ref of the superseded edition"
26
+ option :edition, type: :numeric,
27
+ desc: "Edition number (tagged as edition:N)"
20
28
 
21
29
  def generate_register(schema_manifest_file, output_path)
22
30
  RegisterManifestGenerator.new(
@@ -27,6 +35,10 @@ module Suma
27
35
  ref: options[:ref],
28
36
  language_code: options[:language_code],
29
37
  owner: options[:owner],
38
+ profile: options[:profile],
39
+ year: options[:year],
40
+ supersedes: options[:supersedes],
41
+ edition: options[:edition],
30
42
  ).generate
31
43
  end
32
44
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Suma
6
+ module Cli
7
+ # Resolve command: emit canonical JSON + HTML artifacts for URNs.
8
+ class Resolve < Thor
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc "resolve URN", "Resolve a URN to canonical JSON + HTML artifacts"
14
+ option :manifest, type: :string, required: true,
15
+ desc: "Schema manifest used for schema/entity URNs"
16
+ option :urn, type: :string,
17
+ desc: "Corpus URN base (defaults to the register URN)"
18
+ option :concepts, type: :string,
19
+ desc: "Directory of Glossarist concept YAML files"
20
+ option :register, type: :string,
21
+ desc: "Glossarist register.yaml (dataset URN)"
22
+ option :output, type: :string, aliases: "-o", required: true,
23
+ desc: "Artifact output directory"
24
+ option :all, type: :boolean, default: false,
25
+ desc: "Emit artifacts for every known URN"
26
+ def resolve(urn = nil)
27
+ resolver = UrnResolver.new(
28
+ manifest_file: options[:manifest],
29
+ urn: options[:urn],
30
+ concepts_dir: options[:concepts],
31
+ register_file: options[:register],
32
+ )
33
+
34
+ if options[:all]
35
+ resolver.write(options[:output])
36
+ elsif urn
37
+ resolver.write_urn(urn, options[:output])
38
+ Utils.log "Resolved #{urn}"
39
+ else
40
+ raise ArgumentError, "provide a URN or use --all"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -26,6 +26,22 @@ module Suma
26
26
  puts LinkValidation.generate_summary(result)
27
27
  end
28
28
 
29
+ desc "closure SCHEMAS_MANIFEST [DOCUMENTS_PATH]",
30
+ "Validate referential closure: every EXPRESS-internal reference " \
31
+ "resolves; optionally merge documentation-layer link failures"
32
+ option :format, type: :string, default: "terminal",
33
+ desc: "Report format: terminal, json, yaml"
34
+ def closure(schemas_manifest_file, documents_path = nil)
35
+ graph = CorpusGraph.from_manifest(schemas_manifest_file)
36
+ violations = ClosureValidator.new(graph)
37
+ .validate_manifest(schemas_manifest_file)
38
+ if documents_path
39
+ violations += documentation_violations(schemas_manifest_file,
40
+ documents_path)
41
+ end
42
+ emit_closure_report(violations)
43
+ end
44
+
29
45
  desc "mappings DOCUMENTS_PATH",
30
46
  "Validate module mapping.yaml links and reference paths " \
31
47
  "against the parsed module corpus; only NEW drift " \
@@ -48,6 +64,80 @@ module Suma
48
64
 
49
65
  private
50
66
 
67
+ # Print the closure report and raise on any unresolved reference.
68
+ def emit_closure_report(violations)
69
+ if violations.empty?
70
+ puts closure_success_message
71
+ return
72
+ end
73
+
74
+ puts format_closure_report(violations)
75
+ raise Thor::Error,
76
+ "Referential closure failed: #{violations.size} unresolved " \
77
+ "reference(s)"
78
+ end
79
+
80
+ def closure_success_message
81
+ case options[:format]
82
+ when "json"
83
+ require "json"
84
+ { "violations" => [] }.to_json
85
+ when "yaml"
86
+ require "yaml"
87
+ { "violations" => [] }.to_yaml
88
+ else
89
+ "Referential closure OK: all references resolve."
90
+ end
91
+ end
92
+
93
+ def format_closure_report(violations)
94
+ case options[:format]
95
+ when "json"
96
+ require "json"
97
+ { "violations" => violations.map(&:to_h) }.to_json
98
+ when "yaml"
99
+ require "yaml"
100
+ { "violations" => violations.map(&:to_h) }.to_yaml
101
+ else
102
+ violations.map { |v| format_closure_line(v) }
103
+ .sort
104
+ .join("\n")
105
+ end
106
+ end
107
+
108
+ def format_closure_line(violation)
109
+ location = [violation.file, violation.line].compact.join(":")
110
+ "[#{violation.kind}] #{violation.schema_id}/#{violation.owner_id} " \
111
+ "#{violation.reference} (#{location}) — #{violation.reason}"
112
+ end
113
+
114
+ # Merge documentation-layer link failures into the closure report:
115
+ # the same unresolved-reference predicate, applied by the doc-layer
116
+ # extractor.
117
+ def documentation_violations(schemas_file, documents_path)
118
+ result = LinkValidation.new(
119
+ schemas_file: schemas_file,
120
+ documents_path: documents_path,
121
+ output_file: nil,
122
+ ).call
123
+ result.unresolved.map { |issue| documentation_violation(issue) }
124
+ rescue StandardError => e
125
+ Utils.log "documentation-layer validation skipped: #{e.message}"
126
+ []
127
+ end
128
+
129
+ def documentation_violation(issue)
130
+ ClosureValidator::UnresolvedReference.new(
131
+ file: issue.file.to_s,
132
+ line: issue.line,
133
+ schema_id: issue.link.to_s.split(".").first,
134
+ owner_id: "(documentation)",
135
+ kind: "documentation",
136
+ reference: issue.link,
137
+ reason: issue.reason,
138
+ )
139
+ end
140
+
51
141
  def default_baseline_path(documents_path)
52
142
  File.join(documents_path, "mapping-drift-baseline.yml")
53
143
  end
data/lib/suma/cli.rb CHANGED
@@ -12,6 +12,7 @@ module Suma
12
12
  autoload :GenerateRegister, "suma/cli/generate_register"
13
13
  autoload :GenerateSchemas, "suma/cli/generate_schemas"
14
14
  autoload :Reformat, "suma/cli/reformat"
15
+ autoload :Resolve, "suma/cli/resolve"
15
16
  autoload :Validate, "suma/cli/validate"
16
17
  autoload :ValidateLinks, "suma/cli/validate_links"
17
18
  end