mysql_genius 0.7.1 → 0.7.2
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/.gitignore +1 -0
- data/CHANGELOG.md +9 -0
- data/app/views/layouts/mysql_genius/application.html.erb +27 -0
- data/lib/mysql_genius/version.rb +1 -1
- data/mysql_genius.gemspec +1 -1
- data/ralph/prd.json +85 -110
- data/ralph/progress.txt +113 -116
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d2b3f6350a4c464b3ec546a5248a9b1db4dc933e80e2da26472012367740828
|
|
4
|
+
data.tar.gz: 13ffee6262a381bb970cf6f9e473c95e40161d191249228225b6960aec7c9313
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 350005c5824825d20f187099dab6f181eb476d1b99441fdf2101f09430b4bb23abff72bbbae487f9a58bca43de02a20213e508980dbb407577678c45bbdf3f08
|
|
7
|
+
data.tar.gz: c372b88067e33f91b89840e994733ffb5a810ac0bdf38a582e32e240126d026871babe689911a4655bf7a1ba1d0e7664e4f3c7185ba1a96472a6f65d349ecfec
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.2
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Anthropic Messages API support** — `x-api-key` auth style with `anthropic-version` header, top-level `system` parameter, `content[0].text` response parsing.
|
|
7
|
+
- **Configurable `max_tokens`** — new field on `Core::Ai::Config` (default 4096), sent to both OpenAI and Anthropic APIs.
|
|
8
|
+
- **Copy response button** on all AI result sections (schema review, migration risk, optimization, describe query, rewrite, index advisor, root cause, anomaly detection).
|
|
9
|
+
- **Dark mode contrast fixes** for AI result sections — proper CSS classes with dark-mode variants replace hardcoded light-mode inline styles.
|
|
10
|
+
- **`capability?(:standalone_header)`** guard hides the dashboard header when rendered inside a layout that already provides one.
|
|
11
|
+
|
|
3
12
|
## 0.7.1
|
|
4
13
|
|
|
5
14
|
Lockstep version bump with `mysql_genius-core 0.7.1` which fixes missing ERB templates in the gem package.
|
|
@@ -145,6 +145,17 @@
|
|
|
145
145
|
code { font-size: 12px; word-break: break-all; background: #f0f1f3; padding: 2px 6px; border-radius: 3px; color: #24292f; }
|
|
146
146
|
pre.mg-pre { background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
|
|
147
147
|
|
|
148
|
+
/* AI result sections */
|
|
149
|
+
.mg-ai-section { border-radius: 4px; border: 1px solid #dee2e6; border-left-width: 4px; margin-bottom: 12px; }
|
|
150
|
+
.mg-ai-section-header { padding: 8px 12px; border-bottom: 1px solid #dee2e6; font-size: 13px; }
|
|
151
|
+
.mg-ai-section-body { padding: 12px; font-size: 13px; }
|
|
152
|
+
.mg-ai-danger { border-left-color: #dc3545; background: #fff5f5; }
|
|
153
|
+
.mg-ai-danger .mg-ai-section-header { border-bottom-color: #f5c6cb; }
|
|
154
|
+
.mg-ai-warning { border-left-color: #ffc107; background: #fffbeb; }
|
|
155
|
+
.mg-ai-warning .mg-ai-section-header { border-bottom-color: #ffeeba; }
|
|
156
|
+
.mg-ai-info { border-left-color: #17a2b8; background: #f0f9ff; }
|
|
157
|
+
.mg-ai-info .mg-ai-section-header { border-bottom-color: #bee5eb; }
|
|
158
|
+
|
|
148
159
|
/* Theme toggle */
|
|
149
160
|
.mg-theme-toggle { background: none; border: 1px solid #ced4da; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 14px; line-height: 1; color: inherit; }
|
|
150
161
|
.mg-theme-toggle:hover { background: #e9ecef; }
|
|
@@ -220,7 +231,13 @@
|
|
|
220
231
|
|
|
221
232
|
/* Inline code */
|
|
222
233
|
[data-theme="dark"] code { background: #21262d; color: #c9d1d9; }
|
|
234
|
+
[data-theme="dark"] pre { background: #161b22; color: #c9d1d9; border-color: #30363d; }
|
|
223
235
|
[data-theme="dark"] pre.mg-pre { background: #161b22; color: #c9d1d9; }
|
|
236
|
+
[data-theme="dark"] .mg-card-body { color: #c9d1d9; }
|
|
237
|
+
[data-theme="dark"] .mg-card-body h1, [data-theme="dark"] .mg-card-body h2, [data-theme="dark"] .mg-card-body h3, [data-theme="dark"] .mg-card-body h4 { color: #c9d1d9; }
|
|
238
|
+
[data-theme="dark"] .mg-card-body p, [data-theme="dark"] .mg-card-body li, [data-theme="dark"] .mg-card-body td { color: #c9d1d9; }
|
|
239
|
+
[data-theme="dark"] .mg-card-body strong { color: #e6edf3; }
|
|
240
|
+
[data-theme="dark"] .mg-card-body a { color: #58a6ff; }
|
|
224
241
|
|
|
225
242
|
/* Links */
|
|
226
243
|
[data-theme="dark"] .mg-link { color: #58a6ff; }
|
|
@@ -234,6 +251,16 @@
|
|
|
234
251
|
/* Checkboxes */
|
|
235
252
|
[data-theme="dark"] .mg-check .type-hint { color: #484f58; }
|
|
236
253
|
|
|
254
|
+
/* AI result sections (dark) */
|
|
255
|
+
[data-theme="dark"] .mg-ai-section { border-color: #30363d; }
|
|
256
|
+
[data-theme="dark"] .mg-ai-danger { background: #2d0d0d; border-left-color: #f85149; }
|
|
257
|
+
[data-theme="dark"] .mg-ai-danger .mg-ai-section-header { border-bottom-color: #3d1414; color: #f85149; }
|
|
258
|
+
[data-theme="dark"] .mg-ai-warning { background: #2d2000; border-left-color: #d29922; }
|
|
259
|
+
[data-theme="dark"] .mg-ai-warning .mg-ai-section-header { border-bottom-color: #3d2e00; color: #d29922; }
|
|
260
|
+
[data-theme="dark"] .mg-ai-info { background: #0d2a3a; border-left-color: #58a6ff; }
|
|
261
|
+
[data-theme="dark"] .mg-ai-info .mg-ai-section-header { border-bottom-color: #0d3a5a; color: #58a6ff; }
|
|
262
|
+
[data-theme="dark"] .mg-ai-section-body { color: #c9d1d9; }
|
|
263
|
+
|
|
237
264
|
/* Theme toggle (dark) */
|
|
238
265
|
[data-theme="dark"] .mg-theme-toggle { border-color: #30363d; color: #c9d1d9; }
|
|
239
266
|
[data-theme="dark"] .mg-theme-toggle:hover { background: #21262d; }
|
data/lib/mysql_genius/version.rb
CHANGED
data/mysql_genius.gemspec
CHANGED
|
@@ -30,6 +30,6 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.require_paths = ["lib"]
|
|
31
31
|
|
|
32
32
|
spec.add_dependency("activerecord", ">= 6.0", "< 9")
|
|
33
|
-
spec.add_dependency("mysql_genius-core", "~> 0.7.
|
|
33
|
+
spec.add_dependency("mysql_genius-core", "~> 0.7.2")
|
|
34
34
|
spec.add_dependency("railties", ">= 6.0", "< 9")
|
|
35
35
|
end
|
data/ralph/prd.json
CHANGED
|
@@ -1,174 +1,149 @@
|
|
|
1
1
|
{
|
|
2
2
|
"project": "MysqlGenius",
|
|
3
|
-
"branchName": "ralph/
|
|
4
|
-
"description": "
|
|
3
|
+
"branchName": "ralph/sqlite-migration",
|
|
4
|
+
"description": "Replace in-memory StatsHistory and YAML ProfileManager with SQLite for persistent stats and clean profile CRUD in the desktop sidecar",
|
|
5
5
|
"userStories": [
|
|
6
6
|
{
|
|
7
7
|
"id": "US-001",
|
|
8
|
-
"title": "Add
|
|
9
|
-
"description": "As a developer, I need a
|
|
8
|
+
"title": "Add sqlite3 dependency and create Database class with schema",
|
|
9
|
+
"description": "As a developer, I need a Database class that manages a SQLite file with profiles, settings, and stats_snapshots tables.",
|
|
10
10
|
"acceptanceCriteria": [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
11
|
+
"Add sqlite3 ~> 2.0 to gems/mysql_genius-desktop/mysql_genius-desktop.gemspec as runtime dependency",
|
|
12
|
+
"Run bundle install in gems/mysql_genius-desktop",
|
|
13
|
+
"Create gems/mysql_genius-desktop/lib/mysql_genius/desktop/database.rb",
|
|
14
|
+
"Database.new(path) creates the SQLite file if missing and runs CREATE TABLE IF NOT EXISTS for all 3 tables",
|
|
15
|
+
"profiles table: name TEXT PRIMARY KEY, host TEXT NOT NULL, port INTEGER DEFAULT 3306, username TEXT NOT NULL, password TEXT DEFAULT '', database_name TEXT NOT NULL, tls_mode TEXT DEFAULT 'preferred', created_at TEXT, updated_at TEXT",
|
|
16
|
+
"settings table: key TEXT PRIMARY KEY, value TEXT",
|
|
17
|
+
"stats_snapshots table: id INTEGER PRIMARY KEY AUTOINCREMENT, digest_text TEXT NOT NULL, timestamp TEXT NOT NULL, delta_calls INTEGER DEFAULT 0, delta_total_time_ms REAL DEFAULT 0, delta_avg_time_ms REAL DEFAULT 0",
|
|
18
|
+
"Index on stats_snapshots(digest_text, timestamp)",
|
|
19
|
+
"Profile CRUD: list_profiles returns array of hashes, find_profile(name) returns hash or nil, add_profile(attrs) inserts (raises on duplicate), update_profile(name, attrs) updates (raises if not found), delete_profile(name) deletes (raises if not found)",
|
|
20
|
+
"Settings: get_setting(key) returns string or nil, set_setting(key, value) upserts, get_ai_config returns hash of ai.* keys with prefix stripped, set_ai_config(hash) writes each key with ai. prefix",
|
|
21
|
+
"Stats: record_snapshot(digest_text, snapshot_hash) inserts + prunes rows older than 24 hours, series_for(digest_text) returns ordered array last 24hr, digests returns distinct digest_texts, clear deletes all",
|
|
22
|
+
"Create spec at gems/mysql_genius-desktop/spec/mysql_genius/desktop/database_spec.rb with tests for all CRUD operations using tmpdir",
|
|
23
|
+
"Desktop gem suite passes",
|
|
23
24
|
"Typecheck passes"
|
|
24
25
|
],
|
|
25
26
|
"priority": 1,
|
|
26
27
|
"passes": true,
|
|
27
|
-
"notes": "
|
|
28
|
+
"notes": "Use raw sqlite3 gem calls, no ORM. Thread safety: SQLite WAL mode handles concurrent reads. Error classes: Database::DuplicateProfileError, Database::ProfileNotFoundError. The spec should use Dir.mktmpdir for isolated DB files per test."
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
"id": "US-002",
|
|
31
|
-
"title": "
|
|
32
|
-
"description": "As a developer, I need a
|
|
32
|
+
"title": "Create SqliteStatsHistory as drop-in replacement for StatsHistory",
|
|
33
|
+
"description": "As a developer, I need a SQLite-backed stats history with the same API as the in-memory StatsHistory so the StatsCollector works unchanged.",
|
|
33
34
|
"acceptanceCriteria": [
|
|
34
|
-
"Create gems/mysql_genius-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"If performance_schema is unavailable, logs warning and stops (no crash loop)",
|
|
44
|
-
"Add require to gems/mysql_genius-core/lib/mysql_genius/core.rb",
|
|
45
|
-
"Create spec at gems/mysql_genius-core/spec/mysql_genius/core/analysis/stats_collector_spec.rb",
|
|
46
|
-
"Core gem suite passes",
|
|
35
|
+
"Create gems/mysql_genius-desktop/lib/mysql_genius/desktop/sqlite_stats_history.rb",
|
|
36
|
+
"SqliteStatsHistory.new(database) takes a Database instance",
|
|
37
|
+
"record(digest_text, snapshot_hash) delegates to database.record_snapshot",
|
|
38
|
+
"series_for(digest_text) delegates to database.series_for, returns array of hashes with symbol keys (timestamp:, calls:, total_time_ms:, avg_time_ms:)",
|
|
39
|
+
"digests delegates to database.digests",
|
|
40
|
+
"clear delegates to database.clear",
|
|
41
|
+
"Same public API as MysqlGenius::Core::Analysis::StatsHistory (record, series_for, digests, clear)",
|
|
42
|
+
"Create spec at gems/mysql_genius-desktop/spec/mysql_genius/desktop/sqlite_stats_history_spec.rb",
|
|
43
|
+
"Desktop gem suite passes",
|
|
47
44
|
"Typecheck passes"
|
|
48
45
|
],
|
|
49
46
|
"priority": 2,
|
|
50
47
|
"passes": true,
|
|
51
|
-
"notes": "The
|
|
48
|
+
"notes": "The StatsCollector passes snapshot hashes with string keys from its tick method. SqliteStatsHistory should accept both string and symbol keys. The series_for return format must match what the query_detail template JS expects: [{timestamp:, calls:, total_time_ms:, avg_time_ms:}]."
|
|
52
49
|
},
|
|
53
50
|
{
|
|
54
51
|
"id": "US-003",
|
|
55
|
-
"title": "Add
|
|
56
|
-
"description": "As a
|
|
52
|
+
"title": "Add first-boot YAML import logic to Launcher",
|
|
53
|
+
"description": "As a user, I want my existing YAML profiles and AI config automatically imported into SQLite on first boot.",
|
|
57
54
|
"acceptanceCriteria": [
|
|
58
|
-
"Modify gems/mysql_genius-
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
55
|
+
"Modify gems/mysql_genius-desktop/lib/mysql_genius/desktop/launcher.rb",
|
|
56
|
+
"After Config.load, create Database.new at ~/.config/mysql_genius/mysql_genius.db (create dir if needed)",
|
|
57
|
+
"If database.list_profiles is empty AND config.profiles is not empty, import each profile into SQLite",
|
|
58
|
+
"If database.get_setting('ai.endpoint') is nil AND config.ai.endpoint is present, import AI config into SQLite",
|
|
59
|
+
"Set App.set(:database, db)",
|
|
60
|
+
"Create SqliteStatsHistory.new(db) and pass to StatsCollector instead of StatsHistory.new",
|
|
61
|
+
"Add require statements for database and sqlite_stats_history to desktop.rb",
|
|
62
|
+
"Existing desktop specs still pass (rack_helper needs updating to inject a test Database)",
|
|
63
|
+
"Desktop gem suite passes",
|
|
64
64
|
"Typecheck passes"
|
|
65
65
|
],
|
|
66
66
|
"priority": 3,
|
|
67
67
|
"passes": true,
|
|
68
|
-
"notes": "
|
|
68
|
+
"notes": "The rack_helper.rb needs to create a tmpdir-based Database and set it on the App via App.set(:database, db). Ensure mkdir_p for ~/.config/mysql_genius/ before creating the DB. The import only runs when the DB is empty — deleting mysql_genius.db forces re-import on next boot."
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"id": "US-004",
|
|
72
|
-
"title": "
|
|
73
|
-
"description": "As a user, I want
|
|
72
|
+
"title": "Rewire profile API routes to use Database",
|
|
73
|
+
"description": "As a user, I want the /connections page to read and write profiles from SQLite instead of YAML.",
|
|
74
74
|
"acceptanceCriteria": [
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
75
|
+
"Modify gems/mysql_genius-desktop/lib/mysql_genius/desktop/app.rb",
|
|
76
|
+
"GET /api/profiles reads from settings.database.list_profiles + settings.current_profile_name",
|
|
77
|
+
"POST /api/profiles calls settings.database.add_profile, returns updated list",
|
|
78
|
+
"PUT /api/profiles/:name calls settings.database.update_profile, returns updated list",
|
|
79
|
+
"DELETE /api/profiles/:name calls settings.database.delete_profile (rejects active profile), returns updated list",
|
|
80
|
+
"POST /api/test_connection unchanged (no storage involved)",
|
|
81
|
+
"POST /api/profiles/:name/connect reads profile from settings.database.find_profile",
|
|
82
|
+
"GET /api/ai_config reads from settings.database.get_ai_config",
|
|
83
|
+
"PUT /api/ai_config writes to settings.database.set_ai_config + reloads into running app",
|
|
84
|
+
"Remove require for profile_manager from app.rb",
|
|
85
|
+
"Update profiles_api_spec.rb to use Database instead of YAML tmpdir",
|
|
86
|
+
"Desktop gem suite passes",
|
|
87
87
|
"Typecheck passes"
|
|
88
88
|
],
|
|
89
89
|
"priority": 4,
|
|
90
90
|
"passes": true,
|
|
91
|
-
"notes": "
|
|
91
|
+
"notes": "Error mapping: Database::DuplicateProfileError -> 409, Database::ProfileNotFoundError -> 404. The active-profile-delete check stays in the route handler (check settings.current_profile_name before calling delete). The connect route builds a MysqlConfig from the DB hash: Config::MysqlConfig.from_hash(profile_hash)."
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"id": "US-005",
|
|
95
|
-
"title": "
|
|
96
|
-
"description": "As a
|
|
95
|
+
"title": "Rewire SessionSwapper to use Database",
|
|
96
|
+
"description": "As a developer, I need SessionSwapper to look up profiles from SQLite instead of Config.",
|
|
97
97
|
"acceptanceCriteria": [
|
|
98
|
-
"Modify gems/mysql_genius-
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
98
|
+
"Modify gems/mysql_genius-desktop/lib/mysql_genius/desktop/session_swapper.rb",
|
|
99
|
+
"Change initialize to accept (app_class, config, database) instead of (app_class, config)",
|
|
100
|
+
"switch_to looks up profile via @database.find_profile(name) instead of @config.profile_by_name(name)",
|
|
101
|
+
"Build MysqlConfig from the DB hash via Config::MysqlConfig.from_hash(profile)",
|
|
102
|
+
"Update all callers of SessionSwapper.new to pass the database",
|
|
103
|
+
"Update session_swapper_spec.rb",
|
|
104
|
+
"Desktop gem suite passes",
|
|
104
105
|
"Typecheck passes"
|
|
105
106
|
],
|
|
106
107
|
"priority": 5,
|
|
107
108
|
"passes": true,
|
|
108
|
-
"notes": "The
|
|
109
|
+
"notes": "The profile hash from Database has string keys (host, port, etc). MysqlConfig.from_hash handles both string and symbol keys via transform_keys. The build_switch_config method still creates a Config.allocate with profiles array for ActiveSession compatibility."
|
|
109
110
|
},
|
|
110
111
|
{
|
|
111
112
|
"id": "US-006",
|
|
112
|
-
"title": "
|
|
113
|
-
"description": "As a
|
|
113
|
+
"title": "Delete ProfileManager and clean up",
|
|
114
|
+
"description": "As a developer, I want to remove the dead ProfileManager code now that Database handles everything.",
|
|
114
115
|
"acceptanceCriteria": [
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"Add query_history action that returns JSON with current stats + history",
|
|
123
|
-
"query_history looks up the digest in performance_schema and gets history from MysqlGenius.stats_history",
|
|
124
|
-
"Add request spec for GET /queries/:digest (returns 200)",
|
|
125
|
-
"Add request spec for GET /api/query_history/:digest (returns JSON with query and history keys)",
|
|
126
|
-
"Rails adapter suite passes",
|
|
116
|
+
"Delete gems/mysql_genius-desktop/lib/mysql_genius/desktop/profile_manager.rb",
|
|
117
|
+
"Delete gems/mysql_genius-desktop/spec/mysql_genius/desktop/profile_manager_spec.rb",
|
|
118
|
+
"Remove require 'mysql_genius/desktop/profile_manager' from desktop.rb",
|
|
119
|
+
"Remove require 'mysql_genius/desktop/profile_manager' from app.rb if present",
|
|
120
|
+
"Grep the entire desktop gem for 'ProfileManager' references and remove any remaining",
|
|
121
|
+
"Desktop gem suite passes",
|
|
122
|
+
"All 3 rubocops pass",
|
|
127
123
|
"Typecheck passes"
|
|
128
124
|
],
|
|
129
125
|
"priority": 6,
|
|
130
126
|
"passes": true,
|
|
131
|
-
"notes": "
|
|
127
|
+
"notes": "Make sure no spec files reference ProfileManager. The Database class error classes (DuplicateProfileError, ProfileNotFoundError) replace ProfileManager's error classes — verify the route handlers rescue the correct class names."
|
|
132
128
|
},
|
|
133
129
|
{
|
|
134
130
|
"id": "US-007",
|
|
135
|
-
"title": "Wire stats collector and detail routes into desktop sidecar",
|
|
136
|
-
"description": "As a sidecar user, I want the stats collector running and the query detail page accessible.",
|
|
137
|
-
"acceptanceCriteria": [
|
|
138
|
-
"Update gems/mysql_genius-desktop/lib/mysql_genius/desktop/launcher.rb to create StatsHistory + StatsCollector and set on App",
|
|
139
|
-
"Add App settings: :stats_history, :stats_collector",
|
|
140
|
-
"Collector uses connection_provider that checks out from the active session",
|
|
141
|
-
"Register at_exit to stop collector",
|
|
142
|
-
"Update SessionSwapper to stop old collector, clear history, start new collector on profile switch",
|
|
143
|
-
"Add GET /queries/:digest route to App (renders query_detail template through layout)",
|
|
144
|
-
"Add GET /api/query_history/:digest route to App (returns JSON)",
|
|
145
|
-
"Both new routes are under session-token auth",
|
|
146
|
-
"Add request spec for the two new routes",
|
|
147
|
-
"Desktop gem suite passes: (cd gems/mysql_genius-desktop && bundle exec rspec)",
|
|
148
|
-
"Typecheck passes"
|
|
149
|
-
],
|
|
150
|
-
"priority": 7,
|
|
151
|
-
"passes": true,
|
|
152
|
-
"notes": "The connection_provider for the sidecar wraps session.checkout. On profile switch, SessionSwapper should: App.settings.stats_collector&.stop, App.settings.stats_history&.clear, then after swapping the session create a new collector with the new session and start it. The render_query_detail method follows the same pattern as render_dashboard (Tilt through layout)."
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
"id": "US-008",
|
|
156
131
|
"title": "Full green sweep across all suites",
|
|
157
132
|
"description": "As a developer, I need all test suites and linters passing before the PR.",
|
|
158
133
|
"acceptanceCriteria": [
|
|
159
|
-
"Rails adapter rspec passes (
|
|
160
|
-
"Core gem rspec passes (
|
|
161
|
-
"Desktop gem rspec passes (
|
|
134
|
+
"Rails adapter rspec passes (82+ examples)",
|
|
135
|
+
"Core gem rspec passes (215+ examples)",
|
|
136
|
+
"Desktop gem rspec passes (150+ examples)",
|
|
162
137
|
"Rails adapter rubocop clean",
|
|
163
138
|
"Core gem rubocop clean",
|
|
164
139
|
"Desktop gem rubocop clean",
|
|
165
|
-
"
|
|
166
|
-
"
|
|
140
|
+
"No changes to mysql_genius-core or mysql_genius Rails adapter source files",
|
|
141
|
+
"publish.yml untouched",
|
|
167
142
|
"Typecheck passes"
|
|
168
143
|
],
|
|
169
|
-
"priority":
|
|
170
|
-
"passes":
|
|
171
|
-
"notes": "Run all six commands
|
|
144
|
+
"priority": 7,
|
|
145
|
+
"passes": true,
|
|
146
|
+
"notes": "Run all six commands. If any fail, fix before marking complete."
|
|
172
147
|
}
|
|
173
148
|
]
|
|
174
149
|
}
|
data/ralph/progress.txt
CHANGED
|
@@ -1,141 +1,138 @@
|
|
|
1
1
|
# Ralph Progress Log
|
|
2
|
-
Started: Sun Apr 12
|
|
2
|
+
Started: Sun Apr 12 23:47:10 CDT 2026
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
## Codebase Patterns
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- Desktop gem has NO ActiveSupport — never use `.squish` or other AS extensions; use plain string concatenation for SQL
|
|
17
|
-
- Desktop `Core::Result#to_a` returns raw row arrays; use `to_hashes` to get `{column => value}` hashes
|
|
18
|
-
- Desktop `rack_helper.rb` sets `@fake_adapter` via before block — specs using it must disable `RSpec/InstanceVariable`
|
|
19
|
-
- Rails `ActiveRecord::Result` `to_a` returns hashes directly; for specs use `instance_double` with `to_a:` returning hash arrays
|
|
20
|
-
- When adding new App settings to desktop, also reset them in `rack_helper.rb` after block and set in before block
|
|
21
|
-
- `StatsCollector.new` spawns real threads — mock it in request specs via `allow(StatsCollector).to receive(:new)` in rack_helper
|
|
22
|
-
- `path_for(:query_detail)` and `path_for(:query_history)` both need `@digest` to build a complete URL; both adapters handle this in their `path_for` implementation
|
|
23
|
-
- Routes that need a dynamic segment (digest) use base-path + value pattern in desktop PATHS hash; Rails uses named route helpers with keyword arg
|
|
6
|
+
- Desktop gem specs use `Dir.mktmpdir` for isolated file-based test fixtures (DB files, YAML configs)
|
|
7
|
+
- Desktop gem rubocop config inherits from root `.rubocop.yml` via `inherit_from`
|
|
8
|
+
- Desktop gem Gemfile.lock is not tracked in git (gem convention)
|
|
9
|
+
- Use `results_as_hash = true` on SQLite3::Database to get hash-style row access
|
|
10
|
+
- Desktop app settings are injected via `App.set(:key, value)` (Sinatra pattern)
|
|
11
|
+
- Launcher private methods can be stubbed via `allow(launcher).to(receive(:method_name))` in RSpec
|
|
12
|
+
- First-boot import logic: only runs when DB is empty (list_profiles.empty? / get_setting returns nil)
|
|
13
|
+
- rack_helper.rb must set `App.set(:database, ...)` for request specs that depend on DB settings
|
|
14
|
+
- Database returns `database_name` key; `MysqlConfig.from_hash` expects `database` — remap via `mysql_hash_from_profile`
|
|
15
|
+
- Profile API response must be `{name:, mysql: {host:, port:, database:, ...}}` — use `format_profile` to reshape DB rows
|
|
24
16
|
|
|
25
17
|
---
|
|
26
18
|
|
|
27
19
|
## 2026-04-12 - US-001
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
20
|
+
- What was implemented:
|
|
21
|
+
- Added `sqlite3 ~> 2.0` runtime dependency to `mysql_genius-desktop.gemspec`
|
|
22
|
+
- Created `Database` class at `lib/mysql_genius/desktop/database.rb` with:
|
|
23
|
+
- SQLite WAL mode for concurrent reads
|
|
24
|
+
- 3 tables: profiles, settings, stats_snapshots
|
|
25
|
+
- Composite index on stats_snapshots(digest_text, timestamp)
|
|
26
|
+
- Full profile CRUD: list_profiles, find_profile, add_profile, update_profile, delete_profile
|
|
27
|
+
- Settings: get_setting, set_setting, get_ai_config, set_ai_config
|
|
28
|
+
- Stats: record_snapshot (with 24hr pruning), series_for, digests, clear
|
|
29
|
+
- Error classes: DuplicateProfileError, ProfileNotFoundError
|
|
30
|
+
- Created comprehensive spec at `spec/mysql_genius/desktop/database_spec.rb` (29 examples)
|
|
31
|
+
- Files changed:
|
|
32
|
+
- `gems/mysql_genius-desktop/mysql_genius-desktop.gemspec` (added sqlite3 dep)
|
|
33
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/database.rb` (new)
|
|
34
|
+
- `gems/mysql_genius-desktop/spec/mysql_genius/desktop/database_spec.rb` (new)
|
|
35
35
|
- **Learnings for future iterations:**
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
36
|
+
- ISO 8601 timestamps have second precision — `sleep(0.01)` is not enough to test timestamp changes, need `sleep(1.1)`
|
|
37
|
+
- RSpec `let` is lazy — must reference the variable in the test body before asserting on side effects like file creation
|
|
38
|
+
- `Naming/AccessorMethodName` cop flags `get_`/`set_` prefixes — disable inline when API names are mandated by the PRD
|
|
39
|
+
- `add_profile` accepts both `database_name` and `database` keys for compatibility with existing config hash shapes
|
|
39
40
|
---
|
|
40
41
|
|
|
41
|
-
## 2026-04-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
- Added
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
42
|
+
## 2026-04-13 - US-003
|
|
43
|
+
- What was implemented:
|
|
44
|
+
- Modified `launcher.rb` to create Database at `~/.config/mysql_genius/mysql_genius.db`
|
|
45
|
+
- Added first-boot YAML import: profiles imported when DB empty, AI config imported when endpoint missing
|
|
46
|
+
- Replaced `StatsHistory.new` with `SqliteStatsHistory.new(db)` for persistent stats
|
|
47
|
+
- Added `App.set(:database, db)` to wire Database into Sinatra settings
|
|
48
|
+
- Added `database` and `sqlite_stats_history` requires to `desktop.rb`
|
|
49
|
+
- Added `set :database, nil` to `app.rb` settings
|
|
50
|
+
- Updated `rack_helper.rb` to create tmpdir-based test Database for request specs
|
|
51
|
+
- Updated `launcher_spec.rb` to stub `open_database` and verify Database wiring
|
|
52
|
+
- Files changed:
|
|
53
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/launcher.rb` (modified)
|
|
54
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/app.rb` (added database setting)
|
|
55
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop.rb` (added requires)
|
|
56
|
+
- `gems/mysql_genius-desktop/spec/rack_helper.rb` (inject test Database)
|
|
57
|
+
- `gems/mysql_genius-desktop/spec/mysql_genius/desktop/launcher_spec.rb` (updated)
|
|
52
58
|
- **Learnings for future iterations:**
|
|
53
|
-
- `
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
59
|
+
- `open_database` extracted as private method so specs can stub it without touching filesystem
|
|
60
|
+
- Import logic checks DB emptiness, not config emptiness — deleting mysql_genius.db forces re-import
|
|
61
|
+
- Profile import maps `mysql.database` → `database_name` for Database schema compatibility
|
|
62
|
+
- rack_helper cleanup: must `FileUtils.remove_entry` tmpdir in after block to avoid leftover test DBs
|
|
57
63
|
---
|
|
58
64
|
|
|
59
|
-
## 2026-04-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
65
|
+
## 2026-04-13 - US-004
|
|
66
|
+
- What was implemented:
|
|
67
|
+
- Rewired all profile API routes (GET/POST/PUT/DELETE) in `app.rb` to use `Database` instead of `ProfileManager`
|
|
68
|
+
- Added `GET /api/ai_config` and `PUT /api/ai_config` routes backed by Database settings
|
|
69
|
+
- Removed `require 'mysql_genius/desktop/profile_manager'` from `app.rb`
|
|
70
|
+
- Added `switch_to_config(name, mysql_config)` to `SessionSwapper` for pre-built MysqlConfig objects
|
|
71
|
+
- Inlined `test_connection` logic in the route handler (was delegated to ProfileManager)
|
|
72
|
+
- Added private helpers: `format_profile`, `profile_attrs_from_request`, `update_attrs_from_request`, `mysql_hash_from_profile`, `build_minimal_config`, `reload_ai_config_from_database`
|
|
73
|
+
- Rewrote `profiles_api_spec.rb` to seed test data via `@test_database.add_profile` instead of YAML tmpdir
|
|
74
|
+
- Added specs for `GET /api/ai_config`, `PUT /api/ai_config`, and `POST /api/profiles/:name/connect` with unknown profile
|
|
75
|
+
- Files changed:
|
|
76
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/app.rb` (rewired routes, removed ProfileManager require)
|
|
77
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/session_swapper.rb` (added switch_to_config method)
|
|
78
|
+
- `gems/mysql_genius-desktop/spec/requests/profiles_api_spec.rb` (rewrote for Database)
|
|
66
79
|
- **Learnings for future iterations:**
|
|
67
|
-
-
|
|
68
|
-
-
|
|
80
|
+
- Database returns `database_name` key but `MysqlConfig.from_hash` expects `database` — need `mysql_hash_from_profile` helper to remap
|
|
81
|
+
- Profile API response format must be `{name:, mysql: {host:, port:, database:, ...}}` to match frontend expectations (connections.html.erb)
|
|
82
|
+
- `SessionSwapper.switch_to_config` allows callers that already have a MysqlConfig to skip the config lookup
|
|
83
|
+
- Active profile delete check stays in route handler, not in Database (Database.delete_profile has no concept of "active")
|
|
69
84
|
---
|
|
70
85
|
|
|
71
|
-
## 2026-04-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
- RuboCop: clean
|
|
86
|
+
## 2026-04-13 - US-005
|
|
87
|
+
- What was implemented:
|
|
88
|
+
- Changed `SessionSwapper.initialize` to accept `(app_class, config, database)` instead of `(app_class, config)`
|
|
89
|
+
- Rewired `switch_to` to look up profiles via `@database.find_profile(name)` instead of `@config.profile_by_name(name)`
|
|
90
|
+
- Added `mysql_hash_from_profile` private helper to remap `database_name` → `database` for `MysqlConfig.from_hash`
|
|
91
|
+
- Changed `switch_to_config` to create `SqliteStatsHistory.new(@database)` instead of in-memory `StatsHistory.new`
|
|
92
|
+
- Updated the caller in `app.rb` connect route to pass `settings.database` as third arg
|
|
93
|
+
- Rewrote `session_swapper_spec.rb` to use a tmpdir-based Database instead of Config profile lookup
|
|
94
|
+
- Files changed:
|
|
95
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/session_swapper.rb` (rewired to use Database)
|
|
96
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/app.rb` (updated SessionSwapper.new call)
|
|
97
|
+
- `gems/mysql_genius-desktop/spec/mysql_genius/desktop/session_swapper_spec.rb` (rewrote for Database)
|
|
84
98
|
- **Learnings for future iterations:**
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
- Y-axis nice step calculation: ceil(rawStep/magnitude)*magnitude ensures clean round-number ticks
|
|
89
|
-
- The `@digest` instance variable must be set by the controller action before rendering this template
|
|
99
|
+
- `RSpec/MultipleMemoizedHelpers` max is 5 (subject excluded via AllowSubject). Inline tmpdir into `let(:database)` via `Dir.mktmpdir` to avoid extra helper
|
|
100
|
+
- `mysql_hash_from_profile` is duplicated between `app.rb` and `session_swapper.rb` — could be extracted to a shared module in US-006 cleanup if desired
|
|
101
|
+
- `switch_to_config` is still used by the connect route in `app.rb` which pre-builds the MysqlConfig — `switch_to` is for callers that only have a profile name
|
|
90
102
|
---
|
|
91
103
|
|
|
92
|
-
## 2026-04-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
104
|
+
## 2026-04-13 - US-006
|
|
105
|
+
- What was implemented:
|
|
106
|
+
- Deleted `profile_manager.rb` source file
|
|
107
|
+
- Deleted `profile_manager_spec.rb` spec file
|
|
108
|
+
- Removed `require "mysql_genius/desktop/profile_manager"` from `desktop.rb`
|
|
109
|
+
- Verified no remaining `ProfileManager` or `profile_manager` references in the desktop gem
|
|
110
|
+
- Confirmed route handlers already rescue `Database::DuplicateProfileError` / `Database::ProfileNotFoundError` (not ProfileManager versions)
|
|
111
|
+
- Files changed:
|
|
112
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop/profile_manager.rb` (deleted)
|
|
113
|
+
- `gems/mysql_genius-desktop/spec/mysql_genius/desktop/profile_manager_spec.rb` (deleted)
|
|
114
|
+
- `gems/mysql_genius-desktop/lib/mysql_genius/desktop.rb` (removed require line)
|
|
99
115
|
- **Learnings for future iterations:**
|
|
100
|
-
-
|
|
101
|
-
-
|
|
116
|
+
- US-004 already removed `profile_manager` require from `app.rb`, so only `desktop.rb` needed updating
|
|
117
|
+
- All error classes were already migrated to `Database::` namespace in US-001/US-004 — cleanup was straightforward
|
|
118
|
+
- 190 desktop specs pass, all 3 rubocops clean after removal
|
|
102
119
|
---
|
|
103
120
|
|
|
104
|
-
## 2026-04-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
121
|
+
## 2026-04-13 - US-007
|
|
122
|
+
- What was implemented:
|
|
123
|
+
- Full green sweep: ran all 6 quality commands and verified all pass
|
|
124
|
+
- Rails adapter rspec: 82 examples, 0 failures
|
|
125
|
+
- Core gem rspec: 215 examples, 0 failures
|
|
126
|
+
- Desktop gem rspec: 190 examples, 0 failures
|
|
127
|
+
- Rails adapter rubocop: 152 files, no offenses
|
|
128
|
+
- Core gem rubocop: 64 files, no offenses
|
|
129
|
+
- Desktop gem rubocop: 48 files, no offenses
|
|
130
|
+
- Verified no changes to mysql_genius-core or mysql_genius Rails adapter source files
|
|
131
|
+
- Verified publish.yml untouched
|
|
132
|
+
- Files changed:
|
|
133
|
+
- `ralph/prd.json` (marked US-007 passes: true)
|
|
134
|
+
- `ralph/progress.txt` (this entry)
|
|
116
135
|
- **Learnings for future iterations:**
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
- `path_for` needs to know about digest routes: use an allowlist `%i[query_detail query_history]` and conditionally pass `digest:` param
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## 2026-04-11 - US-007
|
|
123
|
-
- Updated `Launcher#call` to create StatsHistory + StatsCollector, set on App, register collector in at_exit
|
|
124
|
-
- Added `set :stats_history, nil` and `set :stats_collector, nil` to App settings
|
|
125
|
-
- Updated App `path_for` helper to append `@digest` for `:query_detail` and `:query_history` routes
|
|
126
|
-
- Added `GET /queries/:digest` and `GET /api/query_history/:digest` routes to App (under session-token auth)
|
|
127
|
-
- Added `render_query_detail`, `fetch_query_history_current`, `fetch_query_history_series` private methods
|
|
128
|
-
- Updated SessionSwapper to stop old collector, clear history, create new collector after session swap
|
|
129
|
-
- Added `:root`, `:query_detail`, `:query_history` to desktop PATHS hash
|
|
130
|
-
- Updated `rack_helper.rb` to reset stats settings and stub `StatsCollector.new` globally to prevent background threads in specs
|
|
131
|
-
- Updated `session_swapper_spec.rb` to mock StatsCollector/StatsHistory and handle new `set` keys
|
|
132
|
-
- Added `spec/requests/query_detail_spec.rb` for desktop (4 examples)
|
|
133
|
-
- Files changed: app.rb, paths.rb, launcher.rb, session_swapper.rb, rack_helper.rb, session_swapper_spec.rb, query_detail_spec.rb (new)
|
|
134
|
-
- Desktop suite: 158 examples, 0 failures (was 154)
|
|
135
|
-
- RuboCop: clean
|
|
136
|
-
- **Learnings for future iterations:**
|
|
137
|
-
- Desktop gem has NO ActiveSupport — never use `.squish`; use explicit string concatenation
|
|
138
|
-
- `Core::Result#to_a` returns raw row arrays (not hashes) — use `to_hashes` for hash access
|
|
139
|
-
- When new `SessionSwapper#switch_to` logic creates real background objects, existing integration tests can fail — mock the constructor in rack_helper to prevent threads
|
|
140
|
-
- Add `# rubocop:disable RSpec/InstanceVariable` at file top + `enable` at bottom when using `@fake_adapter` directly in specs
|
|
136
|
+
- All 487 examples across 3 suites (82 + 215 + 190) pass — the SQLite migration touched only the desktop gem
|
|
137
|
+
- The .DS_Store files appear as untracked — these should stay ignored (not committed)
|
|
141
138
|
---
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mysql_genius
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antarr Byrd
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -36,14 +36,14 @@ dependencies:
|
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.7.
|
|
39
|
+
version: 0.7.2
|
|
40
40
|
type: :runtime
|
|
41
41
|
prerelease: false
|
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.7.
|
|
46
|
+
version: 0.7.2
|
|
47
47
|
- !ruby/object:Gem::Dependency
|
|
48
48
|
name: railties
|
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|