archsight 0.1.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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +186 -0
  5. data/Dockerfile +39 -0
  6. data/LICENSE.txt +201 -0
  7. data/README.md +170 -0
  8. data/SECURITY.md +27 -0
  9. data/exe/archsight +9 -0
  10. data/lib/archsight/annotations/aggregators.rb +109 -0
  11. data/lib/archsight/annotations/annotation.rb +168 -0
  12. data/lib/archsight/annotations/architecture_annotations.rb +59 -0
  13. data/lib/archsight/annotations/backup_annotations.rb +21 -0
  14. data/lib/archsight/annotations/computed.rb +264 -0
  15. data/lib/archsight/annotations/email_recipient.rb +35 -0
  16. data/lib/archsight/annotations/generated_annotations.rb +17 -0
  17. data/lib/archsight/annotations/git_annotations.rb +21 -0
  18. data/lib/archsight/annotations/relation_resolver.rb +160 -0
  19. data/lib/archsight/cli.rb +120 -0
  20. data/lib/archsight/configuration.rb +36 -0
  21. data/lib/archsight/database.rb +183 -0
  22. data/lib/archsight/documentation.rb +171 -0
  23. data/lib/archsight/graph.rb +113 -0
  24. data/lib/archsight/helpers.rb +210 -0
  25. data/lib/archsight/linter.rb +77 -0
  26. data/lib/archsight/mcp/analyze_resource_tool.rb +222 -0
  27. data/lib/archsight/mcp/base.rb +48 -0
  28. data/lib/archsight/mcp/query_tool.rb +113 -0
  29. data/lib/archsight/mcp/resource_doc_tool.rb +87 -0
  30. data/lib/archsight/mcp.rb +6 -0
  31. data/lib/archsight/query/ast.rb +279 -0
  32. data/lib/archsight/query/errors.rb +39 -0
  33. data/lib/archsight/query/evaluator.rb +707 -0
  34. data/lib/archsight/query/lexer.rb +289 -0
  35. data/lib/archsight/query/parser.rb +506 -0
  36. data/lib/archsight/query.rb +68 -0
  37. data/lib/archsight/renderer.rb +134 -0
  38. data/lib/archsight/resources/application_component.rb +346 -0
  39. data/lib/archsight/resources/application_interface.rb +54 -0
  40. data/lib/archsight/resources/application_service.rb +222 -0
  41. data/lib/archsight/resources/base.rb +300 -0
  42. data/lib/archsight/resources/business_actor.rb +195 -0
  43. data/lib/archsight/resources/business_constraint.rb +32 -0
  44. data/lib/archsight/resources/business_process.rb +37 -0
  45. data/lib/archsight/resources/business_product.rb +206 -0
  46. data/lib/archsight/resources/business_requirement.rb +56 -0
  47. data/lib/archsight/resources/compliance_evidence.rb +42 -0
  48. data/lib/archsight/resources/data_object.rb +49 -0
  49. data/lib/archsight/resources/motivation_goal.rb +37 -0
  50. data/lib/archsight/resources/motivation_outcome.rb +33 -0
  51. data/lib/archsight/resources/motivation_stakeholder.rb +38 -0
  52. data/lib/archsight/resources/strategy_capability.rb +38 -0
  53. data/lib/archsight/resources/technology_artifact.rb +154 -0
  54. data/lib/archsight/resources/technology_interface.rb +34 -0
  55. data/lib/archsight/resources/technology_node.rb +42 -0
  56. data/lib/archsight/resources/technology_service.rb +35 -0
  57. data/lib/archsight/resources/technology_system_software.rb +37 -0
  58. data/lib/archsight/resources/view.rb +51 -0
  59. data/lib/archsight/resources.rb +49 -0
  60. data/lib/archsight/template.rb +49 -0
  61. data/lib/archsight/version.rb +5 -0
  62. data/lib/archsight/web/application.rb +290 -0
  63. data/lib/archsight/web/doc/archimate.md +215 -0
  64. data/lib/archsight/web/doc/computed_annotations.md +316 -0
  65. data/lib/archsight/web/doc/icons.md +303 -0
  66. data/lib/archsight/web/doc/index.md.erb +74 -0
  67. data/lib/archsight/web/doc/modeling.md +200 -0
  68. data/lib/archsight/web/doc/search.md +227 -0
  69. data/lib/archsight/web/doc/togaf.md +255 -0
  70. data/lib/archsight/web/doc/tool.md +90 -0
  71. data/lib/archsight/web/public/css/artifact.css +985 -0
  72. data/lib/archsight/web/public/css/base.css +201 -0
  73. data/lib/archsight/web/public/css/graph.css +106 -0
  74. data/lib/archsight/web/public/css/highlight.min.css +10 -0
  75. data/lib/archsight/web/public/css/iconoir.css +22 -0
  76. data/lib/archsight/web/public/css/instance.css +329 -0
  77. data/lib/archsight/web/public/css/layout.css +421 -0
  78. data/lib/archsight/web/public/css/mermaid-layers.css +188 -0
  79. data/lib/archsight/web/public/css/pico.min.css +4 -0
  80. data/lib/archsight/web/public/favicon.ico +0 -0
  81. data/lib/archsight/web/public/img/archimate.png +0 -0
  82. data/lib/archsight/web/public/img/togaf-high-level.png +0 -0
  83. data/lib/archsight/web/public/js/graph-zoom.js +18 -0
  84. data/lib/archsight/web/public/js/highlight.min.js +3899 -0
  85. data/lib/archsight/web/public/js/htmx.min.js +1 -0
  86. data/lib/archsight/web/public/js/mermaid-init.js +88 -0
  87. data/lib/archsight/web/public/js/mermaid.min.js +2811 -0
  88. data/lib/archsight/web/public/js/sparkline.js +42 -0
  89. data/lib/archsight/web/public/js/svg-pan-zoom.min.js +3 -0
  90. data/lib/archsight/web/public/js/svg-zoom-controls.js +93 -0
  91. data/lib/archsight/web/views/index.haml +12 -0
  92. data/lib/archsight/web/views/partials/artifact/_activity.haml +55 -0
  93. data/lib/archsight/web/views/partials/artifact/_agentic.haml +25 -0
  94. data/lib/archsight/web/views/partials/artifact/_deployment.haml +29 -0
  95. data/lib/archsight/web/views/partials/artifact/_git_info.haml +16 -0
  96. data/lib/archsight/web/views/partials/artifact/_language_stats.haml +53 -0
  97. data/lib/archsight/web/views/partials/artifact/_links.haml +24 -0
  98. data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +26 -0
  99. data/lib/archsight/web/views/partials/artifact/_repositories.haml +55 -0
  100. data/lib/archsight/web/views/partials/artifact/_team.haml +83 -0
  101. data/lib/archsight/web/views/partials/artifact/_workflow.haml +69 -0
  102. data/lib/archsight/web/views/partials/components/_activity.haml +37 -0
  103. data/lib/archsight/web/views/partials/components/_git.haml +17 -0
  104. data/lib/archsight/web/views/partials/components/_jira.haml +18 -0
  105. data/lib/archsight/web/views/partials/components/_languages.haml +29 -0
  106. data/lib/archsight/web/views/partials/components/_owner.haml +15 -0
  107. data/lib/archsight/web/views/partials/components/_repositories.haml +37 -0
  108. data/lib/archsight/web/views/partials/components/_status.haml +23 -0
  109. data/lib/archsight/web/views/partials/instance/_detail.haml +99 -0
  110. data/lib/archsight/web/views/partials/instance/_graph.haml +6 -0
  111. data/lib/archsight/web/views/partials/instance/_list.haml +84 -0
  112. data/lib/archsight/web/views/partials/instance/_relations.haml +43 -0
  113. data/lib/archsight/web/views/partials/instance/_requirements.haml +41 -0
  114. data/lib/archsight/web/views/partials/instance/_view_detail.haml +57 -0
  115. data/lib/archsight/web/views/partials/layout/_content.haml +40 -0
  116. data/lib/archsight/web/views/partials/layout/_error.haml +22 -0
  117. data/lib/archsight/web/views/partials/layout/_head.haml +24 -0
  118. data/lib/archsight/web/views/partials/layout/_navigation.haml +20 -0
  119. data/lib/archsight/web/views/partials/layout/_sidebar.haml +27 -0
  120. data/lib/archsight/web/views/search.haml +53 -0
  121. data/lib/archsight.rb +17 -0
  122. metadata +311 -0
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Archsight::MCP::QueryTool < FastMcp::Tool
6
+ tool_name "query"
7
+
8
+ description <<~DESC.gsub("\n", " ").strip
9
+ Search and filter architecture resources using a powerful query language.
10
+
11
+ QUERY SYNTAX:
12
+
13
+ • Name search: 'kubernetes' (shortcut for name =~ "kubernetes"), 'name == "ExactName"', 'name =~ "pattern"'
14
+
15
+ • Kind filter prefix: 'TechnologyArtifact: ...' restricts search to that resource type
16
+
17
+ • Annotation filters: 'activity/status == "active"', 'scc/language/Go/loc > 5000'
18
+ Operators: == (equals), != (not equals), =~ (regex match), >, <, >=, <=
19
+
20
+ • Relation queries:
21
+ -> Kind (has outgoing relation to kind), -> "Name" (to specific instance)
22
+ <- Kind (has incoming relation from kind), <- "Name" (from specific instance)
23
+ ~> Kind (transitively reaches), <~ Kind (transitively reached by)
24
+ -> none (no outgoing relations), <- none (no incoming relations / orphan detection)
25
+
26
+ • Sub-query targets: $(expression) - dynamic relation matching
27
+ -> $(expr) (relates to any resource matching expr)
28
+ ~> $(expr) (transitively reaches any matching resource)
29
+ <- $(expr) (incoming from any matching resource)
30
+
31
+ • Logical operators: AND/and/&, OR/or/|, NOT/not/!, parentheses for grouping
32
+
33
+ EXAMPLES:
34
+ 'kubernetes' - resources with "kubernetes" in name
35
+ 'TechnologyArtifact: activity/status == "active"' - active TechnologyArtifacts
36
+ '-> ApplicationInterface & repository/artifacts == "container"' - containerized services exposing APIs
37
+ '<- none' - resources not referenced by anything (potential orphans)
38
+ '-> none & <- none' - true orphans with no relations at all
39
+ '~> $(dcd-mf-dcxpress)' - transitively reaches instance matching "dcd-mf-dcxpress"
40
+ '<- $(TechnologyArtifact: activity/status == "active")' - referenced by active artifacts
41
+ DESC
42
+ arguments do
43
+ required(:query).filled(:string).description(
44
+ "Query string using the query language syntax. " \
45
+ 'Examples: \'kubernetes\', \'TechnologyArtifact: activity/status == "active"\', ' \
46
+ "'-> ApplicationInterface', '<- none'"
47
+ )
48
+ optional(:output).filled(:string).description(
49
+ "Output format: " \
50
+ "'complete' = full resource details including annotations and relations (default), " \
51
+ "'brief' = minimal output with just kind and name for each resource, " \
52
+ "'count' = only totals grouped by kind, no individual resources returned"
53
+ )
54
+ optional(:limit).filled(:integer).description(
55
+ "Maximum results to return (1-500). Use with offset for pagination. Ignored when output='count'."
56
+ )
57
+ optional(:offset).filled(:integer).description(
58
+ "Number of results to skip. Use with limit for pagination (e.g., offset=50, limit=50 for page 2). " \
59
+ "Ignored when output='count'."
60
+ )
61
+ end
62
+
63
+ def call(query:, output: "complete", limit: 50, offset: 0)
64
+ db = Archsight::MCP.db
65
+
66
+ begin
67
+ parsed_query = Archsight::Query.parse(query)
68
+ results = parsed_query.filter(db)
69
+ total = results.length
70
+
71
+ # Omit kind from output when filtering by a specific kind (it's redundant)
72
+ omit_kind = !parsed_query.kind_filter.nil?
73
+
74
+ # Count-only mode: return totals grouped by kind
75
+ if output == "count"
76
+ by_kind = results.group_by { |r| r.class.to_s.split("::").last }
77
+ .transform_values(&:length)
78
+
79
+ result = {
80
+ query: query,
81
+ total: total,
82
+ by_kind: by_kind
83
+ }
84
+ else
85
+ # Complete or brief mode: return paginated resources
86
+ paginated = results.sort_by(&:name).drop(offset).take(limit)
87
+ resources = case output
88
+ when "brief"
89
+ paginated.map { |r| Archsight::MCP.brief_summary(r, omit_kind: omit_kind) }
90
+ else # "complete"
91
+ paginated.map { |r| Archsight::MCP.complete_summary(r, omit_kind: omit_kind) }
92
+ end
93
+
94
+ result = {
95
+ query: query,
96
+ total: total,
97
+ limit: limit,
98
+ offset: offset,
99
+ count: paginated.length,
100
+ resources: resources
101
+ }
102
+ end
103
+
104
+ JSON.pretty_generate(result)
105
+ rescue Archsight::Query::QueryError => e
106
+ JSON.pretty_generate({
107
+ error: "Query error",
108
+ message: e.message,
109
+ query: query
110
+ })
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Archsight::MCP::ResourceDocTool < FastMcp::Tool
6
+ tool_name "resource_doc"
7
+
8
+ description <<~DESC.gsub("\n", " ").strip
9
+ Get documentation for architecture resource kinds.
10
+
11
+ TWO MODES OF OPERATION:
12
+
13
+ 1. LIST MODE (no kind specified): Returns a list of all available resource kinds with their descriptions.
14
+ Useful for discovering what types of resources exist in the architecture model.
15
+ Example: call with no parameters
16
+
17
+ 2. DOCUMENTATION MODE (kind specified): Returns detailed documentation for a specific resource kind.
18
+ Includes description, available annotations, relations, and a YAML example template.
19
+ Example: kind="TechnologyArtifact"
20
+
21
+ COMMON RESOURCE KINDS:
22
+ • TechnologyArtifact - Code repositories, artifacts, and technology components
23
+ • ApplicationComponent - Services and application building blocks
24
+ • ApplicationInterface - APIs and interfaces exposed by components
25
+ • ApplicationService - Business services provided by applications
26
+ • BusinessRequirement - Compliance controls and business requirements
27
+ • ComplianceEvidence - Evidence linking artifacts to compliance requirements
28
+ DESC
29
+ arguments do
30
+ optional(:kind).filled(:string).description(
31
+ 'Resource kind to get documentation for (e.g., "TechnologyArtifact", "ApplicationComponent"). ' \
32
+ "If not specified, returns a list of all available resource kinds."
33
+ )
34
+ end
35
+
36
+ def call(kind: nil)
37
+ if kind.nil? || kind.empty?
38
+ list_resource_kinds
39
+ else
40
+ get_resource_documentation(kind)
41
+ end
42
+ rescue StandardError => e
43
+ error_response(e.message, e.class.name)
44
+ end
45
+
46
+ private
47
+
48
+ def error_response(message, error_type = "Error")
49
+ JSON.pretty_generate({
50
+ error: error_type,
51
+ message: message
52
+ })
53
+ end
54
+
55
+ def list_resource_kinds
56
+ kinds = []
57
+
58
+ Archsight::Resources.each do |kind_symbol|
59
+ klass = Archsight::Resources.const_get(kind_symbol)
60
+ kinds << {
61
+ kind: kind_symbol.to_s,
62
+ description: klass.description || "No description available",
63
+ annotation_count: klass.annotations.count,
64
+ relation_count: klass.relations.count
65
+ }
66
+ end
67
+
68
+ result = {
69
+ total: kinds.length,
70
+ resource_kinds: kinds
71
+ }
72
+
73
+ JSON.pretty_generate(result)
74
+ end
75
+
76
+ def get_resource_documentation(kind)
77
+ klass = Archsight::Resources[kind]
78
+ return error_response("Unknown resource kind: #{kind}") unless klass
79
+
80
+ content = Archsight::Documentation.generate(kind)
81
+
82
+ JSON.pretty_generate({
83
+ kind: kind,
84
+ documentation: content
85
+ })
86
+ end
87
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mcp/base"
4
+ require_relative "mcp/query_tool"
5
+ require_relative "mcp/analyze_resource_tool"
6
+ require_relative "mcp/resource_doc_tool"
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AST module contains all Abstract Syntax Tree node types for the query language
4
+ module Archsight::Query::AST
5
+ # Base class for all AST nodes
6
+ class Node
7
+ def accept(visitor)
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+
12
+ # Root query node with optional kind filter
13
+ class QueryNode < Node
14
+ attr_reader :kind_filter, :expression
15
+
16
+ def initialize(kind_filter, expression)
17
+ @kind_filter = kind_filter # String or nil
18
+ @expression = expression # Expression node
19
+ end
20
+
21
+ def accept(visitor)
22
+ visitor.visit_query(self)
23
+ end
24
+ end
25
+
26
+ # Binary logical operation (AND, OR)
27
+ class BinaryOp < Node
28
+ attr_reader :operator, :left, :right
29
+
30
+ def initialize(operator, left, right)
31
+ @operator = operator # :and, :or
32
+ @left = left
33
+ @right = right
34
+ end
35
+
36
+ def accept(visitor)
37
+ visitor.visit_binary_op(self)
38
+ end
39
+ end
40
+
41
+ # Unary NOT operation
42
+ class NotOp < Node
43
+ attr_reader :operand
44
+
45
+ def initialize(operand)
46
+ @operand = operand
47
+ end
48
+
49
+ def accept(visitor)
50
+ visitor.visit_not_op(self)
51
+ end
52
+ end
53
+
54
+ # Annotation condition: annotation_path op value
55
+ class AnnotationCondition < Node
56
+ attr_reader :path, :operator, :value
57
+
58
+ def initialize(path, operator, value)
59
+ @path = path # String (e.g., "activity/status")
60
+ @operator = operator # String (==, !=, =~, >, <, >=, <=)
61
+ @value = value # StringValue, NumberValue, or RegexValue
62
+ end
63
+
64
+ def accept(visitor)
65
+ visitor.visit_annotation_condition(self)
66
+ end
67
+ end
68
+
69
+ # Annotation existence condition: annotation_path? (checks if annotation exists)
70
+ class AnnotationExistsCondition < Node
71
+ attr_reader :path
72
+
73
+ def initialize(path)
74
+ @path = path # String (e.g., "activity/status")
75
+ end
76
+
77
+ def accept(visitor)
78
+ visitor.visit_annotation_exists_condition(self)
79
+ end
80
+ end
81
+
82
+ # Annotation "in" condition: annotation_path in (value1, value2, ...)
83
+ class AnnotationInCondition < Node
84
+ attr_reader :path, :values
85
+
86
+ def initialize(path, values)
87
+ @path = path # String (e.g., "repository/artifacts")
88
+ @values = values # Array of StringValue
89
+ end
90
+
91
+ def accept(visitor)
92
+ visitor.visit_annotation_in_condition(self)
93
+ end
94
+ end
95
+
96
+ # Kind condition: kind == "SomeKind" or kind =~ "pattern"
97
+ class KindCondition < Node
98
+ attr_reader :operator, :value
99
+
100
+ def initialize(operator, value)
101
+ @operator = operator # String (==, =~)
102
+ @value = value # StringValue or RegexValue
103
+ end
104
+
105
+ def accept(visitor)
106
+ visitor.visit_kind_condition(self)
107
+ end
108
+ end
109
+
110
+ # Kind "in" condition: kind in ("Kind1", "Kind2", ...)
111
+ class KindInCondition < Node
112
+ attr_reader :values
113
+
114
+ def initialize(values)
115
+ @values = values # Array of StringValue
116
+ end
117
+
118
+ def accept(visitor)
119
+ visitor.visit_kind_in_condition(self)
120
+ end
121
+ end
122
+
123
+ # Name condition: name == "value" or name =~ "pattern"
124
+ class NameCondition < Node
125
+ attr_reader :operator, :value
126
+
127
+ def initialize(operator, value)
128
+ @operator = operator # String (==, !=, =~)
129
+ @value = value # StringValue or RegexValue
130
+ end
131
+
132
+ def accept(visitor)
133
+ visitor.visit_name_condition(self)
134
+ end
135
+ end
136
+
137
+ # Name "in" condition: name in ("name1", "name2", ...)
138
+ class NameInCondition < Node
139
+ attr_reader :values
140
+
141
+ def initialize(values)
142
+ @values = values # Array of StringValue
143
+ end
144
+
145
+ def accept(visitor)
146
+ visitor.visit_name_in_condition(self)
147
+ end
148
+ end
149
+
150
+ # Outgoing direct relation: -> Kind or -> "InstanceName"
151
+ # With optional verb filter: -{verb}> or -{!verb}>
152
+ class OutgoingDirectRelation < Node
153
+ attr_reader :target, :verbs, :exclude_verbs
154
+
155
+ def initialize(target, verbs = nil, exclude_verbs = false)
156
+ @target = target # KindTarget or InstanceTarget
157
+ @verbs = verbs # Array of verb strings, or nil for "all verbs"
158
+ @exclude_verbs = exclude_verbs # true = denylist, false = allowlist
159
+ end
160
+
161
+ def accept(visitor)
162
+ visitor.visit_outgoing_direct_relation(self)
163
+ end
164
+ end
165
+
166
+ # Outgoing transitive relation: ~> Kind or ~> "InstanceName"
167
+ # With optional verb filter: ~{verb}> or ~{!verb}>
168
+ class OutgoingTransitiveRelation < Node
169
+ attr_reader :target, :max_depth, :verbs, :exclude_verbs
170
+
171
+ def initialize(target, verbs = nil, exclude_verbs = false, max_depth = 10)
172
+ @target = target
173
+ @verbs = verbs # Array of verb strings, or nil for "all verbs"
174
+ @exclude_verbs = exclude_verbs # true = denylist, false = allowlist
175
+ @max_depth = max_depth
176
+ end
177
+
178
+ def accept(visitor)
179
+ visitor.visit_outgoing_transitive_relation(self)
180
+ end
181
+ end
182
+
183
+ # Incoming direct relation: <- Kind or <- "InstanceName"
184
+ # With optional verb filter: <{verb}- or <{!verb}-
185
+ class IncomingDirectRelation < Node
186
+ attr_reader :target, :verbs, :exclude_verbs
187
+
188
+ def initialize(target, verbs = nil, exclude_verbs = false)
189
+ @target = target # KindTarget or InstanceTarget
190
+ @verbs = verbs # Array of verb strings, or nil for "all verbs"
191
+ @exclude_verbs = exclude_verbs # true = denylist, false = allowlist
192
+ end
193
+
194
+ def accept(visitor)
195
+ visitor.visit_incoming_direct_relation(self)
196
+ end
197
+ end
198
+
199
+ # Incoming transitive relation: <~ Kind or <~ "InstanceName"
200
+ # With optional verb filter: <{verb}~ or <{!verb}~
201
+ class IncomingTransitiveRelation < Node
202
+ attr_reader :target, :max_depth, :verbs, :exclude_verbs
203
+
204
+ def initialize(target, verbs = nil, exclude_verbs = false, max_depth = 10)
205
+ @target = target
206
+ @verbs = verbs # Array of verb strings, or nil for "all verbs"
207
+ @exclude_verbs = exclude_verbs # true = denylist, false = allowlist
208
+ @max_depth = max_depth
209
+ end
210
+
211
+ def accept(visitor)
212
+ visitor.visit_incoming_transitive_relation(self)
213
+ end
214
+ end
215
+
216
+ # Relation target: a kind name (identifier)
217
+ class KindTarget
218
+ attr_reader :kind_name
219
+
220
+ def initialize(kind_name)
221
+ @kind_name = kind_name
222
+ end
223
+ end
224
+
225
+ # Relation target: a specific instance name (string)
226
+ class InstanceTarget
227
+ attr_reader :instance_name
228
+
229
+ def initialize(instance_name)
230
+ @instance_name = instance_name
231
+ end
232
+ end
233
+
234
+ # Relation target: nothing (no relations)
235
+ # Used with -> # (no outgoing) or <- # (no incoming)
236
+ class NothingTarget
237
+ end
238
+
239
+ # Relation target: sub-query result
240
+ # Used with -> $(expr) to match against query results
241
+ class SubqueryTarget
242
+ attr_reader :query
243
+
244
+ def initialize(query)
245
+ @query = query # QueryNode - the inner sub-query to evaluate
246
+ end
247
+ end
248
+
249
+ # Value types
250
+ class StringValue
251
+ attr_reader :value
252
+
253
+ def initialize(value)
254
+ @value = value
255
+ end
256
+ end
257
+
258
+ class NumberValue
259
+ attr_reader :value
260
+
261
+ def initialize(value)
262
+ @value = value.to_f
263
+ end
264
+ end
265
+
266
+ class RegexValue
267
+ attr_reader :pattern, :flags
268
+
269
+ def initialize(pattern, flags = "")
270
+ @pattern = pattern
271
+ @flags = flags
272
+ end
273
+
274
+ def to_regexp
275
+ options = @flags.include?("i") ? Regexp::IGNORECASE : 0
276
+ Regexp.new(@pattern, options)
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Base error class for all query-related errors
4
+ class Archsight::Query::QueryError < StandardError
5
+ attr_reader :position, :source
6
+
7
+ def initialize(message, position: nil, source: nil)
8
+ @position = position
9
+ @source = source
10
+ super(message)
11
+ end
12
+
13
+ def to_s
14
+ if @position && @source
15
+ line_info = extract_line_info
16
+ "#{super}\n#{line_info}"
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def extract_line_info
25
+ return "" unless @source && @position
26
+
27
+ pointer = "#{" " * @position}^"
28
+ " #{@source}\n #{pointer}"
29
+ end
30
+ end
31
+
32
+ # Error during lexical analysis (tokenization)
33
+ class Archsight::Query::LexerError < Archsight::Query::QueryError; end
34
+
35
+ # Error during parsing
36
+ class Archsight::Query::ParseError < Archsight::Query::QueryError; end
37
+
38
+ # Error during query evaluation
39
+ class Archsight::Query::EvaluationError < Archsight::Query::QueryError; end