archsight 0.1.2 → 0.1.3

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -5
  3. data/lib/archsight/analysis/executor.rb +112 -0
  4. data/lib/archsight/analysis/result.rb +174 -0
  5. data/lib/archsight/analysis/sandbox.rb +319 -0
  6. data/lib/archsight/analysis.rb +11 -0
  7. data/lib/archsight/annotations/architecture_annotations.rb +2 -2
  8. data/lib/archsight/cli.rb +163 -0
  9. data/lib/archsight/database.rb +6 -2
  10. data/lib/archsight/helpers/analysis_renderer.rb +83 -0
  11. data/lib/archsight/helpers/formatting.rb +95 -0
  12. data/lib/archsight/helpers.rb +20 -4
  13. data/lib/archsight/import/concurrent_progress.rb +341 -0
  14. data/lib/archsight/import/executor.rb +466 -0
  15. data/lib/archsight/import/git_analytics.rb +626 -0
  16. data/lib/archsight/import/handler.rb +263 -0
  17. data/lib/archsight/import/handlers/github.rb +161 -0
  18. data/lib/archsight/import/handlers/gitlab.rb +202 -0
  19. data/lib/archsight/import/handlers/jira_base.rb +189 -0
  20. data/lib/archsight/import/handlers/jira_discover.rb +161 -0
  21. data/lib/archsight/import/handlers/jira_metrics.rb +179 -0
  22. data/lib/archsight/import/handlers/openapi_schema_parser.rb +279 -0
  23. data/lib/archsight/import/handlers/repository.rb +439 -0
  24. data/lib/archsight/import/handlers/rest_api.rb +293 -0
  25. data/lib/archsight/import/handlers/rest_api_index.rb +183 -0
  26. data/lib/archsight/import/progress.rb +91 -0
  27. data/lib/archsight/import/registry.rb +54 -0
  28. data/lib/archsight/import/shared_file_writer.rb +67 -0
  29. data/lib/archsight/import/team_matcher.rb +195 -0
  30. data/lib/archsight/import.rb +14 -0
  31. data/lib/archsight/resources/analysis.rb +91 -0
  32. data/lib/archsight/resources/application_component.rb +2 -2
  33. data/lib/archsight/resources/application_service.rb +12 -12
  34. data/lib/archsight/resources/business_product.rb +12 -12
  35. data/lib/archsight/resources/data_object.rb +1 -1
  36. data/lib/archsight/resources/import.rb +79 -0
  37. data/lib/archsight/resources/technology_artifact.rb +23 -2
  38. data/lib/archsight/version.rb +1 -1
  39. data/lib/archsight/web/api/docs.rb +17 -0
  40. data/lib/archsight/web/api/json_helpers.rb +164 -0
  41. data/lib/archsight/web/api/openapi/spec.yaml +500 -0
  42. data/lib/archsight/web/api/routes.rb +101 -0
  43. data/lib/archsight/web/application.rb +66 -43
  44. data/lib/archsight/web/doc/import.md +458 -0
  45. data/lib/archsight/web/doc/index.md.erb +1 -0
  46. data/lib/archsight/web/public/css/artifact.css +10 -0
  47. data/lib/archsight/web/public/css/graph.css +14 -0
  48. data/lib/archsight/web/public/css/instance.css +489 -0
  49. data/lib/archsight/web/views/api_docs.erb +19 -0
  50. data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +14 -8
  51. data/lib/archsight/web/views/partials/instance/_analysis_detail.haml +74 -0
  52. data/lib/archsight/web/views/partials/instance/_analysis_result.haml +64 -0
  53. data/lib/archsight/web/views/partials/instance/_detail.haml +7 -3
  54. data/lib/archsight/web/views/partials/instance/_import_detail.haml +87 -0
  55. data/lib/archsight/web/views/partials/instance/_relations.haml +4 -4
  56. data/lib/archsight/web/views/partials/layout/_content.haml +4 -0
  57. data/lib/archsight/web/views/partials/layout/_navigation.haml +6 -5
  58. metadata +78 -1
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra/base"
4
+ require "sinatra/extension"
5
+ require_relative "json_helpers"
6
+
7
+ module Archsight; end
8
+ module Archsight::Web; end
9
+ module Archsight::Web::API; end
10
+
11
+ # REST API routes for Archsight
12
+ module Archsight::Web::API::Routes
13
+ extend Sinatra::Extension
14
+
15
+ helpers Archsight::Web::API::JsonHelpers
16
+
17
+ # GET /api/v1/kinds - List all resource kinds with counts
18
+ get "/api/v1/kinds" do
19
+ kinds = build_kinds_list
20
+ total = kinds.sum { |k| k[:instance_count] }
21
+
22
+ json_response(
23
+ { total: kinds.length, total_instances: total, kinds: kinds }
24
+ )
25
+ end
26
+
27
+ # GET /api/v1/kinds/:kind - List instances of a kind (paginated)
28
+ get "/api/v1/kinds/:kind" do
29
+ kind = params[:kind]
30
+ klass = Archsight::Resources[kind]
31
+ json_error("Kind '#{kind}' not found", status: 404, error_type: "NotFound") unless klass
32
+
33
+ instances = db.instances_by_kind(kind).values.sort_by(&:name)
34
+ limit, offset = parse_pagination_params
35
+ output = parse_output_param
36
+ pagination = paginate(instances, limit: limit, offset: offset)
37
+
38
+ resources = pagination[:items].map do |inst|
39
+ resource_summary(inst, output: output, omit_kind: true)
40
+ end
41
+
42
+ json_response(build_list_response(kind, pagination, resources))
43
+ end
44
+
45
+ # GET /api/v1/kinds/:kind/instances/:name - Get instance details with relations
46
+ get "/api/v1/kinds/:kind/instances/:name" do
47
+ kind = params[:kind]
48
+ name = params[:name]
49
+
50
+ klass = Archsight::Resources[kind]
51
+ json_error("Kind '#{kind}' not found", status: 404, error_type: "NotFound") unless klass
52
+
53
+ instance = db.instance_by_kind(kind, name)
54
+ json_error("Instance '#{name}' not found", status: 404, error_type: "NotFound") unless instance
55
+
56
+ json_response(build_instance_response(kind, instance))
57
+ end
58
+
59
+ # GET /api/v1/search - Search with query language
60
+ get "/api/v1/search" do
61
+ query = params[:q]
62
+ json_error("Query parameter 'q' is required", status: 400, error_type: "BadRequest") unless query
63
+
64
+ start_time = Time.now
65
+
66
+ begin
67
+ parsed_query = Archsight::Query.parse(query)
68
+ results = parsed_query.filter(db)
69
+ query_time_ms = ((Time.now - start_time) * 1000).round(2)
70
+ output = parse_output_param
71
+
72
+ if output == "count"
73
+ json_response(build_count_response(query, results, query_time_ms))
74
+ else
75
+ json_response(build_search_response(query, results, parsed_query, query_time_ms))
76
+ end
77
+ rescue Archsight::Query::QueryError => e
78
+ json_error(e.message, status: 400, error_type: "QueryError", query: query)
79
+ end
80
+ end
81
+
82
+ # GET /api/v1/openapi.yaml - OpenAPI specification
83
+ get "/api/v1/openapi.yaml" do
84
+ content_type "text/yaml"
85
+ spec_path = File.join(__dir__, "openapi", "spec.yaml")
86
+ File.read(spec_path)
87
+ end
88
+
89
+ # Convenience aliases with .json suffix
90
+ get "/kinds.json" do
91
+ call env.merge("PATH_INFO" => "/api/v1/kinds")
92
+ end
93
+
94
+ get "/kinds/:kind.json" do
95
+ call env.merge("PATH_INFO" => "/api/v1/kinds/#{params[:kind]}")
96
+ end
97
+
98
+ get "/kinds/:kind/instances/:name.json" do
99
+ call env.merge("PATH_INFO" => "/api/v1/kinds/#{params[:kind]}/instances/#{params[:name]}")
100
+ end
101
+ end
@@ -14,6 +14,8 @@ require_relative "../documentation"
14
14
  require_relative "../query"
15
15
  require_relative "../resources"
16
16
  require_relative "../mcp"
17
+ require_relative "api/routes"
18
+ require_relative "api/docs"
17
19
 
18
20
  # Define the Web namespace before the class definition
19
21
  module Archsight::Web; end
@@ -33,6 +35,22 @@ class Archsight::Web::Application < Sinatra::Base
33
35
  dur = (Time.new - start) * 1000
34
36
  puts format("== done %0.2f ms", dur) if database.verbose
35
37
  end
38
+
39
+ # Configure application for the given environment
40
+ # @param env [Symbol] :development or :production
41
+ # @param logging [Boolean, nil] Override default logging setting
42
+ def configure_environment!(env, logging: nil)
43
+ set :environment, env
44
+
45
+ if env == :production
46
+ set :quiet, true
47
+ set :server_settings, { Silent: true }
48
+ end
49
+
50
+ # Determine logging: CLI override > env default (prod=true, dev=false)
51
+ enable_logging = logging.nil? ? (env == :production) : logging
52
+ use Rack::CommonLogger, $stdout if enable_logging
53
+ end
36
54
  end
37
55
 
38
56
  configure do
@@ -40,6 +58,7 @@ class Archsight::Web::Application < Sinatra::Base
40
58
  set :public_folder, File.join(__dir__, "public")
41
59
  set :haml, format: :html5
42
60
  set :server, :puma
61
+ set :reload_enabled, true
43
62
  end
44
63
 
45
64
  # MCP Server setup
@@ -63,11 +82,27 @@ class Archsight::Web::Application < Sinatra::Base
63
82
 
64
83
  helpers Archsight::GraphvisHelper, Archsight::GraphvisRenderer, Archsight::Helpers
65
84
 
85
+ # Register API modules
86
+ register Archsight::Web::API::Routes
87
+ register Archsight::Web::API::Docs
88
+
66
89
  helpers do
67
90
  def db
68
91
  Archsight::Web::Application.database
69
92
  end
70
93
 
94
+ def reload_enabled?
95
+ settings.reload_enabled
96
+ end
97
+
98
+ def production?
99
+ settings.environment == :production
100
+ end
101
+
102
+ def development?
103
+ settings.environment == :development
104
+ end
105
+
71
106
  # Render markdown to HTML with optional URL resolution for repository content
72
107
  # @param data [String] Markdown content
73
108
  # @param git_url [String, nil] Git URL for resolving relative paths (e.g., for README images)
@@ -98,25 +133,6 @@ class Archsight::Web::Application < Sinatra::Base
98
133
  end
99
134
  end
100
135
 
101
- def to_dollar(num)
102
- # Round to 2 decimals first (important for floating‑point edge cases)
103
- rounded = (num * 100).round / 100.0
104
- # Insert commas every three digits left of the decimal point
105
- parts = format("%.2f", rounded).split(".")
106
- parts[0] = parts[0].reverse.scan(/\d{1,3}/).join(",").reverse
107
- "$#{parts.join(".")}"
108
- end
109
-
110
- def http_git(repo_url)
111
- repo_url.gsub(/.git$/, "")
112
- .gsub(":", "/")
113
- .gsub("git@", "https://")
114
- end
115
-
116
- def number_with_delimiter(num)
117
- num.to_s.reverse.scan(/\d{1,3}/).join(",").reverse
118
- end
119
-
120
136
  # Generate asset path with cache-busting query string based on file mtime
121
137
  def asset_path(path)
122
138
  file_path = File.join(settings.public_folder, path)
@@ -128,30 +144,9 @@ class Archsight::Web::Application < Sinatra::Base
128
144
  end
129
145
  end
130
146
 
131
- # Convert timestamp to human-readable relative time
132
- def time_ago(timestamp)
133
- return nil unless timestamp
134
-
135
- time = timestamp.is_a?(Time) ? timestamp : Time.parse(timestamp.to_s)
136
- seconds = (Time.now - time).to_i
137
-
138
- units = [
139
- [60, "second"],
140
- [60, "minute"],
141
- [24, "hour"],
142
- [7, "day"],
143
- [4, "week"],
144
- [12, "month"],
145
- [Float::INFINITY, "year"]
146
- ]
147
-
148
- value = seconds
149
- units.each do |divisor, unit|
150
- return "just now" if unit == "second" && value < 10
151
- return "#{value} #{unit}#{"s" if value != 1} ago" if value < divisor
152
-
153
- value /= divisor
154
- end
147
+ # Wrapper for render_analysis_section that provides markdown renderer
148
+ def render_analysis_section(section)
149
+ Archsight::Helpers.render_analysis_section(section, markdown_renderer: method(:markdown))
155
150
  end
156
151
  end
157
152
 
@@ -160,6 +155,8 @@ class Archsight::Web::Application < Sinatra::Base
160
155
  end
161
156
 
162
157
  get "/reload" do
158
+ halt 404, "Reload is disabled" unless settings.reload_enabled
159
+
163
160
  Archsight::Web::Application.reload!
164
161
  if params["redirect"]&.start_with?("/")
165
162
  redirect params["redirect"]
@@ -289,4 +286,30 @@ class Archsight::Web::Application < Sinatra::Base
289
286
  content_type "text/plain"
290
287
  create_graph_one(db, @kind, @instance, :draw_dot)
291
288
  end
289
+
290
+ # Execute an Analysis and return HTML results
291
+ post "/kinds/Analysis/instances/:instance/execute" do
292
+ require "archsight/analysis"
293
+
294
+ @instance = params["instance"]
295
+ analysis = db.instance_by_kind("Analysis", @instance)
296
+
297
+ unless analysis
298
+ return haml_inline('.analysis-error
299
+ %i.iconoir-warning-triangle
300
+ Analysis not found: #{@instance}')
301
+ end
302
+
303
+ executor = Archsight::Analysis::Executor.new(db)
304
+ result = executor.execute(analysis)
305
+
306
+ haml :"partials/instance/_analysis_result", locals: { result: result }
307
+ end
308
+
309
+ private
310
+
311
+ # Helper for inline HAML rendering
312
+ def haml_inline(template)
313
+ haml template
314
+ end
292
315
  end