ruth-code 0.1.0__py3-none-any.whl

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 (102) hide show
  1. frontend/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff +0 -0
  2. frontend/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2 +0 -0
  3. frontend/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2 +0 -0
  4. frontend/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff +0 -0
  5. frontend/dist/assets/geist-mono-cyrillic-600-normal-CGND36d7.woff2 +0 -0
  6. frontend/dist/assets/geist-mono-cyrillic-600-normal-DrylrLu6.woff +0 -0
  7. frontend/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff +0 -0
  8. frontend/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2 +0 -0
  9. frontend/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff +0 -0
  10. frontend/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2 +0 -0
  11. frontend/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2 +0 -0
  12. frontend/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff +0 -0
  13. frontend/dist/assets/geist-mono-latin-600-normal-DQQBcVN0.woff2 +0 -0
  14. frontend/dist/assets/geist-mono-latin-600-normal-DsVeri3b.woff +0 -0
  15. frontend/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2 +0 -0
  16. frontend/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff +0 -0
  17. frontend/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2 +0 -0
  18. frontend/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff +0 -0
  19. frontend/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2 +0 -0
  20. frontend/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff +0 -0
  21. frontend/dist/assets/geist-mono-latin-ext-600-normal-CJwYYto2.woff2 +0 -0
  22. frontend/dist/assets/geist-mono-latin-ext-600-normal-EvIRCXgu.woff +0 -0
  23. frontend/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff +0 -0
  24. frontend/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2 +0 -0
  25. frontend/dist/assets/index-AEO_WTHY.js +59 -0
  26. frontend/dist/assets/index-JUssvikZ.css +1 -0
  27. frontend/dist/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  28. frontend/dist/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  29. frontend/dist/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  30. frontend/dist/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  31. frontend/dist/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  32. frontend/dist/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  33. frontend/dist/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
  34. frontend/dist/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
  35. frontend/dist/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  36. frontend/dist/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  37. frontend/dist/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  38. frontend/dist/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  39. frontend/dist/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  40. frontend/dist/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  41. frontend/dist/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
  42. frontend/dist/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
  43. frontend/dist/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  44. frontend/dist/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  45. frontend/dist/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  46. frontend/dist/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  47. frontend/dist/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  48. frontend/dist/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  49. frontend/dist/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
  50. frontend/dist/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
  51. frontend/dist/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  52. frontend/dist/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  53. frontend/dist/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  54. frontend/dist/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  55. frontend/dist/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  56. frontend/dist/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  57. frontend/dist/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
  58. frontend/dist/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
  59. frontend/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  60. frontend/dist/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  61. frontend/dist/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  62. frontend/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  63. frontend/dist/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  64. frontend/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  65. frontend/dist/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
  66. frontend/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  67. frontend/dist/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  68. frontend/dist/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  69. frontend/dist/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  70. frontend/dist/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  71. frontend/dist/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  72. frontend/dist/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  73. frontend/dist/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
  74. frontend/dist/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
  75. frontend/dist/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  76. frontend/dist/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  77. frontend/dist/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  78. frontend/dist/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  79. frontend/dist/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  80. frontend/dist/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  81. frontend/dist/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
  82. frontend/dist/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
  83. frontend/dist/favicon.svg +1 -0
  84. frontend/dist/icons.svg +24 -0
  85. frontend/dist/index.html +15 -0
  86. frontend/dist/logo.svg +1 -0
  87. ruth/__init__.py +3 -0
  88. ruth/annotations/__init__.py +1 -0
  89. ruth/annotations/complexity.py +128 -0
  90. ruth/annotations/coverage.py +106 -0
  91. ruth/cli.py +167 -0
  92. ruth/graph/__init__.py +1 -0
  93. ruth/graph/engine.py +383 -0
  94. ruth/parser/__init__.py +1 -0
  95. ruth/parser/discovery.py +226 -0
  96. ruth/parser/symbols.py +656 -0
  97. ruth/server.py +162 -0
  98. ruth_code-0.1.0.dist-info/METADATA +106 -0
  99. ruth_code-0.1.0.dist-info/RECORD +102 -0
  100. ruth_code-0.1.0.dist-info/WHEEL +4 -0
  101. ruth_code-0.1.0.dist-info/entry_points.txt +2 -0
  102. ruth_code-0.1.0.dist-info/licenses/LICENSE +21 -0
ruth/server.py ADDED
@@ -0,0 +1,162 @@
1
+ """Ruth FastAPI server — serves the React frontend and WebSocket API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import asyncio
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
11
+ from fastapi.staticfiles import StaticFiles
12
+ from fastapi.responses import FileResponse, HTMLResponse
13
+
14
+
15
+ # Path to the built frontend assets
16
+ FRONTEND_DIR = Path(__file__).parent.parent / "frontend" / "dist"
17
+
18
+
19
+ class ConnectionManager:
20
+ """Manages active WebSocket connections and broadcasts graph updates."""
21
+
22
+ def __init__(self):
23
+ self.active_connections: list[WebSocket] = []
24
+
25
+ async def connect(self, websocket: WebSocket):
26
+ await websocket.accept()
27
+ self.active_connections.append(websocket)
28
+
29
+ def disconnect(self, websocket: WebSocket):
30
+ if websocket in self.active_connections:
31
+ self.active_connections.remove(websocket)
32
+
33
+ async def send_message(self, websocket: WebSocket, message: dict[str, Any]):
34
+ await websocket.send_json(message)
35
+
36
+ async def broadcast(self, message: dict[str, Any]):
37
+ for connection in self.active_connections:
38
+ try:
39
+ await connection.send_json(message)
40
+ except Exception:
41
+ pass
42
+
43
+
44
+ manager = ConnectionManager()
45
+
46
+
47
+ def _build_graph(project_root: Path) -> dict[str, Any]:
48
+ """Build the full code graph by running the parser → graph → annotation pipeline."""
49
+ from ruth.parser.discovery import discover_files
50
+ from ruth.graph.engine import build_graph
51
+ from ruth.annotations.coverage import load_coverage
52
+
53
+ # Phase 1: Discover files
54
+ discovery = discover_files(project_root)
55
+
56
+ # Phase 2: Build graph from discovered files
57
+ graph = build_graph(discovery, project_root, granularity="module")
58
+
59
+ # Phase 3: Overlay coverage data if available
60
+ coverage_data = load_coverage(project_root)
61
+ if coverage_data:
62
+ for node in graph["nodes"]:
63
+ rel_path = node["data"]["filePath"]
64
+ if rel_path in coverage_data:
65
+ node["data"]["annotations"]["coverage"] = coverage_data[rel_path]
66
+
67
+ return graph
68
+
69
+
70
+ def create_app(project_path: str = ".") -> FastAPI:
71
+ """Create the FastAPI application with WebSocket and static file serving."""
72
+
73
+ app = FastAPI(
74
+ title="Ruth",
75
+ description="Interactive Codebase Topology Visualizer",
76
+ version="0.1.0",
77
+ )
78
+
79
+ project_root = Path(project_path).resolve()
80
+
81
+ # Cache the graph to avoid re-parsing on every request
82
+ _graph_cache: dict[str, Any] = {}
83
+
84
+ def get_graph(force_refresh: bool = False) -> dict[str, Any]:
85
+ if force_refresh or not _graph_cache:
86
+ result = _build_graph(project_root)
87
+ _graph_cache.clear()
88
+ _graph_cache.update(result)
89
+ return _graph_cache
90
+
91
+ @app.websocket("/ws")
92
+ async def websocket_endpoint(websocket: WebSocket):
93
+ await manager.connect(websocket)
94
+ try:
95
+ # Send initial full graph on connection
96
+ graph = get_graph(force_refresh=True)
97
+ await manager.send_message(websocket, {
98
+ "type": "full_graph",
99
+ "payload": graph,
100
+ })
101
+
102
+ # Keep connection alive and listen for client messages
103
+ while True:
104
+ data = await websocket.receive_text()
105
+ msg = json.loads(data)
106
+
107
+ if msg.get("type") == "refresh":
108
+ graph = get_graph(force_refresh=True)
109
+ await manager.send_message(websocket, {
110
+ "type": "full_graph",
111
+ "payload": graph,
112
+ })
113
+
114
+ except WebSocketDisconnect:
115
+ manager.disconnect(websocket)
116
+ except Exception as e:
117
+ try:
118
+ await manager.send_message(websocket, {
119
+ "type": "error",
120
+ "payload": {"message": str(e)},
121
+ })
122
+ except Exception:
123
+ pass
124
+ manager.disconnect(websocket)
125
+
126
+ @app.get("/api/health")
127
+ async def health():
128
+ return {
129
+ "status": "ok",
130
+ "project": project_root.name,
131
+ "connections": len(manager.active_connections),
132
+ }
133
+
134
+ @app.get("/api/graph")
135
+ async def get_graph_api():
136
+ return get_graph()
137
+
138
+ # Serve the built React frontend
139
+ if FRONTEND_DIR.exists():
140
+ # Serve static assets (JS, CSS, etc.)
141
+ app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets")
142
+
143
+ @app.get("/{full_path:path}")
144
+ async def serve_spa(full_path: str):
145
+ """Serve the SPA — all non-API routes return index.html."""
146
+ file_path = FRONTEND_DIR / full_path
147
+ if file_path.is_file():
148
+ return FileResponse(file_path)
149
+ return FileResponse(FRONTEND_DIR / "index.html")
150
+ else:
151
+ @app.get("/")
152
+ async def no_frontend():
153
+ return HTMLResponse(
154
+ "<html><body style='background:#07070d;color:#e8e8f0;font-family:sans-serif;"
155
+ "display:flex;align-items:center;justify-content:center;height:100vh'>"
156
+ "<div style='text-align:center'>"
157
+ "<h1 style='color:#2dd4bf'>◈ Ruth</h1>"
158
+ "<p>Frontend not built. Run <code>npm run build</code> in the frontend/ directory.</p>"
159
+ "</div></body></html>"
160
+ )
161
+
162
+ return app
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: ruth-code
3
+ Version: 0.1.0
4
+ Summary: Interactive codebase topology visualizer — Google Maps for your repo
5
+ Project-URL: Homepage, https://github.com/kossisoroyce/ruth
6
+ Project-URL: Repository, https://github.com/kossisoroyce/ruth
7
+ Project-URL: Issues, https://github.com/kossisoroyce/ruth/issues
8
+ Author: Electric Sheep Africa
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: architecture,code-analysis,code-quality,codebase,dependency-graph,developer-tools,topology,visualization
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Software Development :: Quality Assurance
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: click>=8.1.0
27
+ Requires-Dist: fastapi>=0.110.0
28
+ Requires-Dist: rich>=13.0
29
+ Requires-Dist: tree-sitter>=0.21.0
30
+ Requires-Dist: uvicorn[standard]>=0.27.0
31
+ Requires-Dist: watchfiles>=0.21.0
32
+ Requires-Dist: websockets>=12.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.3.0; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Ruth
39
+
40
+ **Google Maps for your codebase.**
41
+
42
+ Ruth parses your codebase, builds a dependency graph, detects landmarks (entry points, orchestrators, hubs), overlays code quality metrics, and renders it as an explorable visual map in the browser.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install ruth-code
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```bash
53
+ ruth serve ./your-project
54
+ ```
55
+
56
+ Open `http://localhost:4150` and explore.
57
+
58
+ ## What You Get
59
+
60
+ **Landmarks & Points of Interest** — Ruth auto-detects the key files in your codebase:
61
+ - **Entry Points** — `main.py`, `index.ts`, `cli.py` — where execution begins
62
+ - **Orchestrators** — files that import many modules, wiring your app together
63
+ - **Hubs** — core dependencies imported by everyone
64
+ - **Islands** — disconnected files that may be dead code
65
+
66
+ **Dependency Path Tracing** — Click two nodes to trace the import chain between them, like getting directions on a map.
67
+
68
+ **Traffic Lanes** — Import edges scale in thickness based on how heavily two files are connected.
69
+
70
+ **Overlays** — Switch between views like map layers:
71
+ - **Complexity** — cyclomatic complexity heatmap (green to red)
72
+ - **Security** — vulnerability overlay (Semgrep/OSV integration)
73
+ - **Coverage** — test coverage mapping (lcov/coverage.json)
74
+
75
+ **Multi-Language** — Python, TypeScript, JavaScript, Rust, Go, Java, Ruby, C/C++.
76
+
77
+ **Live** — WebSocket-powered real-time updates. Edit a file, see the graph change.
78
+
79
+ ## Commands
80
+
81
+ ```bash
82
+ # Start the visualization dashboard
83
+ ruth serve /path/to/project
84
+
85
+ # Analyze and output graph as JSON
86
+ ruth analyze /path/to/project -o graph.json
87
+
88
+ # Quick project scan
89
+ ruth scan /path/to/project
90
+ ```
91
+
92
+ ## Keyboard Shortcuts
93
+
94
+ | Key | Action |
95
+ |-----|--------|
96
+ | `Cmd+K` | Command palette — search and jump to any node |
97
+ | `/` | Focus search filter |
98
+
99
+ ## Requirements
100
+
101
+ - Python 3.10+
102
+ - Node.js (for development only — the frontend is pre-built)
103
+
104
+ ## License
105
+
106
+ MIT — Electric Sheep Africa
@@ -0,0 +1,102 @@
1
+ ruth/__init__.py,sha256=ADsEjmcD2_bq8Q7JLPxzEIoiukHqYlh9Xg0Am2QgAj0,80
2
+ ruth/cli.py,sha256=2UicJIPWPMBNskfYQrZUnSVNbduY9oLZTv4UPIzvxcI,6165
3
+ ruth/server.py,sha256=M6I-rj1aA5VCi-ibplJG8U_Gm4T5dRUHcVAZtibpIjw,5479
4
+ ruth/annotations/__init__.py,sha256=8RItmI9XfNL5gT0rKK60NjNKXhBJBWhmYJdxAhtprh0,89
5
+ ruth/annotations/complexity.py,sha256=-ByKJI_GOLO8f93jg9oZMJUlT59R0_5VB1yEnsfnEf8,3924
6
+ ruth/annotations/coverage.py,sha256=Lz8ID1CSyOcG5jH4sJo7qgMgrpuZwfAcLIXWO-kGaI8,3122
7
+ ruth/graph/__init__.py,sha256=9gbE3xk8dsq9ds9sLt7sI2LyK5XS6GLTLfvhhs0OlvU,80
8
+ ruth/graph/engine.py,sha256=w9tPrlyxvV6iERe1AnAAH5vyLn5qKcIhbGhFH946ghI,14903
9
+ ruth/parser/__init__.py,sha256=tEWo5apUg6FbM3VW8fl_R2DwCU0ETPPBH_wamrRm8hU,82
10
+ ruth/parser/discovery.py,sha256=4k96rMCe7BnPi_-xU2NhkaBS1XBVB0Ed1Ralnzj4LbM,6533
11
+ ruth/parser/symbols.py,sha256=K1sIr7CJJin7Q5fvWDUkJV9ZxUNxbJh1rW20KmBZRmA,25821
12
+ frontend/dist/favicon.svg,sha256=YbyaFh3lgkgojmkFQl1xgPBiTChlAHuX12P9rBIEOmY,9522
13
+ frontend/dist/icons.svg,sha256=tF-lBhlc_N70BrqfDHezbdwafCJAQJJuxwq8L96nuTo,5031
14
+ frontend/dist/index.html,sha256=MsYEQTkvVTXR9KSMlSdzGDeU-BsuscTJLeWDnCaFcOI,676
15
+ frontend/dist/logo.svg,sha256=HYlVpsdhdNoCa2KyPaPgGtJqL-Jc7iPS4TZeZxabg7c,992958
16
+ frontend/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff,sha256=npeQXwZj6rknvKpytOW4RL-pgK2j-JT_kgUux-0tjAM,7164
17
+ frontend/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2,sha256=JZtj2aLc30Sbc_RmJOQJ3h1ipRE06ITT9K8SQnNs3hA,5460
18
+ frontend/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2,sha256=dljAM01TXLFrh6SxHBa7OzP72_szFgiQJOS2OW0giNg,5664
19
+ frontend/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff,sha256=hhBzau5iR6o4L-WJkWpoQGafhFwKnCx1MJyo3iZfzlo,7352
20
+ frontend/dist/assets/geist-mono-cyrillic-600-normal-CGND36d7.woff2,sha256=buXbsAa-lEHAyYgLInFJXGKN8OJwSc4X7Owgc4zUtwk,5680
21
+ frontend/dist/assets/geist-mono-cyrillic-600-normal-DrylrLu6.woff,sha256=GEQI9Ec1cPqIG36617YCpwHDulf5qEbWcMZsDcg93s0,7356
22
+ frontend/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff,sha256=KaexsnbbEQbVSw1mGupCd4sm8nFnjUgZ_I0LoQ3tmAY,7328
23
+ frontend/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2,sha256=ouPOBzfukA3I_l6knvkjqLShYY7qhwMbiVBFxixXszo,5792
24
+ frontend/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff,sha256=ryoG_GlAq_81MFqWzk4vacKTQQYzm0iLjLhcpvE23_8,19168
25
+ frontend/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2,sha256=YW43ylmir-vjBnUXc6U4uT9aLZqQ_T_JfOczIcuTr1E,14712
26
+ frontend/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2,sha256=mklHZPh8LhHSQAG5EGAL0hkiMFVMCQoN3GboIhsy884,15304
27
+ frontend/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff,sha256=8btiwq_E0RlxzhQhXvsSsBL2zUKlcBYZNfshNujc4m4,19640
28
+ frontend/dist/assets/geist-mono-latin-600-normal-DQQBcVN0.woff2,sha256=iLS5tI4HRnwkZjCWOaReYu0-PeIqSO5kDQR1ITdOPXw,15264
29
+ frontend/dist/assets/geist-mono-latin-600-normal-DsVeri3b.woff,sha256=FKxmaTc2TrEf4upF7mEQxYU0b1SwLwnKQvim7S72i5Q,19608
30
+ frontend/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2,sha256=6ya9no9ryfW2y3aSoEbaQ_Em7ds6CAM1YuprbfHWv68,15300
31
+ frontend/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff,sha256=vQQgU2H63chA_bFF_kdFosAwcr4RW_RXOoAb2dMx1Xw,19632
32
+ frontend/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2,sha256=f0pdPBzfilvI3rR7hWBqVIbVFZ1C66BIuulYPE0IXNA,6424
33
+ frontend/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff,sha256=n3OiV2YLlgX9P64Y7sc_GBPaXU4GY49ffSsVV1BTAYU,8744
34
+ frontend/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2,sha256=onRpwOPaPl7vHKC5v-qThVgejSHUboD3XaQNjUryOlg,6500
35
+ frontend/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff,sha256=9DEGx6ENrVHTcHqvb9qZf3B6bsgN0hcpfzHNbIbEsBA,8856
36
+ frontend/dist/assets/geist-mono-latin-ext-600-normal-CJwYYto2.woff2,sha256=vAr8fiX7_6yjrOG7wQVBWZ6m6B-DFg4fWlMWj6xW7yQ,6496
37
+ frontend/dist/assets/geist-mono-latin-ext-600-normal-EvIRCXgu.woff,sha256=Q12HiDr37dpOlmW65CBVV2Lj9Cr-NUqie39YNYVJFRI,8860
38
+ frontend/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff,sha256=5YskHT-Hh6AhVl2iF-aoIbI8LHWnxSQ9WUGXgI73xtI,8852
39
+ frontend/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2,sha256=SvXmr1pWFNoUmO521DxBSx1Pw5i-a0MiheNKDbg1yPU,6456
40
+ frontend/dist/assets/index-AEO_WTHY.js,sha256=cr_IZ-A-H62UBcQB3YRPJvNwmuQKOGJRpG9U6UUiV24,526550
41
+ frontend/dist/assets/index-JUssvikZ.css,sha256=QOVkNHAvCSWX8jo4r_vPtgkjg4gSP_mQQLKSPEEGAtA,44512
42
+ frontend/dist/assets/inter-cyrillic-400-normal-HOLc17fK.woff,sha256=bkQabJR4gxj6ArbQpjOqKe6-J7acZWfrHlm07caO1w4,9780
43
+ frontend/dist/assets/inter-cyrillic-400-normal-obahsSVq.woff2,sha256=8LtYZFnOjwmyOChQQPF-Pp6VOLLFp6rgd1GU4zw2w8M,7712
44
+ frontend/dist/assets/inter-cyrillic-500-normal-BasfLYem.woff2,sha256=t3qG7Baq3BV_SpnoiY1xzXXqJkdT2b3xP5YvjDmIy7A,7900
45
+ frontend/dist/assets/inter-cyrillic-500-normal-CxZf_p3X.woff,sha256=W-GOA0V4HLiComd5hRhVPA6RUXT8cHMatfCFymS6Hrw,9940
46
+ frontend/dist/assets/inter-cyrillic-600-normal-4D_pXhcN.woff,sha256=zBkO0bZOtlCkrX6Xx6Zoy4s0rNnEx_e_Q3LAy6zTV5U,9936
47
+ frontend/dist/assets/inter-cyrillic-600-normal-CWCymEST.woff2,sha256=bCo3-Cpna81EG3NeTizaTtuIc6BZq5w2KoTwcR8lcEE,7972
48
+ frontend/dist/assets/inter-cyrillic-700-normal-CjBOestx.woff2,sha256=WReHHTzJcNjOGVEBy_ZcH2jslIAi62BwAwNCu37fs7s,7904
49
+ frontend/dist/assets/inter-cyrillic-700-normal-DrXBdSj3.woff,sha256=crbapJFz5TECfYomDvEo4UKhZYEcq2oode7-DDxY-g4,9912
50
+ frontend/dist/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2,sha256=71cvkYeovgGNnZwsa3fB9ssq9BmfAvwWwpLqEKslosw,10232
51
+ frontend/dist/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff,sha256=XLLxzAk2-JpCzq409aVyAkjcW4EUwSscM6fS7wGptrk,13336
52
+ frontend/dist/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2,sha256=HC25LTzZsjfKfJwHr1VtVC8o3tHbVM1Dgs93ZQHFKR8,10432
53
+ frontend/dist/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff,sha256=4lDAWuvPSZceCyX0dpkt1GPCjAKS67IE2dtUL7UGbKc,13452
54
+ frontend/dist/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff,sha256=pQcyhUCUQ_RlPIWN5YxdwL_C_TPmDYdHOINF58WrGrM,13464
55
+ frontend/dist/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2,sha256=UJ_KnFlWT5qEb6abxkf5sFD8Eavxyfbz2nH7LV7UJb0,10484
56
+ frontend/dist/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2,sha256=pGuZeBFwlx4smah_9-6fXPkKnfzRgXXhvmMAIB0DASM,10496
57
+ frontend/dist/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff,sha256=Dz1L4r1wGPePo0hKX26djOVnaJQmZDuEpH6Mq7keXxY,13408
58
+ frontend/dist/assets/inter-greek-400-normal-B4URO6DV.woff2,sha256=wV3dAKmSe1b1xlWkH5dveb2EiiDM6R_BQid1GHKhzCc,7776
59
+ frontend/dist/assets/inter-greek-400-normal-q2sYcFCs.woff,sha256=va6dKHKejGhYfV1U4hbXcaQNemgA6CRYam4vVwwvEN8,9924
60
+ frontend/dist/assets/inter-greek-500-normal-BIZE56-Y.woff2,sha256=zBl9edM6NhWgR7quhfTRlX9vmLiJgxNljhYLHVGbG28,7920
61
+ frontend/dist/assets/inter-greek-500-normal-Xzm54t5V.woff,sha256=htVaSxucvO1-YedtXUTPSdC9HIFNPfLeZ7cTarT9zs4,9980
62
+ frontend/dist/assets/inter-greek-600-normal-BZpKdvQh.woff,sha256=CQsk4i_E3fQUUNE73sgcinSAi5_H7a-N8GyQmxd6iZI,10032
63
+ frontend/dist/assets/inter-greek-600-normal-plRanbMR.woff2,sha256=nFqJf52fz-YNkGMddmrmK0vx7ddmM_p6Sy-Mh-A2eJs,7944
64
+ frontend/dist/assets/inter-greek-700-normal-BUv2fZ6O.woff,sha256=UzaPU9X8QzgdzEXIUzuiR2-AM3E2MTUl-W3L3HNmFqU,9980
65
+ frontend/dist/assets/inter-greek-700-normal-C3JjAnD8.woff2,sha256=c3xskc1gNy0w5lQAlq2oPInJtGE810cuM2a_LgetmfE,7920
66
+ frontend/dist/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2,sha256=6t7dnhPCWCQwqfsVGeuGFS_au0g6b2bZD42KUul3obc,5264
67
+ frontend/dist/assets/inter-greek-ext-400-normal-KugGGMne.woff,sha256=Tytvkt3-zogS55d01EYhiqbgdz_4wtMRMP4r3CO9ItA,7064
68
+ frontend/dist/assets/inter-greek-ext-500-normal-2j5mBUwD.woff,sha256=yF0RvecZEIzJpPb1sAgIf2w2STtHOVWA1PUzYYKw8Mc,7192
69
+ frontend/dist/assets/inter-greek-ext-500-normal-C4iEst2y.woff2,sha256=dansk4Ojt6e_fPuU9k7fONnP3F9_-gy-0nM2J6bkQm0,5428
70
+ frontend/dist/assets/inter-greek-ext-600-normal-B8X0CLgF.woff,sha256=78eqnM5cUF3YzExNrwAkf6gF83vNqWB5I5ujNYqyffs,7212
71
+ frontend/dist/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2,sha256=OZ-U11qnNAPkDiNT6MDMdcOPc2KmyiY7OwLZcjFX_-U,5432
72
+ frontend/dist/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff,sha256=sm7WTJE1p1CtWAuSFSvt7vkHcedOGPb3dZptetsPG10,7216
73
+ frontend/dist/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2,sha256=t7JzLryYGzClRj_8hQQHZJ26QeYlruEkzGMpPaRUdik,5444
74
+ frontend/dist/assets/inter-latin-400-normal-C38fXH4l.woff2,sha256=iQmQSrbIcuuZQJNIKoiijsos2VkS17b-zXIQOw3Aftw,23664
75
+ frontend/dist/assets/inter-latin-400-normal-CyCys3Eg.woff,sha256=4g-gtP0t0m5NFLOsPMkiUJw6Y_pekQ6QxhRUSqBC3UU,30696
76
+ frontend/dist/assets/inter-latin-500-normal-BL9OpVg8.woff,sha256=m0LBCNpIte1VowcugV_-4tJxVi5sDdaZiRDo-cCeCj0,31284
77
+ frontend/dist/assets/inter-latin-500-normal-Cerq10X2.woff2,sha256=83efHvzMS9zfnAoCq5W_a9CS7QnEjAjO3HJYie3R0Z8,24272
78
+ frontend/dist/assets/inter-latin-600-normal-CiBQ2DWP.woff,sha256=apyzpQm07q8St92mxKrKw-hdB_QgG_TdcW4zLmkrh70,31260
79
+ frontend/dist/assets/inter-latin-600-normal-LgqL8muc.woff2,sha256=-aBuec06KiCVHA8OKPZt0ObT_ac5EdZAohJcj8t48ho,24452
80
+ frontend/dist/assets/inter-latin-700-normal-BLAVimhd.woff,sha256=fF7VZVcw3jN3BNP8lGKFFc1-PY0yNohxcJv1asA5fno,31320
81
+ frontend/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2,sha256=b1ZAn9PWS7hffQcLziB0nbLWa21jzsWGzCLRx2G-JJE,24356
82
+ frontend/dist/assets/inter-latin-ext-400-normal-77YHD8bZ.woff,sha256=Iq6DYPutJNKvfGyYmKNGyIF3Tch_fPlL0mLEitWNcRY,47560
83
+ frontend/dist/assets/inter-latin-ext-400-normal-C1nco2VV.woff2,sha256=Z0Sn9QnrxqsiCmzU6nfomK3wFPA9iNzaXUXYqf7vtOk,35000
84
+ frontend/dist/assets/inter-latin-ext-500-normal-BxGbmqWO.woff,sha256=MNKQPOl_ionqDpwIO0LLi_l-e7tWB-fwA1KBXxnLDVs,48492
85
+ frontend/dist/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2,sha256=LG-8QtMVUovrBsEJbfRUh79BhsS3i4ER0SyclR-KzKI,36024
86
+ frontend/dist/assets/inter-latin-ext-600-normal-CIVaiw4L.woff,sha256=Iv8gphcGdPuWN8KgciJ76GMDu27FK4KbnXB3QF9xlqY,48668
87
+ frontend/dist/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2,sha256=5L32ewzRXKnhhFCSdb6V25QhldPMKxf2oEUvKt910L8,36260
88
+ frontend/dist/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2,sha256=FD-VBPE3cBKqPjnJDENU70KcsElLmsDhQ38ageVBIjY,36244
89
+ frontend/dist/assets/inter-latin-ext-700-normal-TidjK2hL.woff,sha256=F2HtujIBQYDeqz274_cVbGTvbzS8djE7oxziJ_yf85E,48632
90
+ frontend/dist/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff,sha256=VKpC0yW839Yj7LnqxKXjxkIMY9e4bp2FRftHn2U761c,6500
91
+ frontend/dist/assets/inter-vietnamese-400-normal-DMkecbls.woff2,sha256=VHrZ_a6wrkNIf0uKAuR8NlU8DAy3PIqpj5O3thX2tV0,4972
92
+ frontend/dist/assets/inter-vietnamese-500-normal-DOriooB6.woff2,sha256=RONqpajxITACmim7DmaV0CYrZ0Rs4yHoeLH7VhYrXeY,5112
93
+ frontend/dist/assets/inter-vietnamese-500-normal-mJboJaSs.woff,sha256=GKSgn3UWcc2JKdfGbo_n2CC1EcNuUc9gR__Oq4juxKQ,6596
94
+ frontend/dist/assets/inter-vietnamese-600-normal-BuLX-rYi.woff,sha256=JKAee9lHru-Rro97jt6yP62vXO2jSmEM5i0rva6lfoM,6640
95
+ frontend/dist/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2,sha256=GuuUrp2wUqSi_m8wRvkoz_H9vXq9-HCpoBwTyub666U,5100
96
+ frontend/dist/assets/inter-vietnamese-700-normal-BZaoP0fm.woff,sha256=QSAjqy9BMzs3PixusqPrq4Bv3dYQAV0q6M1PQxTHupI,6632
97
+ frontend/dist/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2,sha256=X6-jgpoW2epaw6Z8UeVBhjUxInkLYEo-Nh-WyrGI2os,5104
98
+ ruth_code-0.1.0.dist-info/METADATA,sha256=vVdYG1_IsLP-sMdBZ9Cy-PTGNlfUGNWYNhZGujfWd7c,3473
99
+ ruth_code-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
100
+ ruth_code-0.1.0.dist-info/entry_points.txt,sha256=OUOEjN_vTYnRswfFUO85zRtxWta0OtAPeIbbHkRVVkM,38
101
+ ruth_code-0.1.0.dist-info/licenses/LICENSE,sha256=xcti2_uJQmSN1XNbBTAqkLakLRC_ohS1Eye00zW1s0E,1078
102
+ ruth_code-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ruth = ruth.cli:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Electric Sheep Africa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.