archsight 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +5 -4
- data/README.md +44 -59
- data/chart/archsight/Chart.yaml +6 -0
- data/chart/archsight/README.md +3 -0
- data/chart/archsight/templates/NOTES.txt +22 -0
- data/chart/archsight/templates/_helpers.tpl +62 -0
- data/chart/archsight/templates/deployment.yaml +114 -0
- data/chart/archsight/templates/ingress.yaml +56 -0
- data/chart/archsight/templates/resources-configmap.yaml +10 -0
- data/chart/archsight/templates/resources-pvc.yaml +23 -0
- data/chart/archsight/templates/service.yaml +15 -0
- data/chart/archsight/templates/serviceaccount.yaml +12 -0
- data/chart/archsight/values.yaml +162 -0
- data/docs/architecture.md +39 -0
- data/docs/docker.md +49 -0
- data/{lib/archsight/web/doc → docs}/import.md +10 -2
- data/{lib/archsight/web/doc → docs}/index.md.erb +3 -1
- data/docs/kubernetes.md +149 -0
- data/docs/licenses.md +307 -0
- data/lib/archsight/analysis/executor.rb +0 -10
- data/lib/archsight/annotations/annotation.rb +85 -36
- data/lib/archsight/annotations/architecture_annotations.rb +1 -34
- data/lib/archsight/annotations/computed.rb +1 -1
- data/lib/archsight/annotations/generated_annotations.rb +6 -3
- data/lib/archsight/annotations/git_annotations.rb +8 -4
- data/lib/archsight/annotations/interface_annotations.rb +35 -0
- data/lib/archsight/cli.rb +4 -2
- data/lib/archsight/editor/content_hasher.rb +37 -0
- data/lib/archsight/editor/file_writer.rb +79 -0
- data/lib/archsight/editor.rb +237 -0
- data/lib/archsight/graph.rb +1 -51
- data/lib/archsight/helpers.rb +0 -20
- data/lib/archsight/import/handlers/github.rb +16 -6
- data/lib/archsight/import/handlers/gitlab.rb +28 -10
- data/lib/archsight/import/handlers/repository.rb +56 -6
- data/lib/archsight/import/handlers/rest_api.rb +13 -1
- data/lib/archsight/import/license_analyzer.rb +650 -0
- data/lib/archsight/import/team_matcher.rb +111 -61
- data/lib/archsight/linter.rb +1 -1
- data/lib/archsight/mcp/base.rb +11 -0
- data/lib/archsight/mcp/execute_analysis_tool.rb +100 -0
- data/lib/archsight/mcp.rb +1 -0
- data/lib/archsight/renderer.rb +4 -4
- data/lib/archsight/resources/analysis.rb +1 -17
- data/lib/archsight/resources/application_interface.rb +1 -5
- data/lib/archsight/resources/base.rb +14 -14
- data/lib/archsight/resources/business_actor.rb +18 -3
- data/lib/archsight/resources/technology_artifact.rb +48 -0
- data/lib/archsight/resources/technology_interface.rb +1 -1
- data/lib/archsight/resources/technology_service.rb +5 -0
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/docs.rb +37 -2
- data/lib/archsight/web/api/json_helpers.rb +79 -13
- data/lib/archsight/web/api/openapi/spec.yaml +699 -0
- data/lib/archsight/web/api/routes.rb +23 -0
- data/lib/archsight/web/application.rb +48 -128
- data/lib/archsight/web/editor/form_builder.rb +100 -0
- data/lib/archsight/web/editor/helpers.rb +150 -0
- data/lib/archsight/web/editor/routes.rb +166 -0
- data/lib/archsight/web/public/vue/ApiDocsPage-B1RqTNqh.js +1 -0
- data/lib/archsight/web/public/vue/ApiDocsPage-DhNTOH4o.css +1 -0
- data/lib/archsight/web/public/vue/DocPage-DzwBgBd4.js +1 -0
- data/lib/archsight/web/public/vue/EditorPage-D_miHSv4.js +34 -0
- data/lib/archsight/web/public/vue/EditorPage-Dq0MuTnp.css +1 -0
- data/lib/archsight/web/public/vue/ErrorPage-CQQtPey3.js +2 -0
- data/lib/archsight/web/public/vue/ErrorPage-CwPT3JUr.css +1 -0
- data/lib/archsight/web/public/vue/GraphView-DRcIqAiR.css +1 -0
- data/lib/archsight/web/public/vue/GraphView-T9jFH_qg.js +1 -0
- data/lib/archsight/web/public/vue/InstanceRouter-1Sm-CRhf.js +2 -0
- data/lib/archsight/web/public/vue/InstanceRouter-BJkDRXZY.css +1 -0
- data/lib/archsight/web/public/vue/KindList-JA_L_-Cz.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-8iqavWdg.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-DP-z-j71.css +1 -0
- data/lib/archsight/web/public/vue/SearchResults-BGHbg48-.css +1 -0
- data/lib/archsight/web/public/vue/SearchResults-BdgFeHcm.js +1 -0
- data/lib/archsight/web/public/vue/_basePickBy-CVgieyx-.js +1 -0
- data/lib/archsight/web/public/vue/_baseUniq-BNfrOSaP.js +1 -0
- data/lib/archsight/web/public/vue/architectureDiagram-VXUJARFQ-CJXNpTr5.js +36 -0
- data/lib/archsight/web/public/vue/blockDiagram-VD42YOAC-B5488Hes.js +122 -0
- data/lib/archsight/web/public/vue/c4Diagram-YG6GDRKO-eYY3hprM.js +10 -0
- data/lib/archsight/web/public/vue/chunk-4BX2VUAB-ZoXeL4D1.js +1 -0
- data/lib/archsight/web/public/vue/chunk-55IACEB6-rNtQYnu_.js +1 -0
- data/lib/archsight/web/public/vue/chunk-B4BG7PRW-DolAeVV9.js +165 -0
- data/lib/archsight/web/public/vue/chunk-DI55MBZ5-DnN0f_hj.js +220 -0
- data/lib/archsight/web/public/vue/chunk-FMBD7UC4-BQWOCMuR.js +15 -0
- data/lib/archsight/web/public/vue/chunk-QN33PNHL-DId301Kb.js +1 -0
- data/lib/archsight/web/public/vue/chunk-QZHKN3VN-xbY0NLgv.js +1 -0
- data/lib/archsight/web/public/vue/chunk-TZMSLE5B-CgF9_37b.js +1 -0
- data/lib/archsight/web/public/vue/classDiagram-2ON5EDUG-jGlvI-Za.js +1 -0
- data/lib/archsight/web/public/vue/classDiagram-v2-WZHVMYZB-jGlvI-Za.js +1 -0
- data/lib/archsight/web/public/vue/clone-6iRPe1-W.js +1 -0
- data/lib/archsight/web/public/vue/cose-bilkent-S5V4N54A-CB9Zfu50.js +1 -0
- data/lib/archsight/web/public/vue/cytoscape.esm-5J0xJHOV.js +321 -0
- data/lib/archsight/web/public/vue/dagre-6UL2VRFP-BqkmE-LI.js +4 -0
- data/lib/archsight/web/public/vue/diagram-PSM6KHXK-CKBfqtw3.js +24 -0
- data/lib/archsight/web/public/vue/diagram-QEK2KX5R-B78rOlvK.js +43 -0
- data/lib/archsight/web/public/vue/diagram-S2PKOQOG-BlXC6Cia.js +24 -0
- data/lib/archsight/web/public/vue/erDiagram-Q2GNP2WA-BnliyziJ.js +60 -0
- data/lib/archsight/web/public/vue/flowDiagram-NV44I4VS-wuqPowTd.js +162 -0
- data/lib/archsight/web/public/vue/ganttDiagram-JELNMOA3-GSffAIH3.js +267 -0
- data/lib/archsight/web/public/vue/gitGraphDiagram-V2S2FVAM-OA7VyugW.js +65 -0
- data/lib/archsight/web/public/vue/graph-BXHAtA0S.js +1 -0
- data/lib/archsight/web/public/vue/graphviz-CJms5bxZ.js +13 -0
- data/lib/archsight/web/public/vue/index-DsEsN0_K.js +2 -0
- data/lib/archsight/web/public/vue/index-Tiu4C-Sb.css +1 -0
- data/lib/archsight/web/public/vue/infoDiagram-HS3SLOUP-nlVe2qgv.js +2 -0
- data/lib/archsight/web/public/vue/journeyDiagram-XKPGCS4Q-CtTIcKwf.js +139 -0
- data/lib/archsight/web/public/vue/kanban-definition-3W4ZIXB7-837KX0sW.js +89 -0
- data/lib/archsight/web/public/vue/katex-C-M49wc6.js +261 -0
- data/lib/archsight/web/public/vue/layout-DtE0QdL6.js +1 -0
- data/lib/archsight/web/public/vue/mermaid-DpPHPFQh.js +250 -0
- data/lib/archsight/web/public/vue/mindmap-definition-VGOIOE7T-9gLF2AoY.js +68 -0
- data/lib/archsight/web/public/vue/pieDiagram-ADFJNKIX-CyCNgw3u.js +30 -0
- data/lib/archsight/web/public/vue/quadrantDiagram-AYHSOK5B-CkPh8g02.js +7 -0
- data/lib/archsight/web/public/vue/requirementDiagram-UZGBJVZJ-Dkt6OSlY.js +64 -0
- data/lib/archsight/web/public/vue/sankeyDiagram-TZEHDZUN-BqprTk8x.js +10 -0
- data/lib/archsight/web/public/vue/sequenceDiagram-WL72ISMW-CTmTe1FQ.js +145 -0
- data/lib/archsight/web/public/vue/stateDiagram-FKZM4ZOC-CphqmkEU.js +1 -0
- data/lib/archsight/web/public/vue/stateDiagram-v2-4FDKWEC3-CxaDW5sW.js +1 -0
- data/lib/archsight/web/public/vue/timeline-definition-IT6M3QCI-CSQUZkyE.js +61 -0
- data/lib/archsight/web/public/vue/treemap-GDKQZRPO-DTojm7Yr.js +162 -0
- data/lib/archsight/web/public/{css/graph.css → vue/useGraphviz-A5s4h76R.js} +2 -1
- data/lib/archsight/web/public/vue/useHighlight-C6Kb5G3l.js +10 -0
- data/lib/archsight/web/public/vue/useMermaid-DqxTrLRB.js +1 -0
- data/lib/archsight/web/public/vue/usePanZoom-BybZ_rfh.js +11 -0
- data/lib/archsight/web/public/vue/xychartDiagram-PRI3JC2R-B1ZJZtDC.js +7 -0
- data/lib/archsight/web/public/vue.html +15 -0
- data/media/artifact.jpg +0 -0
- data/media/service.jpg +0 -0
- metadata +104 -77
- data/lib/archsight/web/public/css/artifact.css +0 -995
- data/lib/archsight/web/public/css/base.css +0 -201
- data/lib/archsight/web/public/css/highlight.min.css +0 -10
- data/lib/archsight/web/public/css/iconoir.css +0 -22
- data/lib/archsight/web/public/css/instance.css +0 -818
- data/lib/archsight/web/public/css/layout.css +0 -421
- data/lib/archsight/web/public/css/mermaid-layers.css +0 -188
- data/lib/archsight/web/public/css/pico.min.css +0 -4
- data/lib/archsight/web/public/img/archimate.png +0 -0
- data/lib/archsight/web/public/img/togaf-high-level.png +0 -0
- data/lib/archsight/web/public/js/graph-zoom.js +0 -18
- data/lib/archsight/web/public/js/highlight.min.js +0 -3899
- data/lib/archsight/web/public/js/htmx.min.js +0 -1
- data/lib/archsight/web/public/js/mermaid-init.js +0 -88
- data/lib/archsight/web/public/js/mermaid.min.js +0 -2811
- data/lib/archsight/web/public/js/sparkline.js +0 -42
- data/lib/archsight/web/public/js/svg-pan-zoom.min.js +0 -3
- data/lib/archsight/web/public/js/svg-zoom-controls.js +0 -93
- data/lib/archsight/web/views/api_docs.erb +0 -19
- data/lib/archsight/web/views/index.haml +0 -12
- data/lib/archsight/web/views/partials/artifact/_activity.haml +0 -55
- data/lib/archsight/web/views/partials/artifact/_agentic.haml +0 -25
- data/lib/archsight/web/views/partials/artifact/_deployment.haml +0 -29
- data/lib/archsight/web/views/partials/artifact/_git_info.haml +0 -16
- data/lib/archsight/web/views/partials/artifact/_language_stats.haml +0 -53
- data/lib/archsight/web/views/partials/artifact/_links.haml +0 -24
- data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +0 -32
- data/lib/archsight/web/views/partials/artifact/_repositories.haml +0 -55
- data/lib/archsight/web/views/partials/artifact/_team.haml +0 -83
- data/lib/archsight/web/views/partials/artifact/_workflow.haml +0 -69
- data/lib/archsight/web/views/partials/components/_activity.haml +0 -37
- data/lib/archsight/web/views/partials/components/_git.haml +0 -17
- data/lib/archsight/web/views/partials/components/_jira.haml +0 -18
- data/lib/archsight/web/views/partials/components/_languages.haml +0 -29
- data/lib/archsight/web/views/partials/components/_owner.haml +0 -15
- data/lib/archsight/web/views/partials/components/_repositories.haml +0 -37
- data/lib/archsight/web/views/partials/components/_status.haml +0 -23
- data/lib/archsight/web/views/partials/instance/_analysis_detail.haml +0 -74
- data/lib/archsight/web/views/partials/instance/_analysis_result.haml +0 -64
- data/lib/archsight/web/views/partials/instance/_detail.haml +0 -103
- data/lib/archsight/web/views/partials/instance/_graph.haml +0 -6
- data/lib/archsight/web/views/partials/instance/_import_detail.haml +0 -87
- data/lib/archsight/web/views/partials/instance/_list.haml +0 -84
- data/lib/archsight/web/views/partials/instance/_relations.haml +0 -43
- data/lib/archsight/web/views/partials/instance/_requirements.haml +0 -41
- data/lib/archsight/web/views/partials/instance/_view_detail.haml +0 -57
- data/lib/archsight/web/views/partials/layout/_content.haml +0 -44
- data/lib/archsight/web/views/partials/layout/_error.haml +0 -22
- data/lib/archsight/web/views/partials/layout/_head.haml +0 -24
- data/lib/archsight/web/views/partials/layout/_navigation.haml +0 -21
- data/lib/archsight/web/views/partials/layout/_sidebar.haml +0 -27
- data/lib/archsight/web/views/search.haml +0 -53
- /data/{lib/archsight/web/doc → docs}/archimate.md +0 -0
- /data/{lib/archsight/web/doc → docs}/computed_annotations.md +0 -0
- /data/{lib/archsight/web/doc → docs}/icons.md +0 -0
- /data/{lib/archsight/web/doc → docs}/modeling.md +0 -0
- /data/{lib/archsight/web/doc → docs}/search.md +0 -0
- /data/{lib/archsight/web/doc → docs}/togaf.md +0 -0
- /data/{lib/archsight/web/doc → docs}/tool.md +0 -0
|
@@ -15,11 +15,16 @@ class Archsight::Import::TeamMatcher
|
|
|
15
15
|
# Teams to ignore when matching (bots, unknown, etc.)
|
|
16
16
|
IGNORED_TEAMS = %w[Bot:Team No:Team Team:Unknown Team:Bot].freeze
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# Team annotation keys that contain member/lead information
|
|
19
|
+
TEAM_ANNOTATION_KEYS = %w[team/members team/lead].freeze
|
|
20
|
+
|
|
21
|
+
def initialize(database, corporate_affixes: [])
|
|
19
22
|
@database = database
|
|
23
|
+
@corporate_affixes = corporate_affixes
|
|
20
24
|
@teams = load_teams
|
|
21
25
|
@email_to_team = build_email_index
|
|
22
26
|
@name_to_team = build_name_index
|
|
27
|
+
@member_identities = build_member_identities
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
# Analyze top contributors and return team assignments
|
|
@@ -72,6 +77,12 @@ class Archsight::Import::TeamMatcher
|
|
|
72
77
|
return team if team
|
|
73
78
|
end
|
|
74
79
|
|
|
80
|
+
# Try corporate username pattern match (e.g. jsmith-ionos -> John Smith)
|
|
81
|
+
if name
|
|
82
|
+
team = pattern_match_username(name)
|
|
83
|
+
return team if team
|
|
84
|
+
end
|
|
85
|
+
|
|
75
86
|
nil
|
|
76
87
|
end
|
|
77
88
|
|
|
@@ -87,21 +98,22 @@ class Archsight::Import::TeamMatcher
|
|
|
87
98
|
end
|
|
88
99
|
end
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
# Yields (team_name, annotation_value) for each team/members and team/lead annotation
|
|
102
|
+
def each_team_annotation
|
|
93
103
|
@teams.each do |team|
|
|
94
104
|
team_name = team.name
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
parse_email_list(members).each do |email|
|
|
99
|
-
index[email.downcase] = team_name
|
|
105
|
+
TEAM_ANNOTATION_KEYS.each do |key|
|
|
106
|
+
value = team.annotations[key]
|
|
107
|
+
yield team_name, value if value && !value.empty?
|
|
100
108
|
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_email_index
|
|
113
|
+
index = {}
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
parse_email_list(lead).each do |email|
|
|
115
|
+
each_team_annotation do |team_name, value|
|
|
116
|
+
parse_email_list(value).each do |email|
|
|
105
117
|
index[email.downcase] = team_name
|
|
106
118
|
end
|
|
107
119
|
end
|
|
@@ -112,19 +124,8 @@ class Archsight::Import::TeamMatcher
|
|
|
112
124
|
def build_name_index
|
|
113
125
|
index = {}
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Extract names from team/members annotation
|
|
119
|
-
members = team.annotations["team/members"]
|
|
120
|
-
parse_name_list(members).each do |name|
|
|
121
|
-
normalized = normalize_name(name)
|
|
122
|
-
index[normalized] = team_name if normalized && !normalized.empty?
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Extract name from team/lead annotation
|
|
126
|
-
lead = team.annotations["team/lead"]
|
|
127
|
-
parse_name_list(lead).each do |name|
|
|
127
|
+
each_team_annotation do |team_name, value|
|
|
128
|
+
parse_name_list(value).each do |name|
|
|
128
129
|
normalized = normalize_name(name)
|
|
129
130
|
index[normalized] = team_name if normalized && !normalized.empty?
|
|
130
131
|
end
|
|
@@ -136,60 +137,109 @@ class Archsight::Import::TeamMatcher
|
|
|
136
137
|
# Parse email addresses from team annotation
|
|
137
138
|
# Supports formats: "Name <email>", "email", or comma/newline separated lists
|
|
138
139
|
def parse_email_list(value)
|
|
139
|
-
|
|
140
|
+
parse_name_email_pairs(value).filter_map { |pair| pair[:email] }
|
|
141
|
+
end
|
|
140
142
|
|
|
141
|
-
|
|
143
|
+
# Parse names from team annotation
|
|
144
|
+
# Supports formats: "Name <email>", "First Last", or comma/newline separated lists
|
|
145
|
+
def parse_name_list(value)
|
|
146
|
+
parse_name_email_pairs(value).filter_map { |pair| pair[:name] }
|
|
147
|
+
end
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
# Normalize name for matching
|
|
150
|
+
# Converts to lowercase, removes extra spaces, handles common variations
|
|
151
|
+
def normalize_name(name)
|
|
152
|
+
return nil if name.nil?
|
|
147
153
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
name.to_s
|
|
155
|
+
.downcase
|
|
156
|
+
.gsub(/\s+/, " ")
|
|
157
|
+
.strip
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Match git author name as corporate username pattern {first_initial}{lastname}[-affix] or [affix-]{first_initial}{lastname}
|
|
161
|
+
# e.g. "jsmith-ionos" or "ionos-jsmith" -> initial "j", lastname "smith" -> matches "John Smith"
|
|
162
|
+
#
|
|
163
|
+
# Limitations: Multi-part names (e.g. "Hans von Braun") only match by the last
|
|
164
|
+
# name part ("braun"), so "hvonbraun" would not match. Hyphenated lastnames
|
|
165
|
+
# (e.g. "Meyer-Schmidt") only match the full hyphenated form or the email lastname.
|
|
166
|
+
def pattern_match_username(name)
|
|
167
|
+
username = name.downcase.strip
|
|
168
|
+
return nil unless username.match?(/\A[a-z0-9][-a-z0-9]*\z/)
|
|
169
|
+
return nil if @corporate_affixes.empty?
|
|
170
|
+
|
|
171
|
+
@corporate_affixes.each do |affix|
|
|
172
|
+
clean = affix.delete_prefix("-").delete_suffix("-")
|
|
173
|
+
username = username.delete_suffix("-#{clean}")
|
|
174
|
+
username = username.delete_prefix("#{clean}-")
|
|
175
|
+
end
|
|
176
|
+
return nil if username.length < 3
|
|
177
|
+
|
|
178
|
+
initial = username[0]
|
|
179
|
+
lastname = username[1..]
|
|
180
|
+
return nil if lastname.length < 3
|
|
181
|
+
|
|
182
|
+
candidates = @member_identities.select do |member|
|
|
183
|
+
lastname_match = member[:lastname] == lastname || member[:email_lastname] == lastname
|
|
184
|
+
initial_match = member[:firstname]&.start_with?(initial)
|
|
185
|
+
lastname_match && initial_match
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
teams = candidates.map { |c| c[:team] }.uniq
|
|
189
|
+
return teams.first if teams.size == 1
|
|
190
|
+
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Build identity records for corporate username pattern matching
|
|
195
|
+
def build_member_identities
|
|
196
|
+
identities = []
|
|
197
|
+
|
|
198
|
+
each_team_annotation do |team_name, value|
|
|
199
|
+
parse_name_email_pairs(value).each do |entry|
|
|
200
|
+
name_parts = entry[:name]&.downcase&.split(/\s+/)
|
|
201
|
+
next if name_parts.nil? || name_parts.size < 2
|
|
202
|
+
|
|
203
|
+
firstname = name_parts.first
|
|
204
|
+
lastname = name_parts.last
|
|
205
|
+
|
|
206
|
+
email_prefix = entry[:email]&.split("@")&.first
|
|
207
|
+
email_parts = email_prefix&.split(/[.-]/)
|
|
208
|
+
email_lastname = email_parts&.last&.downcase
|
|
209
|
+
|
|
210
|
+
identities << { team: team_name, firstname: firstname, lastname: lastname, email_lastname: email_lastname }
|
|
154
211
|
end
|
|
155
212
|
end
|
|
156
213
|
|
|
157
|
-
|
|
214
|
+
identities
|
|
158
215
|
end
|
|
159
216
|
|
|
160
|
-
# Parse
|
|
161
|
-
#
|
|
162
|
-
def
|
|
217
|
+
# Parse name and email pairs from team annotation
|
|
218
|
+
# Returns array of { name:, email: } hashes from "Name <email>" format
|
|
219
|
+
def parse_name_email_pairs(value)
|
|
163
220
|
return [] if value.nil? || value.empty?
|
|
164
221
|
|
|
165
|
-
|
|
222
|
+
pairs = []
|
|
166
223
|
|
|
167
|
-
# Split by comma or newline
|
|
168
224
|
value.split(/[,\n]/).each do |entry|
|
|
169
225
|
entry = entry.strip
|
|
170
226
|
next if entry.empty?
|
|
171
227
|
|
|
172
|
-
|
|
173
|
-
|
|
228
|
+
name = nil
|
|
229
|
+
email = nil
|
|
230
|
+
|
|
231
|
+
if (match = entry.match(/^([^<]+)<([^>]+)>/))
|
|
174
232
|
name = match[1].strip
|
|
175
|
-
|
|
176
|
-
elsif
|
|
177
|
-
|
|
178
|
-
|
|
233
|
+
email = match[2].strip
|
|
234
|
+
elsif entry.include?("@")
|
|
235
|
+
email = entry.strip
|
|
236
|
+
else
|
|
237
|
+
name = entry.strip
|
|
179
238
|
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
names
|
|
183
|
-
end
|
|
184
239
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def normalize_name(name)
|
|
188
|
-
return nil if name.nil?
|
|
240
|
+
pairs << { name: name, email: email } if name || email
|
|
241
|
+
end
|
|
189
242
|
|
|
190
|
-
|
|
191
|
-
.downcase
|
|
192
|
-
.gsub(/\s+/, " ")
|
|
193
|
-
.strip
|
|
243
|
+
pairs
|
|
194
244
|
end
|
|
195
245
|
end
|
data/lib/archsight/linter.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "kramdown"
|
|
|
4
4
|
|
|
5
5
|
module Archsight
|
|
6
6
|
class Linter
|
|
7
|
-
#
|
|
7
|
+
# Valid @component references in View annotations
|
|
8
8
|
VALID_COMPONENTS = %w[activity git jira languages owner repositories status].freeze
|
|
9
9
|
|
|
10
10
|
def initialize(database)
|
data/lib/archsight/mcp/base.rb
CHANGED
|
@@ -27,6 +27,17 @@ module Archsight::MCP
|
|
|
27
27
|
result
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def annotations_summary(resource, omit_kind: false)
|
|
31
|
+
result = {
|
|
32
|
+
name: resource.name,
|
|
33
|
+
metadata: {
|
|
34
|
+
annotations: resource.annotations
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
result[:kind] = resource.class.to_s.split("::").last unless omit_kind
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
30
41
|
def extract_description(resource)
|
|
31
42
|
description = resource.annotations["architecture/description"]
|
|
32
43
|
return "No description" if description.nil?
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Archsight::MCP::ExecuteAnalysisTool < FastMcp::Tool
|
|
6
|
+
tool_name "execute_analysis"
|
|
7
|
+
|
|
8
|
+
description <<~DESC.gsub("\n", " ").strip
|
|
9
|
+
Discover and execute Analysis resources that validate architecture data and produce reports.
|
|
10
|
+
|
|
11
|
+
TWO MODES OF OPERATION:
|
|
12
|
+
|
|
13
|
+
1. LIST MODE (no name): Returns all available Analysis resources with name, description,
|
|
14
|
+
handler, and timeout. Use this to discover what analyses exist before running them.
|
|
15
|
+
|
|
16
|
+
2. EXECUTE MODE (name provided): Runs the specified Analysis script in a sandboxed environment
|
|
17
|
+
and returns the results as markdown. The output includes structured findings like tables,
|
|
18
|
+
lists, warnings, and errors generated by the script.
|
|
19
|
+
DESC
|
|
20
|
+
|
|
21
|
+
arguments do
|
|
22
|
+
optional(:name).filled(:string).description(
|
|
23
|
+
"Analysis name to execute (e.g., 'Analysis:Service:TeamOwnership'). " \
|
|
24
|
+
"Omit to list all available analyses."
|
|
25
|
+
)
|
|
26
|
+
optional(:verbose).filled(:bool).description(
|
|
27
|
+
"When true, tables and lists in output are not truncated. Default: false."
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call(name: nil, verbose: false)
|
|
32
|
+
if name.nil?
|
|
33
|
+
list_analyses
|
|
34
|
+
else
|
|
35
|
+
execute_analysis(name, verbose: verbose)
|
|
36
|
+
end
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
error_response(e.message)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def list_analyses
|
|
44
|
+
db = Archsight::MCP.db
|
|
45
|
+
analyses = db.instances_by_kind("Analysis").values
|
|
46
|
+
|
|
47
|
+
resources = analyses.map do |analysis|
|
|
48
|
+
{
|
|
49
|
+
name: analysis.name,
|
|
50
|
+
description: analysis.annotations["analysis/description"] || analysis.annotations["architecture/description"],
|
|
51
|
+
handler: analysis.annotations["analysis/handler"] || "ruby",
|
|
52
|
+
timeout: analysis.annotations["analysis/timeout"] || "30s"
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
JSON.pretty_generate(
|
|
57
|
+
total: resources.size,
|
|
58
|
+
analyses: resources
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def execute_analysis(name, verbose: false)
|
|
63
|
+
require "archsight/analysis"
|
|
64
|
+
|
|
65
|
+
db = Archsight::MCP.db
|
|
66
|
+
analysis = db.instance_by_kind("Analysis", name)
|
|
67
|
+
return error_response("Analysis not found: #{name}") unless analysis
|
|
68
|
+
|
|
69
|
+
executor = Archsight::Analysis::Executor.new(db)
|
|
70
|
+
result = executor.execute(analysis)
|
|
71
|
+
|
|
72
|
+
if result.success?
|
|
73
|
+
JSON.pretty_generate(
|
|
74
|
+
name: result.name,
|
|
75
|
+
success: true,
|
|
76
|
+
duration: result.duration,
|
|
77
|
+
output: result.to_markdown(verbose: verbose),
|
|
78
|
+
has_findings: result.has_findings?,
|
|
79
|
+
warning_count: result.warning_count,
|
|
80
|
+
error_count: result.error_count
|
|
81
|
+
)
|
|
82
|
+
else
|
|
83
|
+
response = {
|
|
84
|
+
name: result.name,
|
|
85
|
+
success: false,
|
|
86
|
+
error: result.error
|
|
87
|
+
}
|
|
88
|
+
partial = result.to_markdown(verbose: verbose)
|
|
89
|
+
response[:partial_output] = partial unless partial.empty?
|
|
90
|
+
JSON.pretty_generate(response)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def error_response(message)
|
|
95
|
+
JSON.pretty_generate(
|
|
96
|
+
error: "Error",
|
|
97
|
+
message: message
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/archsight/mcp.rb
CHANGED
data/lib/archsight/renderer.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Archsight
|
|
|
49
49
|
graph.edge gname(a_inst), gname(b_inst), label: label, fontname: "#{FONT} italic"
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def create_graph_all(db,
|
|
52
|
+
def create_graph_all(db, root_kinds: nil, max_depth: 3, allowed_kinds: nil)
|
|
53
53
|
root_kinds ||= [Archsight::Resources["BusinessProduct"], Archsight::Resources["BusinessProcess"]]
|
|
54
54
|
|
|
55
55
|
# Default allowed kinds for overview: Products, Processes, Services, and Teams
|
|
@@ -61,7 +61,7 @@ module Archsight
|
|
|
61
61
|
]
|
|
62
62
|
allowed_kinds_set = allowed_kinds.to_set
|
|
63
63
|
|
|
64
|
-
Archsight::Graphvis.new("all").
|
|
64
|
+
Archsight::Graphvis.new("all").draw_dot do |g|
|
|
65
65
|
nodes = {} # Track visited nodes
|
|
66
66
|
edges = {} # Track created edges
|
|
67
67
|
|
|
@@ -104,11 +104,11 @@ module Archsight
|
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def create_graph_one(db, klass_pat, name_pat
|
|
107
|
+
def create_graph_one(db, klass_pat, name_pat)
|
|
108
108
|
name = "#{klass_pat}:#{name_pat}"
|
|
109
109
|
nodes = {}
|
|
110
110
|
edges = {}
|
|
111
|
-
Archsight::Graphvis.new(name).
|
|
111
|
+
Archsight::Graphvis.new(name).draw_dot do |g|
|
|
112
112
|
klass = Archsight::Resources[klass_pat] || raise("kind #{klass_pat} unknown")
|
|
113
113
|
instances = db.instances[klass]
|
|
114
114
|
inst = instances[name_pat] || raise("name #{name_pat} for kind #{klass_pat} not found")
|
|
@@ -51,6 +51,7 @@ class Archsight::Resources::Analysis < Archsight::Resources::Base
|
|
|
51
51
|
annotation "analysis/script",
|
|
52
52
|
description: "Ruby script to execute in sandboxed environment",
|
|
53
53
|
title: "Script",
|
|
54
|
+
format: :ruby,
|
|
54
55
|
sidebar: false
|
|
55
56
|
|
|
56
57
|
# Description
|
|
@@ -63,23 +64,6 @@ class Archsight::Resources::Analysis < Archsight::Resources::Base
|
|
|
63
64
|
description: "Maximum execution time (e.g., '30s', '5m')",
|
|
64
65
|
title: "Timeout"
|
|
65
66
|
|
|
66
|
-
# Output configuration
|
|
67
|
-
annotation "analysis/output",
|
|
68
|
-
description: "Output mode for results",
|
|
69
|
-
title: "Output",
|
|
70
|
-
enum: %w[console file]
|
|
71
|
-
|
|
72
|
-
annotation "analysis/outputPath",
|
|
73
|
-
description: "File path for output (when output mode is 'file')",
|
|
74
|
-
title: "Output Path",
|
|
75
|
-
sidebar: false
|
|
76
|
-
|
|
77
|
-
# Enabled flag
|
|
78
|
-
annotation "analysis/enabled",
|
|
79
|
-
description: "Whether this analysis is enabled",
|
|
80
|
-
title: "Enabled",
|
|
81
|
-
enum: %w[true false]
|
|
82
|
-
|
|
83
67
|
# Pattern annotation for custom configuration
|
|
84
68
|
annotation "analysis/config/*",
|
|
85
69
|
description: "Custom configuration values for the analysis script",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# ApplicationInterface between ApplicationComponent
|
|
4
4
|
class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
|
|
5
|
-
include_annotations :git, :architecture, :generated
|
|
5
|
+
include_annotations :git, :architecture, :interface, :generated
|
|
6
6
|
|
|
7
7
|
description <<~MD
|
|
8
8
|
Represents a point of access where application services are made available.
|
|
@@ -39,10 +39,6 @@ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
|
|
|
39
39
|
description: "API authentication method",
|
|
40
40
|
title: "API Authentication Method",
|
|
41
41
|
enum: ["none", "hard coded", "token", "oidc"]
|
|
42
|
-
annotation "api/authenticationProvider",
|
|
43
|
-
description: "API authentication provider",
|
|
44
|
-
title: "API Authentication Provider",
|
|
45
|
-
enum: %w[eiam cloud custom]
|
|
46
42
|
annotation "api/authorization",
|
|
47
43
|
description: "API authorization mechanism",
|
|
48
44
|
title: "API Authorization",
|
|
@@ -15,7 +15,7 @@ module Archsight
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def self.relation(verb, kind, klass_name)
|
|
18
|
-
@relations ||= []
|
|
18
|
+
@relations ||= [] #: Array[[Symbol, Symbol, String]]
|
|
19
19
|
@relations << [verb, kind, klass_name]
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -25,10 +25,10 @@ module Archsight
|
|
|
25
25
|
|
|
26
26
|
# Define an annotation using the Annotation class
|
|
27
27
|
def self.annotation(key, description: nil, filter: nil, title: nil, format: nil, enum: nil, sidebar: true,
|
|
28
|
-
type: nil, list: false)
|
|
29
|
-
@annotations ||= []
|
|
28
|
+
type: nil, list: false, editor: true)
|
|
29
|
+
@annotations ||= [] #: Array[Archsight::Annotations::Annotation]
|
|
30
30
|
options = { description: description, filter: filter, title: title, format: format, enum: enum,
|
|
31
|
-
sidebar: sidebar, type: type, list: list }
|
|
31
|
+
sidebar: sidebar, type: type, list: list, editor: editor }
|
|
32
32
|
@annotations << Archsight::Annotations::Annotation.new(key, options)
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -51,15 +51,15 @@ module Archsight
|
|
|
51
51
|
# @param list [Boolean] Whether values are lists (default false)
|
|
52
52
|
# @yield Block that computes the annotation value, evaluated in Evaluator context
|
|
53
53
|
def self.computed_annotation(key, description: nil, filter: nil, title: nil, format: nil, enum: nil,
|
|
54
|
-
sidebar: false, type: nil, list: false, &)
|
|
54
|
+
sidebar: false, type: nil, list: false, editor: true, &)
|
|
55
55
|
require_relative "../annotations/computed"
|
|
56
|
-
@computed_annotations ||= []
|
|
56
|
+
@computed_annotations ||= [] #: Array[Archsight::Annotations::Computed]
|
|
57
57
|
@computed_annotations << Archsight::Annotations::Computed.new(key, description: description, type: type, &)
|
|
58
58
|
|
|
59
59
|
# Also register as a regular annotation so it passes validation and is recognized
|
|
60
|
-
@annotations ||= []
|
|
60
|
+
@annotations ||= [] #: Array[Archsight::Annotations::Annotation]
|
|
61
61
|
options = { description: description, filter: filter, title: title, format: format, enum: enum,
|
|
62
|
-
sidebar: sidebar, type: type, list: list }
|
|
62
|
+
sidebar: sidebar, type: type, list: list, editor: editor }
|
|
63
63
|
@annotations << Archsight::Annotations::Annotation.new(key, options)
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -180,11 +180,11 @@ module Archsight
|
|
|
180
180
|
# @param key [String] The annotation key
|
|
181
181
|
# @param value [Object] The computed value
|
|
182
182
|
def set_computed_annotation(key, value)
|
|
183
|
-
@computed_values ||= {}
|
|
183
|
+
@computed_values ||= {} #: Hash[String, untyped]
|
|
184
184
|
@computed_values[key] = value
|
|
185
185
|
# Write to annotations hash for query compatibility
|
|
186
186
|
@raw["metadata"] ||= {}
|
|
187
|
-
@raw["metadata"]["annotations"]
|
|
187
|
+
@raw["metadata"]["annotations"] = @raw["metadata"]["annotations"] || {} #: Hash[String, String]
|
|
188
188
|
@raw["metadata"]["annotations"][key] = value
|
|
189
189
|
end
|
|
190
190
|
|
|
@@ -241,13 +241,13 @@ module Archsight
|
|
|
241
241
|
# Get references grouped by kind and verb for display (incoming)
|
|
242
242
|
# Returns: { "Kind" => { "verb" => [instances...] } }
|
|
243
243
|
def references_grouped
|
|
244
|
-
grouped = {}
|
|
244
|
+
grouped = {} #: Hash[String, Hash[untyped, Array[Base]]]
|
|
245
245
|
@references.each do |ref|
|
|
246
246
|
inst = ref[:instance]
|
|
247
247
|
verb = ref[:verb]
|
|
248
248
|
kind = inst.klass
|
|
249
249
|
grouped[kind] ||= {}
|
|
250
|
-
grouped[kind][verb] ||= []
|
|
250
|
+
grouped[kind][verb] ||= [] #: Array[Base]
|
|
251
251
|
grouped[kind][verb] << inst
|
|
252
252
|
end
|
|
253
253
|
# Sort by kind name, then by verb name
|
|
@@ -257,7 +257,7 @@ module Archsight
|
|
|
257
257
|
# Get outgoing relations grouped by verb and kind for display
|
|
258
258
|
# Returns: { "verb" => { "Kind" => [instances...] } }
|
|
259
259
|
def relations_grouped
|
|
260
|
-
grouped = {}
|
|
260
|
+
grouped = {} #: Hash[String, Hash[String, Array[Base]]]
|
|
261
261
|
spec.each do |verb, kinds|
|
|
262
262
|
next unless kinds.is_a?(Hash)
|
|
263
263
|
|
|
@@ -267,7 +267,7 @@ module Archsight
|
|
|
267
267
|
instances.each do |inst|
|
|
268
268
|
kind = inst.klass
|
|
269
269
|
grouped[verb] ||= {}
|
|
270
|
-
grouped[verb][kind] ||= []
|
|
270
|
+
grouped[verb][kind] ||= [] #: Array[Base]
|
|
271
271
|
grouped[verb][kind] << inst
|
|
272
272
|
end
|
|
273
273
|
end
|
|
@@ -43,7 +43,7 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
|
|
|
43
43
|
title: "Team Members",
|
|
44
44
|
sidebar: false,
|
|
45
45
|
filter: :list,
|
|
46
|
-
format: :
|
|
46
|
+
format: :multiline,
|
|
47
47
|
type: Archsight::Annotations::EmailRecipient
|
|
48
48
|
|
|
49
49
|
annotation "team/jira",
|
|
@@ -181,7 +181,7 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
|
|
|
181
181
|
|
|
182
182
|
computed_annotation "jira/projectUrl",
|
|
183
183
|
title: "Jira Board",
|
|
184
|
-
description: "Link to Jira board (computed from team/jira)",
|
|
184
|
+
description: "Link to Jira board (computed from team/jira and Import host config)",
|
|
185
185
|
format: :link do
|
|
186
186
|
jira_key = @instance.annotations["team/jira"]
|
|
187
187
|
next nil if jira_key.nil? || jira_key.empty?
|
|
@@ -190,6 +190,21 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
|
|
|
190
190
|
primary_key = jira_key.split(/[,\n]/).first&.strip
|
|
191
191
|
next nil if primary_key.nil? || primary_key.empty?
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
# Find Jira host from Import resources with a Jira handler
|
|
194
|
+
jira_host = nil
|
|
195
|
+
imports = @database.instances_by_kind("Import")
|
|
196
|
+
imports.each_value do |imp|
|
|
197
|
+
handler = imp.annotations["import/handler"]
|
|
198
|
+
next unless handler&.match?(/\AJira/i)
|
|
199
|
+
|
|
200
|
+
host = imp.annotations["import/config/host"]
|
|
201
|
+
if host && !host.empty?
|
|
202
|
+
jira_host = host
|
|
203
|
+
break
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
next nil unless jira_host
|
|
207
|
+
|
|
208
|
+
"https://#{jira_host}/projects/#{primary_key}/issues"
|
|
194
209
|
end
|
|
195
210
|
end
|
|
@@ -158,6 +158,54 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
|
|
|
158
158
|
title: "Deployment Container Privileges",
|
|
159
159
|
enum: %w[unprivileged privileged]
|
|
160
160
|
|
|
161
|
+
# License information
|
|
162
|
+
annotation "license/spdx",
|
|
163
|
+
description: "SPDX license identifier",
|
|
164
|
+
title: "License",
|
|
165
|
+
filter: :word,
|
|
166
|
+
sidebar: false,
|
|
167
|
+
enum: %w[Apache-2.0 MIT BSD-3-Clause BSD-2-Clause GPL-3.0 GPL-2.0 LGPL-3.0
|
|
168
|
+
LGPL-2.1 MPL-2.0 ISC AGPL-3.0 Unlicense CC0-1.0 BSL-1.0 EUPL-1.2
|
|
169
|
+
0BSD CDDL-1.0 Ruby NOASSERTION proprietary unknown]
|
|
170
|
+
annotation "license/file",
|
|
171
|
+
description: "License file path",
|
|
172
|
+
title: "License File",
|
|
173
|
+
sidebar: false
|
|
174
|
+
annotation "license/category",
|
|
175
|
+
description: "License category",
|
|
176
|
+
title: "License Category",
|
|
177
|
+
filter: :word,
|
|
178
|
+
enum: %w[permissive copyleft weak-copyleft proprietary unknown]
|
|
179
|
+
annotation "license/dependencies/count",
|
|
180
|
+
description: "Total dependencies",
|
|
181
|
+
title: "Dependencies",
|
|
182
|
+
type: Integer
|
|
183
|
+
annotation "license/dependencies/ecosystems",
|
|
184
|
+
description: "Detected dependency ecosystems",
|
|
185
|
+
title: "Ecosystems",
|
|
186
|
+
filter: :list,
|
|
187
|
+
sidebar: false
|
|
188
|
+
annotation "license/dependencies/licenses",
|
|
189
|
+
description: "Dependency license types",
|
|
190
|
+
title: "Dependency Licenses",
|
|
191
|
+
filter: :list,
|
|
192
|
+
sidebar: false
|
|
193
|
+
annotation "license/dependencies/copyleft",
|
|
194
|
+
description: "Copyleft dependencies present",
|
|
195
|
+
title: "Copyleft Dependencies",
|
|
196
|
+
filter: :word,
|
|
197
|
+
sidebar: false,
|
|
198
|
+
enum: %w[true false unknown]
|
|
199
|
+
annotation "license/dependencies/risk",
|
|
200
|
+
description: "License risk level",
|
|
201
|
+
title: "License Risk",
|
|
202
|
+
filter: :word,
|
|
203
|
+
sidebar: false,
|
|
204
|
+
enum: %w[low weak-copyleft copyleft unknown]
|
|
205
|
+
annotation "license/dependencies/*/count",
|
|
206
|
+
description: "Dependencies per license type",
|
|
207
|
+
sidebar: false
|
|
208
|
+
|
|
161
209
|
# Contributor metrics (unique counts, stored during import)
|
|
162
210
|
annotation "activity/contributors/6m",
|
|
163
211
|
title: "Contributors (6 months)",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# TechnologyInterface is the backing of an applicationInterface
|
|
4
4
|
class Archsight::Resources::TechnologyInterface < Archsight::Resources::Base
|
|
5
|
-
include_annotations :git, :architecture
|
|
5
|
+
include_annotations :git, :architecture, :interface
|
|
6
6
|
|
|
7
7
|
description <<~MD
|
|
8
8
|
Represents a point of access where technology services are made available.
|
|
@@ -30,6 +30,11 @@ class Archsight::Resources::TechnologyService < Archsight::Resources::Base
|
|
|
30
30
|
icon "cloud"
|
|
31
31
|
layer "technology"
|
|
32
32
|
|
|
33
|
+
annotation "architecture/applicationSets",
|
|
34
|
+
description: "Related ArgoCD ApplicationSets",
|
|
35
|
+
title: "ApplicationSets",
|
|
36
|
+
format: :markdown
|
|
37
|
+
|
|
33
38
|
relation :suppliedBy, :technologyComponents, :TechnologySystemSoftware
|
|
34
39
|
relation :servedBy, :businessActors, :BusinessActor
|
|
35
40
|
end
|
data/lib/archsight/version.rb
CHANGED