gobby 0.2.5__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 (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. gobby-0.2.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,860 @@
1
+ """Database migrations for local storage.
2
+
3
+ This module handles schema migrations for the Gobby database.
4
+
5
+ For new databases (version == 0):
6
+ The BASELINE_SCHEMA is applied directly, jumping to version 60.
7
+
8
+ For existing databases (0 < version < 60):
9
+ Legacy migrations are imported from migrations_legacy.py and run incrementally.
10
+
11
+ For all databases:
12
+ Any migrations in MIGRATIONS (v61+) are applied after the baseline/legacy path.
13
+
14
+ To add a new migration:
15
+ 1. Add it to the MIGRATIONS list below with version = 61, 62, etc.
16
+ 2. Use SQL strings for schema changes, callables for data migrations.
17
+ """
18
+
19
+ import logging
20
+ from collections.abc import Callable
21
+
22
+ from gobby.storage.database import LocalDatabase
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Migration can be SQL string or a callable that takes LocalDatabase
27
+ MigrationAction = str | Callable[[LocalDatabase], None]
28
+
29
+ # Baseline version - the schema state after all legacy migrations
30
+ BASELINE_VERSION = 60
31
+
32
+ # Baseline schema - applied directly for new databases
33
+ # This represents the final schema state after migrations 1-60
34
+ BASELINE_SCHEMA = """
35
+ -- Schema version tracking
36
+ CREATE TABLE schema_version (
37
+ version INTEGER PRIMARY KEY,
38
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
39
+ );
40
+
41
+ -- Projects
42
+ CREATE TABLE projects (
43
+ id TEXT PRIMARY KEY,
44
+ name TEXT NOT NULL UNIQUE,
45
+ repo_path TEXT,
46
+ github_url TEXT,
47
+ github_repo TEXT,
48
+ linear_team_id TEXT,
49
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
50
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
51
+ );
52
+ CREATE INDEX idx_projects_name ON projects(name);
53
+
54
+ -- Placeholder projects for orphaned/migrated data
55
+ INSERT INTO projects (id, name, repo_path, created_at, updated_at)
56
+ VALUES ('00000000-0000-0000-0000-000000000000', '_orphaned', NULL, datetime('now'), datetime('now'));
57
+ INSERT INTO projects (id, name, repo_path, created_at, updated_at)
58
+ VALUES ('00000000-0000-0000-0000-000000000001', '_migrated', NULL, datetime('now'), datetime('now'));
59
+
60
+ -- MCP Servers
61
+ CREATE TABLE mcp_servers (
62
+ id TEXT PRIMARY KEY,
63
+ name TEXT NOT NULL,
64
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
65
+ transport TEXT NOT NULL,
66
+ url TEXT,
67
+ command TEXT,
68
+ args TEXT,
69
+ env TEXT,
70
+ headers TEXT,
71
+ enabled INTEGER DEFAULT 1,
72
+ description TEXT,
73
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
74
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
75
+ );
76
+ CREATE INDEX idx_mcp_servers_name ON mcp_servers(name);
77
+ CREATE INDEX idx_mcp_servers_project_id ON mcp_servers(project_id);
78
+ CREATE INDEX idx_mcp_servers_enabled ON mcp_servers(enabled);
79
+ CREATE UNIQUE INDEX idx_mcp_servers_name_project ON mcp_servers(name, project_id);
80
+
81
+ -- Tools
82
+ CREATE TABLE tools (
83
+ id TEXT PRIMARY KEY,
84
+ mcp_server_id TEXT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
85
+ name TEXT NOT NULL,
86
+ description TEXT,
87
+ input_schema TEXT,
88
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
89
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
90
+ UNIQUE(mcp_server_id, name)
91
+ );
92
+ CREATE INDEX idx_tools_server_id ON tools(mcp_server_id);
93
+ CREATE INDEX idx_tools_name ON tools(name);
94
+
95
+ -- Tool embeddings for semantic search
96
+ CREATE TABLE tool_embeddings (
97
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
98
+ tool_id TEXT NOT NULL REFERENCES tools(id) ON DELETE CASCADE,
99
+ server_name TEXT NOT NULL,
100
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
101
+ embedding BLOB NOT NULL,
102
+ embedding_model TEXT NOT NULL,
103
+ embedding_dim INTEGER NOT NULL,
104
+ text_hash TEXT NOT NULL,
105
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
106
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
107
+ UNIQUE(tool_id)
108
+ );
109
+ CREATE INDEX idx_tool_embeddings_tool ON tool_embeddings(tool_id);
110
+ CREATE INDEX idx_tool_embeddings_server ON tool_embeddings(server_name);
111
+ CREATE INDEX idx_tool_embeddings_project ON tool_embeddings(project_id);
112
+ CREATE INDEX idx_tool_embeddings_hash ON tool_embeddings(text_hash);
113
+
114
+ -- Tool schema hashes for incremental re-indexing
115
+ CREATE TABLE tool_schema_hashes (
116
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
117
+ server_name TEXT NOT NULL,
118
+ tool_name TEXT NOT NULL,
119
+ project_id TEXT NOT NULL,
120
+ schema_hash TEXT NOT NULL,
121
+ last_verified_at TEXT NOT NULL DEFAULT (datetime('now')),
122
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
123
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
124
+ UNIQUE(project_id, server_name, tool_name)
125
+ );
126
+ CREATE INDEX idx_schema_hashes_server ON tool_schema_hashes(server_name);
127
+ CREATE INDEX idx_schema_hashes_project ON tool_schema_hashes(project_id);
128
+ CREATE INDEX idx_schema_hashes_verified ON tool_schema_hashes(last_verified_at);
129
+
130
+ -- Tool metrics
131
+ CREATE TABLE tool_metrics (
132
+ id TEXT PRIMARY KEY,
133
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
134
+ server_name TEXT NOT NULL,
135
+ tool_name TEXT NOT NULL,
136
+ call_count INTEGER NOT NULL DEFAULT 0,
137
+ success_count INTEGER NOT NULL DEFAULT 0,
138
+ failure_count INTEGER NOT NULL DEFAULT 0,
139
+ total_latency_ms REAL NOT NULL DEFAULT 0,
140
+ avg_latency_ms REAL,
141
+ last_called_at TEXT,
142
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
143
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
144
+ UNIQUE(project_id, server_name, tool_name)
145
+ );
146
+ CREATE INDEX idx_tool_metrics_project ON tool_metrics(project_id);
147
+ CREATE INDEX idx_tool_metrics_server ON tool_metrics(server_name);
148
+ CREATE INDEX idx_tool_metrics_tool ON tool_metrics(tool_name);
149
+ CREATE INDEX idx_tool_metrics_call_count ON tool_metrics(call_count DESC);
150
+ CREATE INDEX idx_tool_metrics_last_called ON tool_metrics(last_called_at);
151
+
152
+ -- Tool metrics daily aggregates
153
+ CREATE TABLE tool_metrics_daily (
154
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
155
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
156
+ server_name TEXT NOT NULL,
157
+ tool_name TEXT NOT NULL,
158
+ date TEXT NOT NULL,
159
+ call_count INTEGER NOT NULL DEFAULT 0,
160
+ success_count INTEGER NOT NULL DEFAULT 0,
161
+ failure_count INTEGER NOT NULL DEFAULT 0,
162
+ total_latency_ms REAL NOT NULL DEFAULT 0,
163
+ avg_latency_ms REAL,
164
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
165
+ UNIQUE(project_id, server_name, tool_name, date)
166
+ );
167
+ CREATE INDEX idx_tool_metrics_daily_project ON tool_metrics_daily(project_id);
168
+ CREATE INDEX idx_tool_metrics_daily_date ON tool_metrics_daily(date);
169
+ CREATE INDEX idx_tool_metrics_daily_server ON tool_metrics_daily(server_name);
170
+
171
+ -- Agent runs
172
+ CREATE TABLE agent_runs (
173
+ id TEXT PRIMARY KEY,
174
+ parent_session_id TEXT NOT NULL REFERENCES sessions(id),
175
+ child_session_id TEXT REFERENCES sessions(id),
176
+ workflow_name TEXT,
177
+ provider TEXT NOT NULL,
178
+ model TEXT,
179
+ status TEXT NOT NULL DEFAULT 'pending',
180
+ prompt TEXT NOT NULL,
181
+ result TEXT,
182
+ error TEXT,
183
+ tool_calls_count INTEGER DEFAULT 0,
184
+ turns_used INTEGER DEFAULT 0,
185
+ started_at TEXT,
186
+ completed_at TEXT,
187
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
188
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
189
+ );
190
+ CREATE INDEX idx_agent_runs_parent_session ON agent_runs(parent_session_id);
191
+ CREATE INDEX idx_agent_runs_child_session ON agent_runs(child_session_id);
192
+ CREATE INDEX idx_agent_runs_status ON agent_runs(status);
193
+ CREATE INDEX idx_agent_runs_provider ON agent_runs(provider);
194
+
195
+ -- Sessions
196
+ CREATE TABLE sessions (
197
+ id TEXT PRIMARY KEY,
198
+ external_id TEXT NOT NULL,
199
+ machine_id TEXT NOT NULL,
200
+ source TEXT NOT NULL,
201
+ project_id TEXT NOT NULL REFERENCES projects(id),
202
+ title TEXT,
203
+ status TEXT DEFAULT 'active',
204
+ jsonl_path TEXT,
205
+ summary_path TEXT,
206
+ summary_markdown TEXT,
207
+ compact_markdown TEXT,
208
+ git_branch TEXT,
209
+ parent_session_id TEXT REFERENCES sessions(id),
210
+ transcript_processed BOOLEAN DEFAULT FALSE,
211
+ agent_depth INTEGER DEFAULT 0,
212
+ spawned_by_agent_id TEXT,
213
+ workflow_name TEXT,
214
+ agent_run_id TEXT REFERENCES agent_runs(id) ON DELETE SET NULL,
215
+ context_injected INTEGER DEFAULT 0,
216
+ original_prompt TEXT,
217
+ usage_input_tokens INTEGER DEFAULT 0,
218
+ usage_output_tokens INTEGER DEFAULT 0,
219
+ usage_cache_creation_tokens INTEGER DEFAULT 0,
220
+ usage_cache_read_tokens INTEGER DEFAULT 0,
221
+ usage_total_cost_usd REAL DEFAULT 0.0,
222
+ terminal_context TEXT,
223
+ seq_num INTEGER,
224
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
225
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
226
+ );
227
+ CREATE INDEX idx_sessions_external_id ON sessions(external_id);
228
+ CREATE INDEX idx_sessions_machine_id ON sessions(machine_id);
229
+ CREATE INDEX idx_sessions_source ON sessions(source);
230
+ CREATE INDEX idx_sessions_status ON sessions(status);
231
+ CREATE INDEX idx_sessions_project_id ON sessions(project_id);
232
+ CREATE INDEX idx_sessions_pending_transcript ON sessions(status, transcript_processed)
233
+ WHERE status = 'expired' AND transcript_processed = FALSE;
234
+ CREATE INDEX idx_sessions_agent_depth ON sessions(agent_depth);
235
+ CREATE INDEX idx_sessions_spawned_by ON sessions(spawned_by_agent_id);
236
+ CREATE INDEX idx_sessions_workflow ON sessions(workflow_name);
237
+ CREATE INDEX idx_sessions_agent_run ON sessions(agent_run_id);
238
+ CREATE UNIQUE INDEX idx_sessions_seq_num ON sessions(seq_num);
239
+ CREATE UNIQUE INDEX idx_sessions_unique ON sessions(external_id, machine_id, source, project_id);
240
+
241
+ -- Session messages
242
+ CREATE TABLE session_messages (
243
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
244
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
245
+ message_index INTEGER NOT NULL,
246
+ role TEXT NOT NULL,
247
+ content TEXT NOT NULL,
248
+ content_type TEXT DEFAULT 'text',
249
+ tool_name TEXT,
250
+ tool_input TEXT,
251
+ tool_result TEXT,
252
+ timestamp TEXT NOT NULL,
253
+ raw_json TEXT,
254
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
255
+ UNIQUE(session_id, message_index)
256
+ );
257
+ CREATE INDEX idx_session_messages_session ON session_messages(session_id);
258
+ CREATE INDEX idx_session_messages_role ON session_messages(role);
259
+ CREATE INDEX idx_session_messages_timestamp ON session_messages(timestamp);
260
+ CREATE INDEX idx_session_messages_tool ON session_messages(tool_name);
261
+
262
+ -- Session message processing state
263
+ CREATE TABLE session_message_state (
264
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
265
+ last_byte_offset INTEGER DEFAULT 0,
266
+ last_message_index INTEGER DEFAULT 0,
267
+ last_processed_at TEXT,
268
+ processing_errors INTEGER DEFAULT 0,
269
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
270
+ );
271
+
272
+ -- Session artifacts with FTS
273
+ CREATE TABLE session_artifacts (
274
+ id TEXT PRIMARY KEY,
275
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
276
+ artifact_type TEXT NOT NULL,
277
+ content TEXT NOT NULL,
278
+ metadata_json TEXT,
279
+ source_file TEXT,
280
+ line_start INTEGER,
281
+ line_end INTEGER,
282
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
283
+ );
284
+ CREATE INDEX idx_session_artifacts_session ON session_artifacts(session_id);
285
+ CREATE INDEX idx_session_artifacts_type ON session_artifacts(artifact_type);
286
+ CREATE INDEX idx_session_artifacts_created ON session_artifacts(created_at);
287
+ CREATE VIRTUAL TABLE session_artifacts_fts USING fts5(id UNINDEXED, content);
288
+
289
+ -- Session stop signals for autonomous stop
290
+ CREATE TABLE session_stop_signals (
291
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
292
+ source TEXT NOT NULL,
293
+ reason TEXT,
294
+ requested_at TEXT NOT NULL,
295
+ acknowledged_at TEXT
296
+ );
297
+ CREATE INDEX idx_stop_signals_pending ON session_stop_signals(acknowledged_at)
298
+ WHERE acknowledged_at IS NULL;
299
+
300
+ -- Loop progress for autonomous progress tracking
301
+ CREATE TABLE loop_progress (
302
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
303
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
304
+ progress_type TEXT NOT NULL,
305
+ tool_name TEXT,
306
+ details TEXT,
307
+ recorded_at TEXT NOT NULL,
308
+ is_high_value INTEGER NOT NULL DEFAULT 0
309
+ );
310
+ CREATE INDEX idx_loop_progress_session ON loop_progress(session_id, recorded_at DESC);
311
+ CREATE INDEX idx_loop_progress_high_value ON loop_progress(session_id, is_high_value, recorded_at DESC)
312
+ WHERE is_high_value = 1;
313
+
314
+ -- Tasks
315
+ CREATE TABLE tasks (
316
+ id TEXT PRIMARY KEY,
317
+ project_id TEXT NOT NULL REFERENCES projects(id),
318
+ parent_task_id TEXT REFERENCES tasks(id),
319
+ created_in_session_id TEXT REFERENCES sessions(id),
320
+ closed_in_session_id TEXT REFERENCES sessions(id),
321
+ closed_commit_sha TEXT,
322
+ closed_at TEXT,
323
+ title TEXT NOT NULL,
324
+ description TEXT,
325
+ status TEXT DEFAULT 'open',
326
+ priority INTEGER DEFAULT 2,
327
+ task_type TEXT DEFAULT 'task',
328
+ assignee TEXT,
329
+ labels TEXT,
330
+ closed_reason TEXT,
331
+ compacted_at TEXT,
332
+ summary TEXT,
333
+ validation_status TEXT CHECK(validation_status IN ('pending', 'valid', 'invalid')),
334
+ validation_feedback TEXT,
335
+ validation_override_reason TEXT,
336
+ original_instruction TEXT,
337
+ details TEXT,
338
+ category TEXT,
339
+ complexity_score INTEGER,
340
+ estimated_subtasks INTEGER,
341
+ expansion_context TEXT,
342
+ validation_criteria TEXT,
343
+ use_external_validator INTEGER DEFAULT 0,
344
+ validation_fail_count INTEGER DEFAULT 0,
345
+ workflow_name TEXT,
346
+ verification TEXT,
347
+ sequence_order INTEGER,
348
+ commits TEXT,
349
+ escalated_at TEXT,
350
+ escalation_reason TEXT,
351
+ github_issue_number INTEGER,
352
+ github_pr_number INTEGER,
353
+ github_repo TEXT,
354
+ linear_issue_id TEXT,
355
+ linear_team_id TEXT,
356
+ seq_num INTEGER,
357
+ path_cache TEXT,
358
+ agent_name TEXT,
359
+ reference_doc TEXT,
360
+ is_expanded INTEGER DEFAULT 0,
361
+ is_tdd_applied INTEGER DEFAULT 0,
362
+ requires_user_review INTEGER DEFAULT 0,
363
+ accepted_by_user INTEGER DEFAULT 0,
364
+ created_at TEXT NOT NULL,
365
+ updated_at TEXT NOT NULL
366
+ );
367
+ CREATE INDEX idx_tasks_project ON tasks(project_id);
368
+ CREATE INDEX idx_tasks_status ON tasks(status);
369
+ CREATE INDEX idx_tasks_parent ON tasks(parent_task_id);
370
+ CREATE INDEX idx_tasks_workflow ON tasks(workflow_name);
371
+ CREATE INDEX idx_tasks_sequence ON tasks(workflow_name, sequence_order);
372
+ CREATE INDEX idx_tasks_created_session ON tasks(created_in_session_id);
373
+ CREATE INDEX idx_tasks_closed_session ON tasks(closed_in_session_id);
374
+ CREATE UNIQUE INDEX idx_tasks_seq_num ON tasks(project_id, seq_num);
375
+ CREATE INDEX idx_tasks_path_cache ON tasks(path_cache);
376
+
377
+ -- Task dependencies
378
+ CREATE TABLE task_dependencies (
379
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
380
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
381
+ depends_on TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
382
+ dep_type TEXT NOT NULL,
383
+ created_at TEXT NOT NULL,
384
+ UNIQUE(task_id, depends_on, dep_type)
385
+ );
386
+ CREATE INDEX idx_deps_task ON task_dependencies(task_id);
387
+ CREATE INDEX idx_deps_depends_on ON task_dependencies(depends_on);
388
+
389
+ -- Session-task linkages
390
+ CREATE TABLE session_tasks (
391
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
392
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
393
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
394
+ action TEXT NOT NULL,
395
+ created_at TEXT NOT NULL,
396
+ UNIQUE(session_id, task_id, action)
397
+ );
398
+ CREATE INDEX idx_session_tasks_session ON session_tasks(session_id);
399
+ CREATE INDEX idx_session_tasks_task ON session_tasks(task_id);
400
+
401
+ -- Task validation history
402
+ CREATE TABLE task_validation_history (
403
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
404
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
405
+ iteration INTEGER NOT NULL,
406
+ status TEXT NOT NULL,
407
+ feedback TEXT,
408
+ issues TEXT,
409
+ context_type TEXT,
410
+ context_summary TEXT,
411
+ validator_type TEXT,
412
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
413
+ );
414
+ CREATE INDEX idx_validation_history_task ON task_validation_history(task_id);
415
+
416
+ -- Task selection history for stuck detection
417
+ CREATE TABLE task_selection_history (
418
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
419
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
420
+ task_id TEXT NOT NULL,
421
+ selected_at TEXT NOT NULL,
422
+ context TEXT
423
+ );
424
+ CREATE INDEX idx_task_selection_session ON task_selection_history(session_id, selected_at DESC);
425
+ CREATE INDEX idx_task_selection_task ON task_selection_history(session_id, task_id, selected_at DESC);
426
+
427
+ -- Workflow states
428
+ CREATE TABLE workflow_states (
429
+ session_id TEXT PRIMARY KEY,
430
+ workflow_name TEXT NOT NULL,
431
+ step TEXT NOT NULL,
432
+ step_entered_at TEXT,
433
+ step_action_count INTEGER DEFAULT 0,
434
+ total_action_count INTEGER DEFAULT 0,
435
+ artifacts TEXT,
436
+ observations TEXT,
437
+ reflection_pending INTEGER DEFAULT 0,
438
+ context_injected INTEGER DEFAULT 0,
439
+ variables TEXT,
440
+ task_list TEXT,
441
+ current_task_index INTEGER DEFAULT 0,
442
+ files_modified_this_task INTEGER DEFAULT 0,
443
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
444
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
445
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
446
+ );
447
+
448
+ -- Workflow audit log
449
+ CREATE TABLE workflow_audit_log (
450
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
451
+ session_id TEXT NOT NULL,
452
+ timestamp TEXT NOT NULL DEFAULT (datetime('now')),
453
+ step TEXT NOT NULL,
454
+ event_type TEXT NOT NULL,
455
+ tool_name TEXT,
456
+ rule_id TEXT,
457
+ condition TEXT,
458
+ result TEXT NOT NULL,
459
+ reason TEXT,
460
+ context TEXT,
461
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
462
+ );
463
+ CREATE INDEX idx_audit_session ON workflow_audit_log(session_id);
464
+ CREATE INDEX idx_audit_timestamp ON workflow_audit_log(timestamp);
465
+ CREATE INDEX idx_audit_event_type ON workflow_audit_log(event_type);
466
+ CREATE INDEX idx_audit_result ON workflow_audit_log(result);
467
+
468
+ -- Memories
469
+ CREATE TABLE memories (
470
+ id TEXT PRIMARY KEY,
471
+ project_id TEXT REFERENCES projects(id),
472
+ memory_type TEXT NOT NULL,
473
+ content TEXT NOT NULL,
474
+ source_type TEXT,
475
+ source_session_id TEXT REFERENCES sessions(id),
476
+ importance REAL DEFAULT 0.5,
477
+ access_count INTEGER DEFAULT 0,
478
+ last_accessed_at TEXT,
479
+ tags TEXT,
480
+ media TEXT,
481
+ created_at TEXT NOT NULL,
482
+ updated_at TEXT NOT NULL
483
+ );
484
+ CREATE INDEX idx_memories_project ON memories(project_id);
485
+ CREATE INDEX idx_memories_type ON memories(memory_type);
486
+ CREATE INDEX idx_memories_importance ON memories(importance DESC);
487
+
488
+ -- Session-memory linkages
489
+ CREATE TABLE session_memories (
490
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
491
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
492
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
493
+ action TEXT NOT NULL,
494
+ created_at TEXT NOT NULL,
495
+ UNIQUE(session_id, memory_id, action)
496
+ );
497
+ CREATE INDEX idx_session_memories_session ON session_memories(session_id);
498
+ CREATE INDEX idx_session_memories_memory ON session_memories(memory_id);
499
+
500
+ -- Memory cross-references
501
+ CREATE TABLE memory_crossrefs (
502
+ source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
503
+ target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
504
+ similarity REAL NOT NULL,
505
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
506
+ PRIMARY KEY (source_id, target_id)
507
+ );
508
+ CREATE INDEX idx_crossrefs_source ON memory_crossrefs(source_id);
509
+ CREATE INDEX idx_crossrefs_target ON memory_crossrefs(target_id);
510
+ CREATE INDEX idx_crossrefs_similarity ON memory_crossrefs(similarity DESC);
511
+
512
+ -- Worktrees
513
+ CREATE TABLE worktrees (
514
+ id TEXT PRIMARY KEY,
515
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
516
+ task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
517
+ branch_name TEXT NOT NULL,
518
+ worktree_path TEXT NOT NULL,
519
+ base_branch TEXT DEFAULT 'main',
520
+ agent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
521
+ status TEXT DEFAULT 'active',
522
+ merge_state TEXT,
523
+ merged_at TEXT,
524
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
525
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
526
+ );
527
+ CREATE INDEX idx_worktrees_project ON worktrees(project_id);
528
+ CREATE INDEX idx_worktrees_status ON worktrees(status);
529
+ CREATE INDEX idx_worktrees_task ON worktrees(task_id);
530
+ CREATE INDEX idx_worktrees_session ON worktrees(agent_session_id);
531
+ CREATE UNIQUE INDEX idx_worktrees_branch ON worktrees(project_id, branch_name);
532
+ CREATE UNIQUE INDEX idx_worktrees_path ON worktrees(worktree_path);
533
+
534
+ -- Merge resolutions
535
+ CREATE TABLE merge_resolutions (
536
+ id TEXT PRIMARY KEY,
537
+ worktree_id TEXT NOT NULL REFERENCES worktrees(id) ON DELETE CASCADE,
538
+ source_branch TEXT NOT NULL,
539
+ target_branch TEXT NOT NULL,
540
+ status TEXT NOT NULL DEFAULT 'pending',
541
+ tier_used TEXT,
542
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
543
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
544
+ );
545
+ CREATE INDEX idx_merge_resolutions_worktree ON merge_resolutions(worktree_id);
546
+ CREATE INDEX idx_merge_resolutions_status ON merge_resolutions(status);
547
+ CREATE INDEX idx_merge_resolutions_source_branch ON merge_resolutions(source_branch);
548
+ CREATE INDEX idx_merge_resolutions_target_branch ON merge_resolutions(target_branch);
549
+
550
+ -- Merge conflicts
551
+ CREATE TABLE merge_conflicts (
552
+ id TEXT PRIMARY KEY,
553
+ resolution_id TEXT NOT NULL REFERENCES merge_resolutions(id) ON DELETE CASCADE,
554
+ file_path TEXT NOT NULL,
555
+ status TEXT NOT NULL DEFAULT 'pending',
556
+ ours_content TEXT,
557
+ theirs_content TEXT,
558
+ resolved_content TEXT,
559
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
560
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
561
+ );
562
+ CREATE INDEX idx_merge_conflicts_resolution ON merge_conflicts(resolution_id);
563
+ CREATE INDEX idx_merge_conflicts_file_path ON merge_conflicts(file_path);
564
+ CREATE INDEX idx_merge_conflicts_status ON merge_conflicts(status);
565
+ """
566
+
567
+ # Future migrations (v61+)
568
+ # Add new migrations here. Do not modify the baseline schema above.
569
+
570
+
571
+ def _migrate_test_strategy_to_category(db: LocalDatabase) -> None:
572
+ """Rename test_strategy column to category if it exists.
573
+
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.
576
+ """
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'")
611
+ if not row:
612
+ return
613
+
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:
634
+ return
635
+
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
656
+
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}")
665
+
666
+
667
+ def _migrate_add_inter_session_messages(db: LocalDatabase) -> None:
668
+ """Add inter_session_messages table for parent-child agent communication.
669
+
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.
673
+ """
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'"
677
+ )
678
+ if row:
679
+ logger.debug("inter_session_messages table already exists, skipping")
680
+ return
681
+
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
+ """)
694
+
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
+ )
706
+
707
+ logger.info("Created inter_session_messages table with indexes")
708
+
709
+
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")
719
+
720
+
721
+ 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),
738
+ ]
739
+
740
+
741
+ def get_current_version(db: LocalDatabase) -> int:
742
+ """Get current schema version from database."""
743
+ try:
744
+ row = db.fetchone("SELECT MAX(version) as version FROM schema_version")
745
+ return row["version"] if row and row["version"] else 0
746
+ except Exception:
747
+ return 0
748
+
749
+
750
+ def _apply_baseline(db: LocalDatabase) -> None:
751
+ """Apply baseline schema for new databases."""
752
+ logger.info("Applying baseline schema (v60)")
753
+
754
+ # Execute baseline schema
755
+ for statement in BASELINE_SCHEMA.strip().split(";"):
756
+ statement = statement.strip()
757
+ if statement:
758
+ db.execute(statement)
759
+
760
+ # Record baseline version
761
+ db.execute(
762
+ "INSERT INTO schema_version (version) VALUES (?)",
763
+ (BASELINE_VERSION,),
764
+ )
765
+
766
+ logger.info(f"Baseline schema applied, now at version {BASELINE_VERSION}")
767
+
768
+
769
+ def _run_migration_list(
770
+ db: LocalDatabase,
771
+ current_version: int,
772
+ migrations: list[tuple[int, str, MigrationAction]],
773
+ ) -> int:
774
+ """
775
+ Run migrations from a list.
776
+
777
+ Args:
778
+ db: LocalDatabase instance
779
+ current_version: Current schema version
780
+ migrations: List of (version, description, action) tuples
781
+
782
+ Returns:
783
+ Number of migrations applied
784
+ """
785
+ applied = 0
786
+ last_version = current_version
787
+
788
+ for version, description, action in migrations:
789
+ if version > current_version:
790
+ logger.debug(f"Applying migration {version}: {description}")
791
+ try:
792
+ if callable(action):
793
+ # Python data migration
794
+ action(db)
795
+ else:
796
+ # SQL migration (may contain multiple statements)
797
+ for statement in action.strip().split(";"):
798
+ statement = statement.strip()
799
+ if statement:
800
+ db.execute(statement)
801
+
802
+ # Record migration
803
+ db.execute(
804
+ "INSERT INTO schema_version (version) VALUES (?)",
805
+ (version,),
806
+ )
807
+ applied += 1
808
+ last_version = version
809
+ except Exception as e:
810
+ logger.error(f"Migration {version} failed: {e}")
811
+ raise
812
+
813
+ if applied > 0:
814
+ logger.debug(f"Applied {applied} migration(s), now at version {last_version}")
815
+
816
+ return applied
817
+
818
+
819
+ def run_migrations(db: LocalDatabase) -> int:
820
+ """
821
+ Run pending migrations.
822
+
823
+ 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.
828
+
829
+ For all databases:
830
+ Runs any new migrations (v61+) after baseline/legacy path.
831
+
832
+ Args:
833
+ db: LocalDatabase instance
834
+
835
+ Returns:
836
+ Number of migrations applied
837
+ """
838
+ current_version = get_current_version(db)
839
+ total_applied = 0
840
+
841
+ if current_version == 0:
842
+ # New database: apply baseline schema directly
843
+ _apply_baseline(db)
844
+ total_applied = 1
845
+ current_version = BASELINE_VERSION
846
+ 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)
854
+
855
+ # Run any new migrations (v61+)
856
+ if MIGRATIONS:
857
+ applied = _run_migration_list(db, current_version, MIGRATIONS)
858
+ total_applied += applied
859
+
860
+ return total_applied