uml-viewer 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.
@@ -0,0 +1,65 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>uml-viewer</title>
6
+ <style>
7
+ :root { --bg: #f7f7f4; --fg: #1d2021; --box: #ffffff; --line: #6b7075; --red: #c0392b; --muted: #8a8f94; --hl: #fff3c4; --grade-l: 86%; }
8
+ @media (prefers-color-scheme: dark) { :root { --bg: #1b1d1f; --fg: #e6e6e3; --box: #26292c; --line: #9aa0a6; --red: #ff6b5b; --muted: #7b8186; --hl: #4a4020; --grade-l: 30%; } }
9
+ [hidden] { display: none !important; }
10
+ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--fg); font: 13px/1.3 ui-monospace, Menlo, monospace; }
11
+ #bar { position: fixed; top: 0; left: 0; right: 0; display: flex; gap: 16px; align-items: center; padding: 8px 12px; background: var(--bg); border-bottom: 1px solid var(--line); z-index: 2; }
12
+ #crumbs { display: flex; gap: 4px; }
13
+ button.crumb { font: inherit; font-weight: bold; background: none; color: var(--fg); border: 0; padding: 0; cursor: pointer; }
14
+ button.crumb:disabled { cursor: default; }
15
+ button.crumb + button.crumb::before { content: "› "; color: var(--muted); font-weight: normal; }
16
+ #banner { color: var(--red); }
17
+ #banner:empty { display: none; }
18
+ svg { position: absolute; top: 36px; left: 0; }
19
+ g.box rect, g.box ellipse { fill: var(--box); stroke: var(--line); }
20
+ g.box.foreign ellipse { stroke-dasharray: 4 3; }
21
+ g.box.folder text.title { font-style: italic; }
22
+ g.box.component rect { stroke-width: 2; }
23
+ text { fill: var(--fg); }
24
+ text.level, text.more { fill: var(--muted); }
25
+ path.arrow { fill: none; stroke: var(--line); stroke-width: 1.2; marker-end: url(#head); }
26
+ path.arrow.violating { stroke: var(--red); marker-end: url(#head-red); }
27
+ path.arrow.both { marker-start: url(#head); }
28
+ path.arrow.both.violating { marker-start: url(#head-red); }
29
+ svg.triangles path.arrow { stroke: transparent; }
30
+ text.note { fill: var(--muted); }
31
+ #menu { position: fixed; display: none; flex-direction: column; background: var(--box); border: 1px solid var(--line); z-index: 3; }
32
+ #menu.open { display: flex; }
33
+ #menu button { font: inherit; background: none; color: var(--fg); border: 0; padding: 6px 14px; text-align: left; cursor: pointer; }
34
+ #toast { position: fixed; bottom: 12px; left: 12px; color: var(--red); }
35
+ #regen, #propose, #proposal, #declutter { font: inherit; background: none; color: var(--fg); border: 1px solid var(--line); border-radius: 3px; padding: 2px 8px; cursor: pointer; }
36
+ aside { position: fixed; top: 36px; bottom: 0; overflow: auto; background: var(--box); border-left: 1px solid var(--line); padding: 8px 12px; z-index: 2; }
37
+ aside h2 { font-size: 14px; margin: 4px 0 8px; }
38
+ #card { right: 0; width: 360px; }
39
+ #source { right: 385px; width: min(720px, 50vw); }
40
+ #card .error { color: var(--red); }
41
+ #card .note, #card .file { color: var(--muted); }
42
+ #card ul { padding: 0; }
43
+ li.op { list-style: none; cursor: pointer; white-space: pre; }
44
+ li.op:hover { text-decoration: underline; }
45
+ #source pre { margin: 0; }
46
+ .line { white-space: pre; }
47
+ .line.current { background: var(--hl); }
48
+ #popup { position: fixed; background: var(--box); border: 1px solid var(--line); padding: 6px 8px; z-index: 3; pointer-events: none; }
49
+ #popup .violating { color: var(--red); }
50
+ path.hit { fill: none; stroke: transparent; stroke-width: 10; }
51
+ circle.dot { stroke: var(--line); stroke-width: 0.5; }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <div id="bar"><span id="crumbs"></span><select id="proposal" aria-label="diagram"></select><button id="propose">New proposal</button><button id="regen">Regen</button><button id="declutter">declutter: none</button><span id="status"></span><span id="banner"></span></div>
56
+ <svg id="diagram"></svg>
57
+ <div id="menu"><button data-op="explain">Explain</button><button data-op="omit">Omit</button></div>
58
+ <div id="toast"></div>
59
+ <aside id="card" hidden></aside>
60
+ <aside id="source" hidden></aside>
61
+ <div id="popup" hidden></div>
62
+ <script src="/elk.js"></script>
63
+ <script src="/app.js"></script>
64
+ </body>
65
+ </html>
@@ -0,0 +1,75 @@
1
+ require "digest"
2
+ require "fileutils"
3
+ require "json"
4
+ require "socket"
5
+
6
+ module Uml
7
+ module Viewer
8
+ class Workspace
9
+ class MissingProject < StandardError; end
10
+
11
+ attr_reader :root, :dir
12
+
13
+ def self.home
14
+ File.expand_path(ENV.fetch("UML_VIEWER_HOME") { File.join(Dir.home, ".uml-viewer") })
15
+ end
16
+
17
+ def self.pid_alive?(pid)
18
+ pid = Integer(pid)
19
+ return false unless pid.positive?
20
+
21
+ Process.kill(0, pid)
22
+ true
23
+ rescue Errno::ESRCH, ArgumentError, TypeError
24
+ false
25
+ rescue Errno::EPERM
26
+ true
27
+ end
28
+
29
+ def self.port_open?(port)
30
+ Socket.tcp("127.0.0.1", Integer(port), connect_timeout: 0.5).close
31
+ true
32
+ rescue SystemCallError, SocketError, ArgumentError, TypeError
33
+ false
34
+ end
35
+
36
+ def initialize(project_path)
37
+ raise MissingProject, "no such project folder: #{project_path}" unless File.directory?(project_path)
38
+
39
+ @root = File.realpath(project_path)
40
+ digest = Digest::SHA256.hexdigest(@root)[0, 8]
41
+ @dir = File.join(self.class.home, "projects", "#{File.basename(@root)}-#{digest}")
42
+ FileUtils.mkdir_p(@dir, mode: 0o700)
43
+ File.chmod(0o700, @dir)
44
+ end
45
+
46
+ def path(name)
47
+ File.join(@dir, name)
48
+ end
49
+
50
+ def write_atomic(name, string)
51
+ target = path(name)
52
+ temp = "#{target}.#{Process.pid}.#{Thread.current.object_id}.tmp"
53
+ File.open(temp, File::WRONLY | File::CREAT | File::TRUNC, 0o600) { |file| file.write(string) }
54
+ File.rename(temp, target)
55
+ end
56
+
57
+ def read_json(name, default)
58
+ JSON.parse(File.read(path(name)))
59
+ rescue Errno::ENOENT
60
+ default
61
+ end
62
+
63
+ def server_info
64
+ read_json("server.json", nil)
65
+ rescue JSON::ParserError
66
+ nil
67
+ end
68
+
69
+ def server_alive?
70
+ info = server_info
71
+ info.is_a?(Hash) && self.class.pid_alive?(info["pid"]) && self.class.port_open?(info["port"])
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/uml/viewer.rb ADDED
@@ -0,0 +1,23 @@
1
+ require_relative "viewer/version"
2
+ require_relative "viewer/workspace"
3
+ require_relative "viewer/resolver"
4
+ require_relative "viewer/complexity"
5
+ require_relative "viewer/operations"
6
+ require_relative "viewer/inflector"
7
+ require_relative "viewer/scanner"
8
+ require_relative "viewer/policy_file"
9
+ require_relative "viewer/policy_init"
10
+ require_relative "viewer/policy"
11
+ require_relative "viewer/views"
12
+ require_relative "viewer/proposals"
13
+ require_relative "viewer/coverage"
14
+ require_relative "viewer/overlay"
15
+ require_relative "viewer/graph"
16
+ require_relative "viewer/mailbox"
17
+ require_relative "viewer/watcher"
18
+ require_relative "viewer/commands"
19
+ require_relative "viewer/rescanner"
20
+ require_relative "viewer/server"
21
+ require_relative "viewer/skill_installer"
22
+ require_relative "viewer/smoke"
23
+ require_relative "viewer/cli"
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uml-viewer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Hopman
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: prism
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.9'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '1.9'
26
+ - !ruby/object:Gem::Dependency
27
+ name: webrick
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.9'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.9'
40
+ description: uml-viewer scans a Ruby or Rails project with Prism and shows it as a
41
+ UML diagram in the browser. Red arrows show dependencies that break the Dependency
42
+ Rule between Rails layers. Box colors show CRAP from a SimpleCov run. A right-click
43
+ sends a command to a Claude Code session, which explains the code, changes the policy
44
+ or writes a proposal. Nothing is written into the viewed project.
45
+ executables:
46
+ - uml-viewer
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - LICENSE
52
+ - README.md
53
+ - exe/uml-viewer
54
+ - lib/uml/viewer.rb
55
+ - lib/uml/viewer/cli.rb
56
+ - lib/uml/viewer/commands.rb
57
+ - lib/uml/viewer/complexity.rb
58
+ - lib/uml/viewer/coverage.rb
59
+ - lib/uml/viewer/graph.rb
60
+ - lib/uml/viewer/inflector.rb
61
+ - lib/uml/viewer/mailbox.rb
62
+ - lib/uml/viewer/operations.rb
63
+ - lib/uml/viewer/overlay.rb
64
+ - lib/uml/viewer/policy.rb
65
+ - lib/uml/viewer/policy_file.rb
66
+ - lib/uml/viewer/policy_init.rb
67
+ - lib/uml/viewer/proposals.rb
68
+ - lib/uml/viewer/rescanner.rb
69
+ - lib/uml/viewer/resolver.rb
70
+ - lib/uml/viewer/scanner.rb
71
+ - lib/uml/viewer/server.rb
72
+ - lib/uml/viewer/skill/SKILL.md.erb
73
+ - lib/uml/viewer/skill_installer.rb
74
+ - lib/uml/viewer/smoke.rb
75
+ - lib/uml/viewer/version.rb
76
+ - lib/uml/viewer/views.rb
77
+ - lib/uml/viewer/watcher.rb
78
+ - lib/uml/viewer/web/LICENSE-EPL-2.0
79
+ - lib/uml/viewer/web/app.js
80
+ - lib/uml/viewer/web/elk.bundled.js
81
+ - lib/uml/viewer/web/index.html
82
+ - lib/uml/viewer/workspace.rb
83
+ homepage: https://github.com/roberthopman/uml-viewer
84
+ licenses:
85
+ - MIT
86
+ - EPL-2.0
87
+ metadata:
88
+ source_code_uri: https://github.com/roberthopman/uml-viewer
89
+ changelog_uri: https://github.com/roberthopman/uml-viewer/blob/main/CHANGELOG.md
90
+ allowed_push_host: https://rubygems.org
91
+ rubygems_mfa_required: 'true'
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '3.3'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 4.0.16
107
+ specification_version: 4
108
+ summary: A live UML diagram of a Ruby or Rails project in the browser, with a Claude
109
+ Code agent loop.
110
+ test_files: []