gobby 0.2.5__py3-none-any.whl → 0.2.7__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 (244) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +13 -4
  4. gobby/adapters/codex_impl/__init__.py +28 -0
  5. gobby/adapters/codex_impl/adapter.py +722 -0
  6. gobby/adapters/codex_impl/client.py +679 -0
  7. gobby/adapters/codex_impl/protocol.py +20 -0
  8. gobby/adapters/codex_impl/types.py +68 -0
  9. gobby/agents/definitions.py +11 -1
  10. gobby/agents/isolation.py +395 -0
  11. gobby/agents/runner.py +8 -0
  12. gobby/agents/sandbox.py +261 -0
  13. gobby/agents/spawn.py +42 -287
  14. gobby/agents/spawn_executor.py +385 -0
  15. gobby/agents/spawners/__init__.py +24 -0
  16. gobby/agents/spawners/command_builder.py +189 -0
  17. gobby/agents/spawners/embedded.py +21 -2
  18. gobby/agents/spawners/headless.py +21 -2
  19. gobby/agents/spawners/prompt_manager.py +125 -0
  20. gobby/cli/__init__.py +6 -0
  21. gobby/cli/clones.py +419 -0
  22. gobby/cli/conductor.py +266 -0
  23. gobby/cli/install.py +4 -4
  24. gobby/cli/installers/antigravity.py +3 -9
  25. gobby/cli/installers/claude.py +15 -9
  26. gobby/cli/installers/codex.py +2 -8
  27. gobby/cli/installers/gemini.py +8 -8
  28. gobby/cli/installers/shared.py +175 -13
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/skills.py +858 -0
  31. gobby/cli/tasks/ai.py +0 -440
  32. gobby/cli/tasks/crud.py +44 -6
  33. gobby/cli/tasks/main.py +0 -4
  34. gobby/cli/tui.py +2 -2
  35. gobby/cli/utils.py +12 -5
  36. gobby/clones/__init__.py +13 -0
  37. gobby/clones/git.py +547 -0
  38. gobby/conductor/__init__.py +16 -0
  39. gobby/conductor/alerts.py +135 -0
  40. gobby/conductor/loop.py +164 -0
  41. gobby/conductor/monitors/__init__.py +11 -0
  42. gobby/conductor/monitors/agents.py +116 -0
  43. gobby/conductor/monitors/tasks.py +155 -0
  44. gobby/conductor/pricing.py +234 -0
  45. gobby/conductor/token_tracker.py +160 -0
  46. gobby/config/__init__.py +12 -97
  47. gobby/config/app.py +69 -91
  48. gobby/config/extensions.py +2 -2
  49. gobby/config/features.py +7 -130
  50. gobby/config/search.py +110 -0
  51. gobby/config/servers.py +1 -1
  52. gobby/config/skills.py +43 -0
  53. gobby/config/tasks.py +9 -41
  54. gobby/hooks/__init__.py +0 -13
  55. gobby/hooks/event_handlers.py +188 -2
  56. gobby/hooks/hook_manager.py +50 -4
  57. gobby/hooks/plugins.py +1 -1
  58. gobby/hooks/skill_manager.py +130 -0
  59. gobby/hooks/webhooks.py +1 -1
  60. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  61. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  62. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  63. gobby/llm/claude.py +22 -34
  64. gobby/llm/claude_executor.py +46 -256
  65. gobby/llm/codex_executor.py +59 -291
  66. gobby/llm/executor.py +21 -0
  67. gobby/llm/gemini.py +134 -110
  68. gobby/llm/litellm_executor.py +143 -6
  69. gobby/llm/resolver.py +98 -35
  70. gobby/mcp_proxy/importer.py +62 -4
  71. gobby/mcp_proxy/instructions.py +56 -0
  72. gobby/mcp_proxy/models.py +15 -0
  73. gobby/mcp_proxy/registries.py +68 -8
  74. gobby/mcp_proxy/server.py +33 -3
  75. gobby/mcp_proxy/services/recommendation.py +43 -11
  76. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  77. gobby/mcp_proxy/stdio.py +2 -1
  78. gobby/mcp_proxy/tools/__init__.py +0 -2
  79. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  80. gobby/mcp_proxy/tools/agents.py +31 -731
  81. gobby/mcp_proxy/tools/clones.py +518 -0
  82. gobby/mcp_proxy/tools/memory.py +3 -26
  83. gobby/mcp_proxy/tools/metrics.py +65 -1
  84. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  85. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  86. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  87. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  88. gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
  89. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  90. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  91. gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
  92. gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
  93. gobby/mcp_proxy/tools/skills/__init__.py +616 -0
  94. gobby/mcp_proxy/tools/spawn_agent.py +417 -0
  95. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  96. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  97. gobby/mcp_proxy/tools/task_sync.py +1 -1
  98. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  99. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  100. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  101. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  102. gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
  103. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  104. gobby/mcp_proxy/tools/workflows.py +1 -1
  105. gobby/mcp_proxy/tools/worktrees.py +0 -338
  106. gobby/memory/backends/__init__.py +6 -1
  107. gobby/memory/backends/mem0.py +6 -1
  108. gobby/memory/extractor.py +477 -0
  109. gobby/memory/ingestion/__init__.py +5 -0
  110. gobby/memory/ingestion/multimodal.py +221 -0
  111. gobby/memory/manager.py +73 -285
  112. gobby/memory/search/__init__.py +10 -0
  113. gobby/memory/search/coordinator.py +248 -0
  114. gobby/memory/services/__init__.py +5 -0
  115. gobby/memory/services/crossref.py +142 -0
  116. gobby/prompts/loader.py +5 -2
  117. gobby/runner.py +37 -16
  118. gobby/search/__init__.py +48 -6
  119. gobby/search/backends/__init__.py +159 -0
  120. gobby/search/backends/embedding.py +225 -0
  121. gobby/search/embeddings.py +238 -0
  122. gobby/search/models.py +148 -0
  123. gobby/search/unified.py +496 -0
  124. gobby/servers/http.py +24 -12
  125. gobby/servers/routes/admin.py +294 -0
  126. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  127. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  128. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  129. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  130. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  131. gobby/servers/routes/mcp/hooks.py +1 -1
  132. gobby/servers/routes/mcp/tools.py +48 -1317
  133. gobby/servers/websocket.py +2 -2
  134. gobby/sessions/analyzer.py +2 -0
  135. gobby/sessions/lifecycle.py +1 -1
  136. gobby/sessions/processor.py +10 -0
  137. gobby/sessions/transcripts/base.py +2 -0
  138. gobby/sessions/transcripts/claude.py +79 -10
  139. gobby/skills/__init__.py +91 -0
  140. gobby/skills/loader.py +685 -0
  141. gobby/skills/manager.py +384 -0
  142. gobby/skills/parser.py +286 -0
  143. gobby/skills/search.py +463 -0
  144. gobby/skills/sync.py +119 -0
  145. gobby/skills/updater.py +385 -0
  146. gobby/skills/validator.py +368 -0
  147. gobby/storage/clones.py +378 -0
  148. gobby/storage/database.py +1 -1
  149. gobby/storage/memories.py +43 -13
  150. gobby/storage/migrations.py +162 -201
  151. gobby/storage/sessions.py +116 -7
  152. gobby/storage/skills.py +782 -0
  153. gobby/storage/tasks/_crud.py +4 -4
  154. gobby/storage/tasks/_lifecycle.py +57 -7
  155. gobby/storage/tasks/_manager.py +14 -5
  156. gobby/storage/tasks/_models.py +8 -3
  157. gobby/sync/memories.py +40 -5
  158. gobby/sync/tasks.py +83 -6
  159. gobby/tasks/__init__.py +1 -2
  160. gobby/tasks/external_validator.py +1 -1
  161. gobby/tasks/validation.py +46 -35
  162. gobby/tools/summarizer.py +91 -10
  163. gobby/tui/api_client.py +4 -7
  164. gobby/tui/app.py +5 -3
  165. gobby/tui/screens/orchestrator.py +1 -2
  166. gobby/tui/screens/tasks.py +2 -4
  167. gobby/tui/ws_client.py +1 -1
  168. gobby/utils/daemon_client.py +2 -2
  169. gobby/utils/project_context.py +2 -3
  170. gobby/utils/status.py +13 -0
  171. gobby/workflows/actions.py +221 -1135
  172. gobby/workflows/artifact_actions.py +31 -0
  173. gobby/workflows/autonomous_actions.py +11 -0
  174. gobby/workflows/context_actions.py +93 -1
  175. gobby/workflows/detection_helpers.py +115 -31
  176. gobby/workflows/enforcement/__init__.py +47 -0
  177. gobby/workflows/enforcement/blocking.py +269 -0
  178. gobby/workflows/enforcement/commit_policy.py +283 -0
  179. gobby/workflows/enforcement/handlers.py +269 -0
  180. gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
  181. gobby/workflows/engine.py +13 -2
  182. gobby/workflows/git_utils.py +106 -0
  183. gobby/workflows/lifecycle_evaluator.py +29 -1
  184. gobby/workflows/llm_actions.py +30 -0
  185. gobby/workflows/loader.py +19 -6
  186. gobby/workflows/mcp_actions.py +20 -1
  187. gobby/workflows/memory_actions.py +154 -0
  188. gobby/workflows/safe_evaluator.py +183 -0
  189. gobby/workflows/session_actions.py +44 -0
  190. gobby/workflows/state_actions.py +60 -1
  191. gobby/workflows/stop_signal_actions.py +55 -0
  192. gobby/workflows/summary_actions.py +111 -1
  193. gobby/workflows/task_sync_actions.py +347 -0
  194. gobby/workflows/todo_actions.py +34 -1
  195. gobby/workflows/webhook_actions.py +185 -0
  196. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
  197. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
  198. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
  199. gobby/adapters/codex.py +0 -1292
  200. gobby/install/claude/commands/gobby/bug.md +0 -51
  201. gobby/install/claude/commands/gobby/chore.md +0 -51
  202. gobby/install/claude/commands/gobby/epic.md +0 -52
  203. gobby/install/claude/commands/gobby/eval.md +0 -235
  204. gobby/install/claude/commands/gobby/feat.md +0 -49
  205. gobby/install/claude/commands/gobby/nit.md +0 -52
  206. gobby/install/claude/commands/gobby/ref.md +0 -52
  207. gobby/install/codex/prompts/forget.md +0 -7
  208. gobby/install/codex/prompts/memories.md +0 -7
  209. gobby/install/codex/prompts/recall.md +0 -7
  210. gobby/install/codex/prompts/remember.md +0 -13
  211. gobby/llm/gemini_executor.py +0 -339
  212. gobby/mcp_proxy/tools/session_messages.py +0 -1056
  213. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  214. gobby/prompts/defaults/expansion/system.md +0 -119
  215. gobby/prompts/defaults/expansion/user.md +0 -48
  216. gobby/prompts/defaults/external_validation/agent.md +0 -72
  217. gobby/prompts/defaults/external_validation/external.md +0 -63
  218. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  219. gobby/prompts/defaults/external_validation/system.md +0 -6
  220. gobby/prompts/defaults/features/import_mcp.md +0 -22
  221. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  222. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  223. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  224. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  225. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  226. gobby/prompts/defaults/features/server_description.md +0 -20
  227. gobby/prompts/defaults/features/server_description_system.md +0 -6
  228. gobby/prompts/defaults/features/task_description.md +0 -31
  229. gobby/prompts/defaults/features/task_description_system.md +0 -6
  230. gobby/prompts/defaults/features/tool_summary.md +0 -17
  231. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  232. gobby/prompts/defaults/research/step.md +0 -58
  233. gobby/prompts/defaults/validation/criteria.md +0 -47
  234. gobby/prompts/defaults/validation/validate.md +0 -38
  235. gobby/storage/migrations_legacy.py +0 -1359
  236. gobby/tasks/context.py +0 -747
  237. gobby/tasks/criteria.py +0 -342
  238. gobby/tasks/expansion.py +0 -626
  239. gobby/tasks/prompts/expand.py +0 -327
  240. gobby/tasks/research.py +0 -421
  241. gobby/tasks/tdd.py +0 -352
  242. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
  243. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
  244. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
@@ -3,17 +3,27 @@
3
3
  This module handles schema migrations for the Gobby database.
4
4
 
5
5
  For new databases (version == 0):
6
- The BASELINE_SCHEMA is applied directly, jumping to version 60.
6
+ By default (use_flattened_baseline=True), BASELINE_SCHEMA_V2 is applied,
7
+ jumping directly to version 75 with a clean schema definition.
7
8
 
8
- For existing databases (0 < version < 60):
9
- Legacy migrations are imported from migrations_legacy.py and run incrementally.
9
+ To use the legacy path (use_flattened_baseline=False), the old BASELINE_SCHEMA
10
+ is applied at version 60, followed by incremental migrations.
11
+
12
+ For existing databases (0 < version < 75):
13
+ Upgrade is not supported without legacy migrations (removed).
14
+
15
+ For existing databases (version >= 75):
16
+ Any migrations in MIGRATIONS (v76+) are applied incrementally.
17
+
18
+ Troubleshooting:
19
+ If you experience issues with new database creation, ensure you are starting
20
+ fresh or have a database version >= 60.
10
21
 
11
- For all databases:
12
- Any migrations in MIGRATIONS (v61+) are applied after the baseline/legacy path.
13
22
 
14
23
  To add a new migration:
15
- 1. Add it to the MIGRATIONS list below with version = 61, 62, etc.
24
+ 1. Add it to the MIGRATIONS list below with version = 76, 77, etc.
16
25
  2. Use SQL strings for schema changes, callables for data migrations.
26
+ 3. Also add the migration to BASELINE_SCHEMA_V2 for future fresh installs.
17
27
  """
18
28
 
19
29
  import logging
@@ -23,22 +33,29 @@ from gobby.storage.database import LocalDatabase
23
33
 
24
34
  logger = logging.getLogger(__name__)
25
35
 
36
+
37
+ class MigrationUnsupportedError(Exception):
38
+ """Raised when database version is too old to migrate."""
39
+
40
+ pass
41
+
42
+
26
43
  # Migration can be SQL string or a callable that takes LocalDatabase
27
44
  MigrationAction = str | Callable[[LocalDatabase], None]
28
45
 
29
46
  # Baseline version - the schema state after all legacy migrations
30
- BASELINE_VERSION = 60
47
+ # Baseline version - the schema state at v75 (flattened)
48
+ BASELINE_VERSION = 75
31
49
 
32
- # Baseline schema - applied directly for new databases
33
- # This represents the final schema state after migrations 1-60
50
+ # Baseline schema - flattened from v75 production state
51
+ # This is applied for new databases directly
52
+ # Generated by: sqlite3 ~/.gobby/gobby-hub.db .schema
34
53
  BASELINE_SCHEMA = """
35
- -- Schema version tracking
36
54
  CREATE TABLE schema_version (
37
55
  version INTEGER PRIMARY KEY,
38
56
  applied_at TEXT NOT NULL DEFAULT (datetime('now'))
39
57
  );
40
58
 
41
- -- Projects
42
59
  CREATE TABLE projects (
43
60
  id TEXT PRIMARY KEY,
44
61
  name TEXT NOT NULL UNIQUE,
@@ -57,7 +74,6 @@ VALUES ('00000000-0000-0000-0000-000000000000', '_orphaned', NULL, datetime('now
57
74
  INSERT INTO projects (id, name, repo_path, created_at, updated_at)
58
75
  VALUES ('00000000-0000-0000-0000-000000000001', '_migrated', NULL, datetime('now'), datetime('now'));
59
76
 
60
- -- MCP Servers
61
77
  CREATE TABLE mcp_servers (
62
78
  id TEXT PRIMARY KEY,
63
79
  name TEXT NOT NULL,
@@ -78,7 +94,6 @@ CREATE INDEX idx_mcp_servers_project_id ON mcp_servers(project_id);
78
94
  CREATE INDEX idx_mcp_servers_enabled ON mcp_servers(enabled);
79
95
  CREATE UNIQUE INDEX idx_mcp_servers_name_project ON mcp_servers(name, project_id);
80
96
 
81
- -- Tools
82
97
  CREATE TABLE tools (
83
98
  id TEXT PRIMARY KEY,
84
99
  mcp_server_id TEXT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
@@ -92,7 +107,6 @@ CREATE TABLE tools (
92
107
  CREATE INDEX idx_tools_server_id ON tools(mcp_server_id);
93
108
  CREATE INDEX idx_tools_name ON tools(name);
94
109
 
95
- -- Tool embeddings for semantic search
96
110
  CREATE TABLE tool_embeddings (
97
111
  id INTEGER PRIMARY KEY AUTOINCREMENT,
98
112
  tool_id TEXT NOT NULL REFERENCES tools(id) ON DELETE CASCADE,
@@ -111,7 +125,6 @@ CREATE INDEX idx_tool_embeddings_server ON tool_embeddings(server_name);
111
125
  CREATE INDEX idx_tool_embeddings_project ON tool_embeddings(project_id);
112
126
  CREATE INDEX idx_tool_embeddings_hash ON tool_embeddings(text_hash);
113
127
 
114
- -- Tool schema hashes for incremental re-indexing
115
128
  CREATE TABLE tool_schema_hashes (
116
129
  id INTEGER PRIMARY KEY AUTOINCREMENT,
117
130
  server_name TEXT NOT NULL,
@@ -127,7 +140,6 @@ CREATE INDEX idx_schema_hashes_server ON tool_schema_hashes(server_name);
127
140
  CREATE INDEX idx_schema_hashes_project ON tool_schema_hashes(project_id);
128
141
  CREATE INDEX idx_schema_hashes_verified ON tool_schema_hashes(last_verified_at);
129
142
 
130
- -- Tool metrics
131
143
  CREATE TABLE tool_metrics (
132
144
  id TEXT PRIMARY KEY,
133
145
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
@@ -149,7 +161,6 @@ CREATE INDEX idx_tool_metrics_tool ON tool_metrics(tool_name);
149
161
  CREATE INDEX idx_tool_metrics_call_count ON tool_metrics(call_count DESC);
150
162
  CREATE INDEX idx_tool_metrics_last_called ON tool_metrics(last_called_at);
151
163
 
152
- -- Tool metrics daily aggregates
153
164
  CREATE TABLE tool_metrics_daily (
154
165
  id INTEGER PRIMARY KEY AUTOINCREMENT,
155
166
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
@@ -168,7 +179,6 @@ CREATE INDEX idx_tool_metrics_daily_project ON tool_metrics_daily(project_id);
168
179
  CREATE INDEX idx_tool_metrics_daily_date ON tool_metrics_daily(date);
169
180
  CREATE INDEX idx_tool_metrics_daily_server ON tool_metrics_daily(server_name);
170
181
 
171
- -- Agent runs
172
182
  CREATE TABLE agent_runs (
173
183
  id TEXT PRIMARY KEY,
174
184
  parent_session_id TEXT NOT NULL REFERENCES sessions(id),
@@ -192,7 +202,6 @@ CREATE INDEX idx_agent_runs_child_session ON agent_runs(child_session_id);
192
202
  CREATE INDEX idx_agent_runs_status ON agent_runs(status);
193
203
  CREATE INDEX idx_agent_runs_provider ON agent_runs(provider);
194
204
 
195
- -- Sessions
196
205
  CREATE TABLE sessions (
197
206
  id TEXT PRIMARY KEY,
198
207
  external_id TEXT NOT NULL,
@@ -221,6 +230,8 @@ CREATE TABLE sessions (
221
230
  usage_total_cost_usd REAL DEFAULT 0.0,
222
231
  terminal_context TEXT,
223
232
  seq_num INTEGER,
233
+ model TEXT,
234
+ had_edits BOOLEAN DEFAULT 0,
224
235
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
225
236
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
226
237
  );
@@ -235,10 +246,9 @@ CREATE INDEX idx_sessions_agent_depth ON sessions(agent_depth);
235
246
  CREATE INDEX idx_sessions_spawned_by ON sessions(spawned_by_agent_id);
236
247
  CREATE INDEX idx_sessions_workflow ON sessions(workflow_name);
237
248
  CREATE INDEX idx_sessions_agent_run ON sessions(agent_run_id);
238
- CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(seq_num);
249
+ CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(project_id, seq_num);
239
250
  CREATE UNIQUE INDEX idx_sessions_unique ON sessions(external_id, machine_id, source, project_id);
240
251
 
241
- -- Session messages
242
252
  CREATE TABLE session_messages (
243
253
  id INTEGER PRIMARY KEY AUTOINCREMENT,
244
254
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -259,7 +269,6 @@ CREATE INDEX idx_session_messages_role ON session_messages(role);
259
269
  CREATE INDEX idx_session_messages_timestamp ON session_messages(timestamp);
260
270
  CREATE INDEX idx_session_messages_tool ON session_messages(tool_name);
261
271
 
262
- -- Session message processing state
263
272
  CREATE TABLE session_message_state (
264
273
  session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
265
274
  last_byte_offset INTEGER DEFAULT 0,
@@ -269,7 +278,6 @@ CREATE TABLE session_message_state (
269
278
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
270
279
  );
271
280
 
272
- -- Session artifacts with FTS
273
281
  CREATE TABLE session_artifacts (
274
282
  id TEXT PRIMARY KEY,
275
283
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -286,7 +294,6 @@ CREATE INDEX idx_session_artifacts_type ON session_artifacts(artifact_type);
286
294
  CREATE INDEX idx_session_artifacts_created ON session_artifacts(created_at);
287
295
  CREATE VIRTUAL TABLE session_artifacts_fts USING fts5(id UNINDEXED, content);
288
296
 
289
- -- Session stop signals for autonomous stop
290
297
  CREATE TABLE session_stop_signals (
291
298
  session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
292
299
  source TEXT NOT NULL,
@@ -297,7 +304,6 @@ CREATE TABLE session_stop_signals (
297
304
  CREATE INDEX idx_stop_signals_pending ON session_stop_signals(acknowledged_at)
298
305
  WHERE acknowledged_at IS NULL;
299
306
 
300
- -- Loop progress for autonomous progress tracking
301
307
  CREATE TABLE loop_progress (
302
308
  id INTEGER PRIMARY KEY AUTOINCREMENT,
303
309
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -311,7 +317,6 @@ CREATE INDEX idx_loop_progress_session ON loop_progress(session_id, recorded_at
311
317
  CREATE INDEX idx_loop_progress_high_value ON loop_progress(session_id, is_high_value, recorded_at DESC)
312
318
  WHERE is_high_value = 1;
313
319
 
314
- -- Tasks
315
320
  CREATE TABLE tasks (
316
321
  id TEXT PRIMARY KEY,
317
322
  project_id TEXT NOT NULL REFERENCES projects(id),
@@ -358,7 +363,7 @@ CREATE TABLE tasks (
358
363
  agent_name TEXT,
359
364
  reference_doc TEXT,
360
365
  is_expanded INTEGER DEFAULT 0,
361
- is_tdd_applied INTEGER DEFAULT 0,
366
+ expansion_status TEXT DEFAULT 'none',
362
367
  requires_user_review INTEGER DEFAULT 0,
363
368
  accepted_by_user INTEGER DEFAULT 0,
364
369
  created_at TEXT NOT NULL,
@@ -374,7 +379,6 @@ CREATE INDEX idx_tasks_closed_session ON tasks(closed_in_session_id);
374
379
  CREATE UNIQUE INDEX idx_tasks_seq_num ON tasks(project_id, seq_num);
375
380
  CREATE INDEX idx_tasks_path_cache ON tasks(path_cache);
376
381
 
377
- -- Task dependencies
378
382
  CREATE TABLE task_dependencies (
379
383
  id INTEGER PRIMARY KEY AUTOINCREMENT,
380
384
  task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
@@ -386,7 +390,6 @@ CREATE TABLE task_dependencies (
386
390
  CREATE INDEX idx_deps_task ON task_dependencies(task_id);
387
391
  CREATE INDEX idx_deps_depends_on ON task_dependencies(depends_on);
388
392
 
389
- -- Session-task linkages
390
393
  CREATE TABLE session_tasks (
391
394
  id INTEGER PRIMARY KEY AUTOINCREMENT,
392
395
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -398,7 +401,6 @@ CREATE TABLE session_tasks (
398
401
  CREATE INDEX idx_session_tasks_session ON session_tasks(session_id);
399
402
  CREATE INDEX idx_session_tasks_task ON session_tasks(task_id);
400
403
 
401
- -- Task validation history
402
404
  CREATE TABLE task_validation_history (
403
405
  id INTEGER PRIMARY KEY AUTOINCREMENT,
404
406
  task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
@@ -413,7 +415,6 @@ CREATE TABLE task_validation_history (
413
415
  );
414
416
  CREATE INDEX idx_validation_history_task ON task_validation_history(task_id);
415
417
 
416
- -- Task selection history for stuck detection
417
418
  CREATE TABLE task_selection_history (
418
419
  id INTEGER PRIMARY KEY AUTOINCREMENT,
419
420
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -424,7 +425,6 @@ CREATE TABLE task_selection_history (
424
425
  CREATE INDEX idx_task_selection_session ON task_selection_history(session_id, selected_at DESC);
425
426
  CREATE INDEX idx_task_selection_task ON task_selection_history(session_id, task_id, selected_at DESC);
426
427
 
427
- -- Workflow states
428
428
  CREATE TABLE workflow_states (
429
429
  session_id TEXT PRIMARY KEY,
430
430
  workflow_name TEXT NOT NULL,
@@ -445,7 +445,6 @@ CREATE TABLE workflow_states (
445
445
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
446
446
  );
447
447
 
448
- -- Workflow audit log
449
448
  CREATE TABLE workflow_audit_log (
450
449
  id INTEGER PRIMARY KEY AUTOINCREMENT,
451
450
  session_id TEXT NOT NULL,
@@ -465,7 +464,6 @@ CREATE INDEX idx_audit_timestamp ON workflow_audit_log(timestamp);
465
464
  CREATE INDEX idx_audit_event_type ON workflow_audit_log(event_type);
466
465
  CREATE INDEX idx_audit_result ON workflow_audit_log(result);
467
466
 
468
- -- Memories
469
467
  CREATE TABLE memories (
470
468
  id TEXT PRIMARY KEY,
471
469
  project_id TEXT REFERENCES projects(id),
@@ -485,7 +483,6 @@ CREATE INDEX idx_memories_project ON memories(project_id);
485
483
  CREATE INDEX idx_memories_type ON memories(memory_type);
486
484
  CREATE INDEX idx_memories_importance ON memories(importance DESC);
487
485
 
488
- -- Session-memory linkages
489
486
  CREATE TABLE session_memories (
490
487
  id INTEGER PRIMARY KEY AUTOINCREMENT,
491
488
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -497,7 +494,6 @@ CREATE TABLE session_memories (
497
494
  CREATE INDEX idx_session_memories_session ON session_memories(session_id);
498
495
  CREATE INDEX idx_session_memories_memory ON session_memories(memory_id);
499
496
 
500
- -- Memory cross-references
501
497
  CREATE TABLE memory_crossrefs (
502
498
  source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
503
499
  target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
@@ -509,7 +505,6 @@ CREATE INDEX idx_crossrefs_source ON memory_crossrefs(source_id);
509
505
  CREATE INDEX idx_crossrefs_target ON memory_crossrefs(target_id);
510
506
  CREATE INDEX idx_crossrefs_similarity ON memory_crossrefs(similarity DESC);
511
507
 
512
- -- Worktrees
513
508
  CREATE TABLE worktrees (
514
509
  id TEXT PRIMARY KEY,
515
510
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
@@ -531,7 +526,6 @@ CREATE INDEX idx_worktrees_session ON worktrees(agent_session_id);
531
526
  CREATE UNIQUE INDEX idx_worktrees_branch ON worktrees(project_id, branch_name);
532
527
  CREATE UNIQUE INDEX idx_worktrees_path ON worktrees(worktree_path);
533
528
 
534
- -- Merge resolutions
535
529
  CREATE TABLE merge_resolutions (
536
530
  id TEXT PRIMARY KEY,
537
531
  worktree_id TEXT NOT NULL REFERENCES worktrees(id) ON DELETE CASCADE,
@@ -547,7 +541,6 @@ CREATE INDEX idx_merge_resolutions_status ON merge_resolutions(status);
547
541
  CREATE INDEX idx_merge_resolutions_source_branch ON merge_resolutions(source_branch);
548
542
  CREATE INDEX idx_merge_resolutions_target_branch ON merge_resolutions(target_branch);
549
543
 
550
- -- Merge conflicts
551
544
  CREATE TABLE merge_conflicts (
552
545
  id TEXT PRIMARY KEY,
553
546
  resolution_id TEXT NOT NULL REFERENCES merge_resolutions(id) ON DELETE CASCADE,
@@ -562,179 +555,148 @@ CREATE TABLE merge_conflicts (
562
555
  CREATE INDEX idx_merge_conflicts_resolution ON merge_conflicts(resolution_id);
563
556
  CREATE INDEX idx_merge_conflicts_file_path ON merge_conflicts(file_path);
564
557
  CREATE INDEX idx_merge_conflicts_status ON merge_conflicts(status);
558
+
559
+ CREATE TABLE inter_session_messages (
560
+ id TEXT PRIMARY KEY,
561
+ from_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
562
+ to_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
563
+ content TEXT NOT NULL,
564
+ priority TEXT NOT NULL DEFAULT 'normal',
565
+ sent_at TEXT NOT NULL,
566
+ read_at TEXT
567
+ );
568
+ CREATE INDEX idx_inter_session_messages_from_session ON inter_session_messages(from_session);
569
+ CREATE INDEX idx_inter_session_messages_to_session ON inter_session_messages(to_session);
570
+ CREATE INDEX idx_inter_session_messages_unread ON inter_session_messages(to_session, read_at)
571
+ WHERE read_at IS NULL;
572
+
573
+ CREATE TABLE skills (
574
+ id TEXT PRIMARY KEY,
575
+ name TEXT NOT NULL,
576
+ description TEXT NOT NULL,
577
+ content TEXT NOT NULL,
578
+ version TEXT,
579
+ license TEXT,
580
+ compatibility TEXT,
581
+ allowed_tools TEXT,
582
+ metadata TEXT,
583
+ source_path TEXT,
584
+ source_type TEXT,
585
+ source_ref TEXT,
586
+ enabled INTEGER DEFAULT 1,
587
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
588
+ created_at TEXT NOT NULL,
589
+ updated_at TEXT NOT NULL
590
+ );
591
+ CREATE INDEX idx_skills_name ON skills(name);
592
+ CREATE INDEX idx_skills_project_id ON skills(project_id);
593
+ CREATE INDEX idx_skills_enabled ON skills(enabled);
594
+ CREATE UNIQUE INDEX idx_skills_name_project ON skills(name, project_id);
595
+ CREATE UNIQUE INDEX idx_skills_name_global ON skills(name) WHERE project_id IS NULL;
596
+
597
+ CREATE TABLE clones (
598
+ id TEXT PRIMARY KEY,
599
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
600
+ branch_name TEXT NOT NULL,
601
+ clone_path TEXT NOT NULL,
602
+ base_branch TEXT DEFAULT 'main',
603
+ task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
604
+ agent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
605
+ status TEXT DEFAULT 'active',
606
+ remote_url TEXT,
607
+ last_sync_at TEXT,
608
+ cleanup_after TEXT,
609
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
610
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
611
+ );
612
+ CREATE INDEX idx_clones_project ON clones(project_id);
613
+ CREATE INDEX idx_clones_status ON clones(status);
614
+ CREATE INDEX idx_clones_task ON clones(task_id);
615
+ CREATE INDEX idx_clones_session ON clones(agent_session_id);
616
+ CREATE UNIQUE INDEX idx_clones_path ON clones(clone_path);
565
617
  """
566
618
 
567
619
  # Future migrations (v61+)
568
620
  # Add new migrations here. Do not modify the baseline schema above.
569
621
 
570
622
 
571
- def _migrate_test_strategy_to_category(db: LocalDatabase) -> None:
572
- """Rename test_strategy column to category if it exists.
623
+ def _migrate_session_seq_num_project_scoped(db: LocalDatabase) -> None:
624
+ """Change sessions.seq_num index from global to project-scoped.
573
625
 
574
- This is a no-op for fresh databases that already have category in the baseline schema.
575
- Only runs the rename for databases upgraded from versions before the rename.
626
+ This allows different projects to have independent session numbering (#1, #2, etc.)
627
+ matching how tasks already work.
576
628
  """
577
- # Check if test_strategy column exists
578
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
579
- if row and "test_strategy" in row["sql"].lower():
580
- db.execute("ALTER TABLE tasks RENAME COLUMN test_strategy TO category")
581
- logger.info("Renamed test_strategy column to category")
582
- else:
583
- logger.debug("test_strategy column not found (fresh database), skipping rename")
584
-
585
-
586
- def _migrate_add_agent_name(db: LocalDatabase) -> None:
587
- """Add agent_name column to tasks table for agent configuration."""
588
- # Check if agent_name column already exists (fresh database)
589
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
590
- if row and "agent_name" not in row["sql"].lower():
591
- db.execute("ALTER TABLE tasks ADD COLUMN agent_name TEXT")
592
- logger.info("Added agent_name column to tasks table")
593
- else:
594
- logger.debug("agent_name column already exists, skipping")
595
-
596
-
597
- def _migrate_add_reference_doc(db: LocalDatabase) -> None:
598
- """Add reference_doc column to tasks table for spec traceability."""
599
- # Check if reference_doc column already exists (fresh database)
600
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
601
- if row and "reference_doc" not in row["sql"].lower():
602
- db.execute("ALTER TABLE tasks ADD COLUMN reference_doc TEXT")
603
- logger.info("Added reference_doc column to tasks table")
604
- else:
605
- logger.debug("reference_doc column already exists, skipping")
606
-
607
-
608
- def _migrate_add_boolean_columns(db: LocalDatabase) -> None:
609
- """Add is_enriched, is_expanded, is_tdd_applied columns to tasks table."""
610
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
629
+ # Check if the old global index exists
630
+ row = db.fetchone(
631
+ "SELECT sql FROM sqlite_master WHERE type='index' AND name='idx_sessions_seq_num'"
632
+ )
611
633
  if not row:
634
+ logger.debug("idx_sessions_seq_num index does not exist, skipping")
612
635
  return
613
636
 
614
- sql_lower = row["sql"].lower()
615
-
616
- # Add each column if it doesn't exist
617
- if "is_enriched" not in sql_lower:
618
- db.execute("ALTER TABLE tasks ADD COLUMN is_enriched INTEGER DEFAULT 0")
619
- logger.info("Added is_enriched column to tasks table")
620
-
621
- if "is_expanded" not in sql_lower:
622
- db.execute("ALTER TABLE tasks ADD COLUMN is_expanded INTEGER DEFAULT 0")
623
- logger.info("Added is_expanded column to tasks table")
624
-
625
- if "is_tdd_applied" not in sql_lower:
626
- db.execute("ALTER TABLE tasks ADD COLUMN is_tdd_applied INTEGER DEFAULT 0")
627
- logger.info("Added is_tdd_applied column to tasks table")
628
-
629
-
630
- def _migrate_add_review_columns(db: LocalDatabase) -> None:
631
- """Add requires_user_review and accepted_by_user columns for review status support."""
632
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
633
- if not row:
637
+ # Check if it's already project-scoped (contains 'project_id')
638
+ if "project_id" in (row["sql"] or "").lower():
639
+ logger.debug("idx_sessions_seq_num is already project-scoped, skipping")
634
640
  return
635
641
 
636
- sql_lower = row["sql"].lower()
637
-
638
- if "requires_user_review" not in sql_lower:
639
- db.execute("ALTER TABLE tasks ADD COLUMN requires_user_review INTEGER DEFAULT 0")
640
- logger.info("Added requires_user_review column to tasks table")
641
-
642
- if "accepted_by_user" not in sql_lower:
643
- db.execute("ALTER TABLE tasks ADD COLUMN accepted_by_user INTEGER DEFAULT 0")
644
- logger.info("Added accepted_by_user column to tasks table")
645
-
646
-
647
- def _migrate_drop_is_enriched(db: LocalDatabase) -> None:
648
- """Drop deprecated is_enriched column from tasks table.
649
-
650
- The is_enriched flag is no longer used after the Task Expansion V3 simplification.
651
- SQLite 3.35.0+ supports ALTER TABLE DROP COLUMN.
652
- """
653
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'")
654
- if not row:
655
- return
642
+ # Drop the old global index and create new project-scoped index atomically
643
+ with db.transaction() as conn:
644
+ conn.execute("DROP INDEX IF EXISTS idx_sessions_seq_num")
645
+ conn.execute("CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(project_id, seq_num)")
656
646
 
657
- if "is_enriched" in row["sql"].lower():
658
- try:
659
- db.execute("ALTER TABLE tasks DROP COLUMN is_enriched")
660
- logger.info("Dropped is_enriched column from tasks table")
661
- except Exception as e:
662
- # SQLite < 3.35.0 doesn't support DROP COLUMN
663
- # Column will remain but be unused - not a problem
664
- logger.warning(f"Could not drop is_enriched column (SQLite < 3.35?): {e}")
647
+ logger.info("Changed sessions.seq_num index from global to project-scoped")
665
648
 
666
649
 
667
- def _migrate_add_inter_session_messages(db: LocalDatabase) -> None:
668
- """Add inter_session_messages table for parent-child agent communication.
650
+ def _migrate_backfill_session_seq_num_per_project(db: LocalDatabase) -> None:
651
+ """Re-backfill session seq_num values to be per-project.
669
652
 
670
- This table enables asynchronous messaging between agent sessions,
671
- allowing parent agents to send instructions to child agents and
672
- receive status updates back.
653
+ This migration re-numbers sessions so each project has independent numbering
654
+ starting from 1. Required after changing the index to be project-scoped.
673
655
  """
674
- # Check if table already exists (fresh database with baseline)
675
- row = db.fetchone(
676
- "SELECT name FROM sqlite_master WHERE type='table' AND name='inter_session_messages'"
656
+ # Get all sessions grouped by project, ordered by created_at
657
+ sessions = db.fetchall(
658
+ """
659
+ SELECT id, project_id FROM sessions
660
+ ORDER BY project_id, created_at ASC, id ASC
661
+ """
677
662
  )
678
- if row:
679
- logger.debug("inter_session_messages table already exists, skipping")
663
+
664
+ if not sessions:
665
+ logger.debug("No sessions to re-number")
680
666
  return
681
667
 
682
- # Create the table
683
- db.execute("""
684
- CREATE TABLE inter_session_messages (
685
- id TEXT PRIMARY KEY,
686
- from_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
687
- to_session TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
688
- content TEXT NOT NULL,
689
- priority TEXT NOT NULL DEFAULT 'normal',
690
- sent_at TEXT NOT NULL,
691
- read_at TEXT
692
- )
693
- """)
668
+ # Wrap the entire re-numbering in a transaction for atomicity
669
+ with db.transaction() as conn:
670
+ # First, clear all seq_num values to avoid unique constraint violations
671
+ # when the existing seq_num order doesn't match created_at order
672
+ conn.execute("UPDATE sessions SET seq_num = NULL")
694
673
 
695
- # Create indexes for efficient querying
696
- db.execute(
697
- "CREATE INDEX idx_inter_session_messages_from_session ON inter_session_messages(from_session)"
698
- )
699
- db.execute(
700
- "CREATE INDEX idx_inter_session_messages_to_session ON inter_session_messages(to_session)"
701
- )
702
- db.execute(
703
- "CREATE INDEX idx_inter_session_messages_unread ON inter_session_messages(to_session, read_at) "
704
- "WHERE read_at IS NULL"
705
- )
674
+ # Assign seq_num per project
675
+ current_project: str | None = None
676
+ seq_num = 0
677
+ updated = 0
706
678
 
707
- logger.info("Created inter_session_messages table with indexes")
679
+ for session in sessions:
680
+ if session["project_id"] != current_project:
681
+ current_project = session["project_id"]
682
+ seq_num = 1
683
+ else:
684
+ seq_num += 1
708
685
 
686
+ conn.execute(
687
+ "UPDATE sessions SET seq_num = ? WHERE id = ?",
688
+ (seq_num, session["id"]),
689
+ )
690
+ updated += 1
709
691
 
710
- def _migrate_add_media_column(db: LocalDatabase) -> None:
711
- """Add media column to memories table for multimodal support."""
712
- # Check if media column already exists (fresh database from baseline)
713
- row = db.fetchone("SELECT sql FROM sqlite_master WHERE type='table' AND name='memories'")
714
- if row and "media" not in row["sql"].lower():
715
- db.execute("ALTER TABLE memories ADD COLUMN media TEXT")
716
- logger.info("Added media column to memories table")
717
- else:
718
- logger.debug("media column already exists, skipping")
692
+ logger.info(f"Re-numbered {updated} sessions with per-project seq_num")
719
693
 
720
694
 
721
695
  MIGRATIONS: list[tuple[int, str, MigrationAction]] = [
722
- # TDD Expansion Restructure: Rename test_strategy to category
723
- (61, "Rename test_strategy to category", _migrate_test_strategy_to_category),
724
- # TDD Expansion Restructure: Add agent_name column
725
- (62, "Add agent_name column to tasks", _migrate_add_agent_name),
726
- # TDD Expansion Restructure: Add reference_doc column
727
- (63, "Add reference_doc column to tasks", _migrate_add_reference_doc),
728
- # TDD Expansion Restructure: Add boolean columns for idempotent operations
729
- (64, "Add boolean columns to tasks", _migrate_add_boolean_columns),
730
- # Review status: Add columns for HITL review workflow
731
- (65, "Add review columns to tasks", _migrate_add_review_columns),
732
- # Task Expansion V3: Drop unused is_enriched column
733
- (66, "Drop is_enriched column from tasks", _migrate_drop_is_enriched),
734
- # Inter-session messaging: Add table for parent-child agent communication
735
- (67, "Add inter_session_messages table", _migrate_add_inter_session_messages),
736
- # Memory V3 Phase 2: Add media column for multimodal support
737
- (68, "Add media column to memories", _migrate_add_media_column),
696
+ # Project-scoped session refs: Change seq_num index from global to project-scoped
697
+ (76, "Make sessions.seq_num project-scoped", _migrate_session_seq_num_project_scoped),
698
+ # Project-scoped session refs: Re-backfill seq_num per project
699
+ (77, "Backfill sessions.seq_num per project", _migrate_backfill_session_seq_num_per_project),
738
700
  ]
739
701
 
740
702
 
@@ -748,8 +710,8 @@ def get_current_version(db: LocalDatabase) -> int:
748
710
 
749
711
 
750
712
  def _apply_baseline(db: LocalDatabase) -> None:
751
- """Apply baseline schema for new databases."""
752
- logger.info("Applying baseline schema (v60)")
713
+ """Apply baseline schema for new databases (flattened at v75)."""
714
+ logger.info("Applying baseline schema (v75)")
753
715
 
754
716
  # Execute baseline schema
755
717
  for statement in BASELINE_SCHEMA.strip().split(";"):
@@ -821,13 +783,10 @@ def run_migrations(db: LocalDatabase) -> int:
821
783
  Run pending migrations.
822
784
 
823
785
  For new databases (version == 0):
824
- Applies baseline schema directly, jumping to version 60.
825
-
826
- For existing databases (0 < version < 60):
827
- Imports and runs legacy migrations incrementally.
786
+ - Applies baseline schema (v75) directly.
828
787
 
829
- For all databases:
830
- Runs any new migrations (v61+) after baseline/legacy path.
788
+ For existing databases:
789
+ - Runs any new migrations from v76 onwards.
831
790
 
832
791
  Args:
833
792
  db: LocalDatabase instance
@@ -839,20 +798,22 @@ def run_migrations(db: LocalDatabase) -> int:
839
798
  total_applied = 0
840
799
 
841
800
  if current_version == 0:
842
- # New database: apply baseline schema directly
801
+ # New database with flattened baseline: apply schema directly
802
+ logger.info("Using flattened baseline for new database")
843
803
  _apply_baseline(db)
844
804
  total_applied = 1
845
805
  current_version = BASELINE_VERSION
846
806
  elif current_version < BASELINE_VERSION:
847
- # Existing database needing legacy migrations
848
- # Lazy import to avoid loading legacy code for new databases
849
- from gobby.storage.migrations_legacy import LEGACY_MIGRATIONS
850
-
851
- applied = _run_migration_list(db, current_version, LEGACY_MIGRATIONS)
852
- total_applied += applied
853
- current_version = get_current_version(db)
807
+ # Unsupported: Pre-v75 database without local migrations
808
+ # Since we removed legacy migrations, we can't upgrade.
809
+ msg = (
810
+ f"Database version {current_version} is older than baseline "
811
+ f"{BASELINE_VERSION}. Upgrade not supported without legacy migrations."
812
+ )
813
+ logger.error(msg)
814
+ raise MigrationUnsupportedError(msg)
854
815
 
855
- # Run any new migrations (v61+)
816
+ # Run any new migrations (v76+)
856
817
  if MIGRATIONS:
857
818
  applied = _run_migration_list(db, current_version, MIGRATIONS)
858
819
  total_applied += applied