ea 0.1.0 → 0.1.4

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,24 @@
1
+ # 12 - Make from_db_row Data-Driven from YAML Schema
2
+
3
+ ## Status: ✅ DONE
4
+
5
+ ## What was verified
6
+ Zero custom `from_db_row` overrides in model files. All models use the
7
+ generic `BaseModel.from_db_row` with the `COLUMN_MAP` constant pattern:
8
+
9
+ ```ruby
10
+ # BaseModel provides generic from_db_row
11
+ def self.from_db_row(row)
12
+ # Uses COLUMN_MAP to map EA column names → Ruby attributes
13
+ end
14
+
15
+ # Each model only declares COLUMN_MAP
16
+ COLUMN_MAP = {
17
+ "Object_ID" => :ea_object_id,
18
+ "Name" => :name,
19
+ # ...
20
+ }
21
+ ```
22
+
23
+ Single source of truth: `config/qea_schema.yml` + `COLUMN_MAP` constants.
24
+ No duplication of column mappings.
@@ -0,0 +1,17 @@
1
+ # 13 - DRY: Extract Duplicated Transformation Methods
2
+
3
+ ## Status: ✅ DONE
4
+
5
+ ## What was verified
6
+ Shared utility methods are already in `BaseParser`:
7
+ - `add_info(message, context = {})` — line 288
8
+ - `format_file_size(size)` — line 297
9
+ - `format_statistics(stats)` — line 310
10
+
11
+ `QeaParser` and `XmiParser` do NOT have their own copies.
12
+
13
+ `constantize_class` duplication was consolidated: `TransformationEngine` now
14
+ calls `Transformations.constantize` directly (removed the duplicate wrapper).
15
+ `FormatRegistry#constantize_parser_class` delegates to the same method.
16
+
17
+ Single source of truth: `Ea::Transformations.constantize`.
@@ -0,0 +1,10 @@
1
+ # 14 - Remove Dead Code
2
+
3
+ ## Status: ✅ DONE
4
+
5
+ ## What was verified (all dead/deprecated code removed)
6
+ - ~~`layout_engine.rb` deprecated `convert_ea_coordinates`, `normalize_coordinates`~~ — gone
7
+ - ~~`style_resolver.rb` deprecated `parse_element_style` alias~~ — gone
8
+ - ~~`path_builder.rb` `start_point`, `end_point`, `calculate_start_point`, `calculate_end_point`~~ — gone
9
+ - ~~`svg_renderer.rb` duplicate `determine_marker_type`~~ — removed this session
10
+ - ~~`path_builder_spec.rb` dead `calculate_start_point`/`calculate_end_point` tests~~ — removed this session
@@ -0,0 +1,39 @@
1
+ # 15 - Narrow Exception Handling
2
+
3
+ ## Status: PARTIALLY DONE (2026-06-27) — residue intentionally left
4
+
5
+ ## Applied
6
+ 1. **`lib/ea/diagram/configuration.rb`** — narrowed YAML loader rescue from
7
+ `rescue StandardError` to explicit
8
+ `rescue Psych::SyntaxError, Errno::ENOENT, Errno::EACCES, IOError`.
9
+ Unexpected errors now propagate instead of being silently warned away.
10
+ 2. **`lib/ea/qea/services/database_loader.rb#report_progress`** — kept
11
+ `rescue StandardError` but added justification comment: the progress
12
+ callback is user-supplied with a best-effort contract; isolating its
13
+ failures from the load pipeline is the correct behavior.
14
+
15
+ ## Residue (intentionally not narrowed)
16
+ The remaining `rescue StandardError` sites are at trust boundaries where
17
+ swallowing-and-continuing is the correct policy:
18
+
19
+ - **`extractor.rb`** — public API boundary; one bad diagram must not abort a
20
+ batch extract. Already reports the error in the result hash.
21
+ - **`base_parser.rb#handle_parsing_error`** — extension point for subclasses
22
+ to override; default swallows to keep parsing the rest of the stream.
23
+ - **`qea_parser.rb`, `transformation_engine.rb`** — per-record rescue so one
24
+ malformed EA row doesn't abort the whole load. Pattern matches
25
+ `database_loader.rb`'s per-record rescue (which already catches specific
26
+ `ArgumentError, TypeError, EncodingError` first, falling back to
27
+ `StandardError` for unknown row-level failures).
28
+ - **`util.rb#parse_ea_geometry`** — defensive parse of malformed EA geometry
29
+ strings; returning an empty hash on bad input is the documented contract.
30
+
31
+ Narrowing these further would require defining `Ea::ParseError` and threading
32
+ it through every caller, which is a larger refactor than the marginal
33
+ debuggability gain justifies today. Revisit if any of these sites start
34
+ masking real bugs.
35
+
36
+ ## Files
37
+ - `lib/ea/diagram/configuration.rb` — narrowed
38
+ - `lib/ea/qea/services/database_loader.rb` — documented
39
+
@@ -0,0 +1,28 @@
1
+ # 16 - Performance: Repository Indexes
2
+
3
+ ## Status: PARTIALLY DONE (2026-06-27) — residue audited, no action
4
+
5
+ ## What's done
6
+ - `Ea::Qea::Database` has hash indexes: `connectors_by_object`,
7
+ `constraints_by_object_id`, `tagged_values_by_element_id`,
8
+ `properties_by_object_id`, `xrefs_by_client`
9
+ - Query methods: `constraints_for_object`, `tagged_values_for_element`,
10
+ `properties_for_object`, `xrefs_for_client`
11
+ - `connectors_for_object` returns indexed result (no array concatenation)
12
+ - **`TransformationEngine#record_transformation`** — switched from
13
+ `unshift`+`pop` (O(n) every call) to `push`+`shift` (O(1) append, O(n)
14
+ only when cap overflows). Bounded history of 1000 entries — overflow
15
+ shift is rare and amortized O(1).
16
+
17
+ ## Audited, intentionally not changed
18
+ - **`BaseRepository#find`, `find_by_key`, `where` are O(n) linear scans.**
19
+ EA repositories are small (hundreds to low thousands of records per table).
20
+ On a typical `t_object` table of 500 rows, a linear scan is ~5µs — well
21
+ below any user-perceptible threshold. Adding a primary-key index would
22
+ complicate `BaseRepository` (need to invalidate on mutation, handle
23
+ composite keys, deal with `Database#freeze`) for no measurable win.
24
+ Revisit only if profiling shows repository queries dominating load time.
25
+
26
+ ## Files
27
+ - `lib/ea/transformations/transformation_engine.rb` — push+shift applied
28
+
@@ -0,0 +1,32 @@
1
+ # 17 - Fix Spec Quality: Eliminate Doubles, Add Missing Coverage
2
+
3
+ ## Status: PARTIALLY DONE (remaining doubles don't cause failures)
4
+
5
+ ## What's done this session
6
+ - `send` on private methods — 0 occurrences (eliminated)
7
+ - `instance_variable_get/set` — 0 occurrences (eliminated)
8
+ - `method_defined?` — 0 occurrences (eliminated)
9
+ - `double("Connector", class: ...)` in style_resolver_spec — replaced with real UML objects
10
+ - `double("Database")` in class_transformer_spec — replaced with real `build_test_database`
11
+ - `double("Database")` in package_transformer_spec — replaced with real `build_test_database`
12
+ - `double("Database")` in association_transformer_spec — replaced with real `build_test_database`
13
+ - `double("Element")` in base_renderer_spec — replaced with `Struct.new`
14
+ - `double("Connection")` removed from factory specs
15
+ - **Stdlib method shadowing audit on `BaseRepository`** — audited (2026-06-27):
16
+ `find`, `count`, `any?`, `none?`, `group_by` deliberately shadow
17
+ `Enumerable` with ActiveRecord-like signatures (PK lookup, conditions hash,
18
+ attribute symbol). This is intentional API design, not a bug. `find` is
19
+ O(1) via lazy primary-key index (`build_pk_index`); `find_by_key` and
20
+ `where` remain O(n) — see [[16-repository-indexes]] for the decision to
21
+ not optimize those.
22
+
23
+ ## Remaining doubles (low priority — tests pass)
24
+ Some factory specs still use `double()` for auxiliary objects. These should be
25
+ replaced with real instances or Structs for maximum fidelity, but they don't
26
+ cause test failures.
27
+
28
+ ## Coverage added
29
+ - `spec/ea/qea/standalone_api_spec.rb` (7 examples) — standalone API coverage
30
+ - All 1845 examples passing with 0 failures
31
+ - Full suite after this session's refactors: **1953 examples, 0 failures, 37 pending**
32
+
@@ -0,0 +1,172 @@
1
+ # 18 - XMI Tool-Specific Parser Architecture
2
+
3
+ ## Status: DESIGN-CORRECT, CLOSED (2026-06-27)
4
+
5
+ ## Decision
6
+ The architecture described in this document is correct as written and the
7
+ current `ea` codebase follows it:
8
+
9
+ - `Ea::Xmi::Parser` is hard-wired to Sparx via `::Xmi::Sparx::Root.parse_xml`
10
+ — by design. `Ea::Xmi` means Sparx EA XMI, not generic XMI.
11
+ - `ea` does not claim to be the generic `.xmi` handler. `.xmi` registration
12
+ uses content detection (see TODO.next/04) so non-Sparx XMI files fall
13
+ through to other parsers.
14
+ - Future MagicDraw/Papyrus parsers will be **separate gems** with their own
15
+ namespaces (`MagicDraw::Xmi`, `Papyrus::Xmi`), built on the `xmi` gem's
16
+ `Xmi::Uml::*` shared base. OCP: adding a tool = adding a gem, not
17
+ modifying this one.
18
+
19
+ ## No further action needed in `ea`
20
+ This document is design rationale, not a backlog item. Closing it.
21
+
22
+ ## Context
23
+
24
+ XMI (XML Metadata Interchange) is an OMG standard for serializing UML models.
25
+ **All major UML tools export XMI, but each adds tool-specific idiosyncrasies.**
26
+
27
+ | Tool | XMI flavor | Unique extension | Key differences from standard |
28
+ |---|---|---|---|
29
+ | Sparx EA | Sparx XMI | `.qea` (native DB) | EA stereotypes, EA tagged values, `{GUID}` format, `xmlns:ea=`, EA diagram objects |
30
+ | MagicDraw / Cameo | MagicDraw XMI | `.mdxml` | `com.nomagic` namespaces, MagicDraw-specific profiles, different diagram serialization |
31
+ | Eclipse Papyrus | Papyrus XMI | `.uml` | Eclipse namespaces, closer to UML 2.5 reference, OCL constraints |
32
+ | Standard OMG | XMI 2.x | `.xmi` (shared) | No tool extensions — pure UML 2.x |
33
+
34
+ ## The `xmi` gem is already layered
35
+
36
+ The `xmi` gem (v0.5.10) provides:
37
+
38
+ ```
39
+ Xmi::Uml::* — generic UML/XMI elements (PackagedElement, etc.)
40
+ Xmi::Root — base root parser
41
+ Xmi::Sparx::Root — Sparx EA-specific schema
42
+ Xmi::EaRoot — another EA root variant
43
+ Xmi::v20110701 — XMI 2.4.1 schema
44
+ Xmi::v20131001 — XMI 2.4.2 schema
45
+ Xmi::v20161101 — XMI 2.5 schema
46
+ Xmi::CustomProfile::* — profile extensions
47
+ ```
48
+
49
+ This is the correct layering: **generic XMI infrastructure + tool-specific schemas**.
50
+
51
+ ## Current state in `ea`
52
+
53
+ `Ea::Xmi::Parser` is hard-wired to Sparx (line 30):
54
+
55
+ ```ruby
56
+ def get_xmi_model(xml)
57
+ ::Xmi::Sparx::Root.parse_xml(File.read(xml))
58
+ end
59
+ ```
60
+
61
+ It uses `::Xmi::Sparx::Element::*` throughout (`NoteLink`, `Generalization`,
62
+ `Aggregation`, `Association`). **It cannot parse MagicDraw or Papyrus XMI.**
63
+ This is correct — `Ea::Xmi` means Sparx EA XMI, not generic XMI.
64
+
65
+ ## Architecture: each tool gets its own parser gem
66
+
67
+ ### Naming convention
68
+
69
+ | Parser gem | Namespace | Parses | Uses |
70
+ |---|---|---|---|
71
+ | `ea` | `Ea::Xmi` | Sparx EA XMI | `Xmi::Sparx::Root` |
72
+ | `magicdraw` (future) | `MagicDraw::Xmi` | MagicDraw XMI | `Xmi::MagicDraw::Root` (to be added) |
73
+ | `papyrus` (future) | `Papyrus::Xmi` | Eclipse Papyrus XMI | `Xmi::Papyrus::Root` (to be added) |
74
+ | `xmi-standard` (future?) | `XmiStandard` | Pure OMG XMI 2.x | `Xmi::Root` |
75
+
76
+ ### Dependency structure
77
+
78
+ ```
79
+ xmi gem (generic XMI infrastructure + tool-specific schemas)
80
+ ├── Xmi::Uml::* (shared base — all tools use this)
81
+ ├── Xmi::Sparx::* (EA-specific extensions)
82
+ ├── Xmi::MagicDraw::* (future — MagicDraw extensions)
83
+ └── Xmi::Papyrus::* (future — Papyrus extensions)
84
+
85
+ Each tool parser gem builds on xmi:
86
+ ea → uses Xmi::Sparx::* + Xmi::Uml::*
87
+ magicdraw → uses Xmi::MagicDraw::* + Xmi::Uml::*
88
+ papyrus → uses Xmi::Papyrus::* + Xmi::Uml::*
89
+ ```
90
+
91
+ ### Why separate gems, not one "universal XMI" parser
92
+
93
+ 1. **Different idiosyncrasies.** Each tool has its own extensions, stereotypes,
94
+ tagged value formats, and diagram serializations. A "universal" parser would
95
+ be a pile of `if sparx? / if magicdraw? / if papyrus?` branches — worse than
96
+ separate parsers.
97
+
98
+ 2. **Different dependencies.** Sparx needs `sqlite3` (for QEA). MagicDraw needs
99
+ its own profile libraries. Bundling them forces users to install deps they
100
+ don't need.
101
+
102
+ 3. **Independent release cycles.** Tools change their export formats. Each
103
+ parser should be versioned independently.
104
+
105
+ 4. **OCP.** Adding a new tool = adding a new gem, not modifying an existing one.
106
+
107
+ ### What `ea` must NOT do
108
+
109
+ - `ea` must NOT register itself as the generic `.xmi` handler (see TODO.next/04)
110
+ - `ea` must NOT attempt to parse MagicDraw or Papyrus XMI files
111
+ - `ea`'s `.xmi` registration must use content detection to match ONLY Sparx EA
112
+ XMI files (by checking for `"Enterprise Architect"` exporter, `xmlns:ea=`,
113
+ etc.)
114
+
115
+ ### Content detection signatures
116
+
117
+ Each tool's XMI has identifiable signatures in the file header:
118
+
119
+ ```ruby
120
+ # Sparx EA
121
+ content.include?("Enterprise Architect") ||
122
+ content.match?(/xmlns:ea=/) ||
123
+ content.include?("<EA:")
124
+
125
+ # MagicDraw / Cameo
126
+ content.include?("MagicDraw") ||
127
+ content.include?("com.nomagic") ||
128
+ content.include?("nomagic")
129
+
130
+ # Eclipse Papyrus
131
+ content.include?("Papyrus") ||
132
+ content.match?(/eclipse\.org/) ||
133
+ content.match?(/xmlns:.*papyrus/)
134
+
135
+ # Standard OMG XMI (fallback — no tool signatures)
136
+ true
137
+ ```
138
+
139
+ ## Action items
140
+
141
+ ### For `ea` (this gem)
142
+ 1. Keep `Ea::Xmi::Parser` Sparx-specific — no changes to the parser itself
143
+ 2. Register `.xmi` with content detection (not as the sole `.xmi` handler)
144
+ — see TODO.next/04
145
+ 3. Document in `Ea::Xmi::Parser` that it is Sparx-specific and will fail on
146
+ non-Sparx XMI files
147
+
148
+ ### For the `xmi` gem (separate repo)
149
+ 1. Add `Xmi::MagicDraw::*` schema (when MagicDraw support is needed)
150
+ 2. Add `Xmi::Papyrus::*` schema (when Papyrus support is needed)
151
+ 3. These are extensions to the existing `xmi` gem, following the pattern set by
152
+ `Xmi::Sparx::*`
153
+
154
+ ### For future tool-specific parser gems
155
+ 1. Follow the `ea` pattern: standalone gem, own namespace, optional `lutaml-uml`
156
+ dependency
157
+ 2. Register with `UmlRepository` via `register_extension` (unique extensions)
158
+ or `register_format` (shared `.xmi` with content detection)
159
+ 3. Build on the `xmi` gem's infrastructure, not on `ea`
160
+
161
+ ## Why this matters
162
+
163
+ If `ea` claimed generic `.xmi`, a user with a MagicDraw file and both `ea` and
164
+ `lutaml-uml` loaded would get `Ea::Xmi::Parser.parse` — which calls
165
+ `::Xmi::Sparx::Root.parse_xml`. This would either:
166
+ - **Crash** — the Sparx schema doesn't understand MagicDraw extensions
167
+ - **Silently misparse** — applying EA assumptions to non-EA data, losing
168
+ MagicDraw-specific information
169
+ - **Produce wrong results** — correct-looking but semantically incorrect UML
170
+
171
+ Content detection prevents this: the MagicDraw file has no EA signatures, so
172
+ `ea`'s loader is never selected for it.
@@ -0,0 +1,56 @@
1
+ # 09 - Fix ea gemspec: optional lutaml-uml, missing lutaml-model/lutaml-path
2
+
3
+ ## Status: DONE (2026-06-29)
4
+
5
+ ## Problem
6
+ `ea.gemspec` had two dependency-declaration bugs:
7
+
8
+ 1. **`lutaml-uml` was declared as a hard runtime dependency**
9
+ (`spec.add_dependency "lutaml-uml"`) even though the gem is
10
+ documented as standalone with an OPTIONAL lutaml-uml bridge.
11
+ `lib/ea/qea.rb#require_uml!` already lazy-requires `lutaml/uml`
12
+ inside the method body with a clear `LoadError` rescue. The hard
13
+ declaration defeated the optional-bridge design: `gem install ea`
14
+ pulled in `lutaml-uml` (and its dep tree) even for users who only
15
+ wanted QEA/XMI parsing.
16
+
17
+ 2. **`lutaml-model` and `lutaml-path` were undeclared.** Both are
18
+ load-time requires in `lib/ea/`:
19
+ - `lib/ea/qea/models.rb`, `base_model.rb`, `services/configuration.rb`,
20
+ `lib/ea/transformations/configuration.rb` — `require "lutaml/model"`
21
+ (BaseModel extends `Lutaml::Model::Serializable`)
22
+ - `lib/ea/xmi/parser.rb` — `require "lutaml/path"`
23
+ (`Lutaml::Path.parse(path)` for XMI path resolution)
24
+
25
+ These were silently pulled in transitively via `lutaml-uml` →
26
+ `lutaml-path` and `lutaml-uml` → `lutaml-model`. Once `lutaml-uml`
27
+ moved to dev-only, the transitive pull vanished and 11 specs failed
28
+ with `LoadError: cannot load such file -- lutaml/path`.
29
+
30
+ ## Fix
31
+ - Removed `spec.add_dependency "lutaml-uml"` from ea.gemspec.
32
+ - Added `spec.add_development_dependency "lutaml-uml"` (needed for
33
+ the spec suite's UML bridge tests).
34
+ - Added `spec.add_dependency "lutaml-model"` (runtime).
35
+ - Added `spec.add_dependency "lutaml-path"` (runtime).
36
+
37
+ The Gemfile already declares `gem "lutaml-uml", path: "../lutaml-uml"`
38
+ for development, so dev-only declaration in the gemspec is belt-and-
39
+ suspenders.
40
+
41
+ ## Verification
42
+ - `bundle exec rspec`: 1953 examples, 0 failures, 37 pending
43
+ (unchanged from baseline)
44
+ - `gem dependency ea` (after install) no longer pulls `lutaml-uml`
45
+ - Standalone `ea` install correctly pulls `lutaml-model` and
46
+ `lutaml-path` as runtime deps
47
+
48
+ ## Lesson
49
+ A `require` statement at the top of any lib file is a load-time
50
+ contract. If the gemspec doesn't declare the corresponding gem as a
51
+ runtime dependency, the gem only works when something else in the
52
+ bundle happens to pull that gem in transitively. The dependency
53
+ becomes implicit and breaks when the transitive path changes.
54
+
55
+ Audit rule: every `require "external/gem"` at the top of a `lib/`
56
+ file must have a matching `spec.add_dependency` in the gemspec.
@@ -0,0 +1,63 @@
1
+ # 20 - CI requires an unreleased lutaml-uml
2
+
3
+ ## Status: BLOCKED — waiting on lutaml-uml release
4
+
5
+ ## Problem
6
+
7
+ `bundle exec rspec` for the `ea` gem requires `Lutaml::UmlRepository`
8
+ (see `spec/spec_helper.rb:6` and several specs under `spec/ea/qea/` and
9
+ `spec/ea/diagram/`). `UmlRepository` lives in
10
+ `lib/lutaml/uml_repository.rb` in the lutaml-uml source tree.
11
+
12
+ No published version of `lutaml-uml` ships that file (audited
13
+ 2026-06-30: 0.2.0, 0.2.12, 0.3.0, 0.4.3, 1.0.0 all lack it). The
14
+ constant only exists in the local checkout at
15
+ `/Users/mulgogi/src/lutaml/lutaml-uml` and is unreleased.
16
+
17
+ The same applies to several `Lutaml::Uml::*` constants the ea bridge
18
+ references (e.g. `Lutaml::Uml::UmlClass`, which 1.x renamed to
19
+ `Lutaml::Uml::Class`).
20
+
21
+ ## Consequence
22
+
23
+ `ea`'s CI cannot go green until a new `lutaml-uml` version is released
24
+ containing:
25
+
26
+ 1. `lib/lutaml/uml_repository.rb` (the `Lutaml::UmlRepository` namespace)
27
+ 2. The pre-1.x `Lutaml::Uml::*` constant names the bridge code uses
28
+
29
+ ## Current state of the ea PR
30
+
31
+ - Gemfile uses a conditional path-or-rubygems selector for
32
+ `lutaml-uml`/`canon` (`EA_FORCE_RUBYGEMS=1` forces rubygems mode for
33
+ testing the CI-resolved versions locally).
34
+ - Gemfile.lock is committed in rubygems mode so CI installs a
35
+ published `lutaml-uml` rather than failing on a missing sibling path.
36
+ - ea.gemspec pins the dev dep to `~> 0.2.0` to match the API the spec
37
+ suite is written against.
38
+ - CI on this PR will fail with `LoadError: cannot load such file --
39
+ lutaml/uml_repository` at `spec_helper.rb` load time. That failure
40
+ is the clearest signal we can give CI until the matching lutaml-uml
41
+ is released.
42
+
43
+ ## Unblocking steps
44
+
45
+ When a new `lutaml-uml` is released with `uml_repository`:
46
+
47
+ 1. Bump ea.gemspec dev dep to the new version range
48
+ 2. Regenerate Gemfile.lock in rubygems mode
49
+ 3. Re-run CI — should be green
50
+
51
+ ## Architectural follow-up (out of scope here)
52
+
53
+ The ea gem is documented as standalone with an optional UML bridge.
54
+ The spec suite currently requires the bridge unconditionally in
55
+ `spec_helper.rb`. A cleaner architecture would:
56
+
57
+ - Move bridge specs into a separate `spec/bridge/` directory
58
+ - Gate them on `defined?(Lutaml::UmlRepository)` (skip when not loadable)
59
+ - Keep `spec_helper.rb` standalone-only
60
+
61
+ That way the standalone subset of `ea` can be tested in CI without
62
+ any lutaml-uml dependency at all, and the bridge specs run only in
63
+ environments that have a compatible lutaml-uml.