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,304 @@
1
+ """SQLite memory backend.
2
+
3
+ This backend wraps the existing LocalMemoryManager to provide a
4
+ MemoryBackendProtocol-compliant interface for SQLite storage.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import json
11
+ from datetime import UTC, datetime
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from gobby.memory.protocol import (
15
+ MediaAttachment,
16
+ MemoryCapability,
17
+ MemoryQuery,
18
+ MemoryRecord,
19
+ )
20
+ from gobby.storage.memories import LocalMemoryManager
21
+
22
+ if TYPE_CHECKING:
23
+ from gobby.storage.database import DatabaseProtocol
24
+
25
+
26
+ class SQLiteBackend:
27
+ """SQLite-based memory backend.
28
+
29
+ Wraps LocalMemoryManager to provide MemoryBackendProtocol interface.
30
+ Supports full CRUD operations and text-based search.
31
+ """
32
+
33
+ def __init__(self, database: DatabaseProtocol):
34
+ """Initialize with a database connection.
35
+
36
+ Args:
37
+ database: Database protocol instance for SQLite operations
38
+ """
39
+ self._storage = LocalMemoryManager(database)
40
+ self._db = database
41
+
42
+ def capabilities(self) -> set[MemoryCapability]:
43
+ """Return supported capabilities."""
44
+ return {
45
+ # Basic CRUD
46
+ MemoryCapability.CREATE,
47
+ MemoryCapability.READ,
48
+ MemoryCapability.UPDATE,
49
+ MemoryCapability.DELETE,
50
+ # Search
51
+ MemoryCapability.SEARCH_TEXT,
52
+ MemoryCapability.SEARCH,
53
+ # Advanced
54
+ MemoryCapability.TAGS,
55
+ MemoryCapability.IMPORTANCE,
56
+ MemoryCapability.LIST,
57
+ # MCP-aligned
58
+ MemoryCapability.REMEMBER,
59
+ MemoryCapability.RECALL,
60
+ MemoryCapability.FORGET,
61
+ }
62
+
63
+ async def create(
64
+ self,
65
+ content: str,
66
+ memory_type: str = "fact",
67
+ importance: float = 0.5,
68
+ project_id: str | None = None,
69
+ user_id: str | None = None,
70
+ tags: list[str] | None = None,
71
+ source_type: str | None = None,
72
+ source_session_id: str | None = None,
73
+ media: list[MediaAttachment] | None = None,
74
+ metadata: dict[str, Any] | None = None,
75
+ ) -> MemoryRecord:
76
+ """Create a new memory.
77
+
78
+ Args:
79
+ content: The memory content text
80
+ memory_type: Type of memory (fact, preference, etc.)
81
+ importance: Importance score (0.0 to 1.0)
82
+ project_id: Associated project ID
83
+ user_id: Associated user ID (stored in metadata for SQLite)
84
+ tags: List of tags
85
+ source_type: Origin of memory
86
+ source_session_id: Session that created the memory
87
+ media: List of media attachments (stored in metadata)
88
+ metadata: Additional metadata
89
+
90
+ Returns:
91
+ The created MemoryRecord
92
+ """
93
+ # Serialize media list to JSON for storage
94
+ media_json: str | None = None
95
+ if media:
96
+ media_json = json.dumps(
97
+ [
98
+ {
99
+ "media_type": m.media_type,
100
+ "content_path": m.content_path,
101
+ "mime_type": m.mime_type,
102
+ "description": m.description,
103
+ "description_model": m.description_model,
104
+ "metadata": m.metadata,
105
+ }
106
+ for m in media
107
+ ]
108
+ )
109
+
110
+ # Create via storage layer (wrap sync call to avoid blocking event loop)
111
+ memory = await asyncio.to_thread(
112
+ self._storage.create_memory,
113
+ content=content,
114
+ memory_type=memory_type,
115
+ importance=importance,
116
+ project_id=project_id,
117
+ source_type=source_type or "user",
118
+ source_session_id=source_session_id,
119
+ tags=tags,
120
+ media=media_json,
121
+ )
122
+
123
+ # Convert to MemoryRecord
124
+ return self._memory_to_record(memory, user_id=user_id, metadata=metadata)
125
+
126
+ async def get(self, memory_id: str) -> MemoryRecord | None:
127
+ """Retrieve a memory by ID.
128
+
129
+ Args:
130
+ memory_id: The memory ID to retrieve
131
+
132
+ Returns:
133
+ The MemoryRecord if found, None otherwise
134
+ """
135
+ try:
136
+ memory = await asyncio.to_thread(self._storage.get_memory, memory_id)
137
+ return self._memory_to_record(memory)
138
+ except ValueError:
139
+ # Storage layer raises ValueError when memory not found
140
+ return None
141
+
142
+ async def update(
143
+ self,
144
+ memory_id: str,
145
+ content: str | None = None,
146
+ importance: float | None = None,
147
+ tags: list[str] | None = None,
148
+ ) -> MemoryRecord:
149
+ """Update an existing memory.
150
+
151
+ Args:
152
+ memory_id: The memory ID to update
153
+ content: New content (optional)
154
+ importance: New importance score (optional)
155
+ tags: New tags (optional)
156
+
157
+ Returns:
158
+ The updated MemoryRecord
159
+
160
+ Raises:
161
+ ValueError: If memory not found
162
+ """
163
+ memory = await asyncio.to_thread(
164
+ self._storage.update_memory,
165
+ memory_id=memory_id,
166
+ content=content,
167
+ importance=importance,
168
+ tags=tags,
169
+ )
170
+ if memory is None:
171
+ raise ValueError(f"Memory not found: {memory_id}")
172
+ return self._memory_to_record(memory)
173
+
174
+ async def delete(self, memory_id: str) -> bool:
175
+ """Delete a memory.
176
+
177
+ Args:
178
+ memory_id: The memory ID to delete
179
+
180
+ Returns:
181
+ True if deleted, False if not found
182
+ """
183
+ return await asyncio.to_thread(self._storage.delete_memory, memory_id)
184
+
185
+ async def search(self, query: MemoryQuery) -> list[MemoryRecord]:
186
+ """Search for memories.
187
+
188
+ Args:
189
+ query: Search parameters
190
+
191
+ Returns:
192
+ List of matching MemoryRecords
193
+ """
194
+ # Use storage layer's search (wrap sync call to avoid blocking event loop)
195
+ memories = await asyncio.to_thread(
196
+ self._storage.search_memories,
197
+ query_text=query.text,
198
+ project_id=query.project_id,
199
+ limit=query.limit,
200
+ tags_all=query.tags_all,
201
+ tags_any=query.tags_any,
202
+ tags_none=query.tags_none,
203
+ )
204
+
205
+ # Apply additional filters not supported by storage layer
206
+ if query.min_importance is not None:
207
+ memories = [m for m in memories if m.importance >= query.min_importance]
208
+ if query.memory_type is not None:
209
+ memories = [m for m in memories if m.memory_type == query.memory_type]
210
+
211
+ return [self._memory_to_record(m) for m in memories]
212
+
213
+ async def list_memories(
214
+ self,
215
+ project_id: str | None = None,
216
+ user_id: str | None = None,
217
+ memory_type: str | None = None,
218
+ limit: int = 50,
219
+ offset: int = 0,
220
+ ) -> list[MemoryRecord]:
221
+ """List memories with optional filtering.
222
+
223
+ Args:
224
+ project_id: Filter by project ID
225
+ user_id: Filter by user ID (not supported in SQLite, ignored)
226
+ memory_type: Filter by memory type
227
+ limit: Maximum number of results
228
+ offset: Number of results to skip
229
+
230
+ Returns:
231
+ List of MemoryRecords
232
+ """
233
+ memories = await asyncio.to_thread(
234
+ self._storage.list_memories,
235
+ project_id=project_id,
236
+ memory_type=memory_type,
237
+ limit=limit,
238
+ offset=offset,
239
+ )
240
+
241
+ return [self._memory_to_record(m) for m in memories]
242
+
243
+ def _memory_to_record(
244
+ self,
245
+ memory: Any,
246
+ user_id: str | None = None,
247
+ metadata: dict[str, Any] | None = None,
248
+ ) -> MemoryRecord:
249
+ """Convert a Memory object to MemoryRecord.
250
+
251
+ Args:
252
+ memory: Memory object from storage layer
253
+ user_id: Optional user ID to include
254
+ metadata: Optional additional metadata
255
+
256
+ Returns:
257
+ MemoryRecord instance
258
+ """
259
+ # Parse datetime strings
260
+ created_at = (
261
+ datetime.fromisoformat(memory.created_at) if memory.created_at else datetime.now(UTC)
262
+ )
263
+ updated_at = datetime.fromisoformat(memory.updated_at) if memory.updated_at else None
264
+ last_accessed = (
265
+ datetime.fromisoformat(memory.last_accessed_at) if memory.last_accessed_at else None
266
+ )
267
+
268
+ # Deserialize media from JSON string
269
+ media_list: list[MediaAttachment] = []
270
+ if memory.media:
271
+ try:
272
+ media_data = json.loads(memory.media)
273
+ media_list = [
274
+ MediaAttachment(
275
+ media_type=m.get("media_type", "unknown"),
276
+ content_path=m.get("content_path", ""),
277
+ mime_type=m.get("mime_type", "application/octet-stream"),
278
+ description=m.get("description"),
279
+ description_model=m.get("description_model"),
280
+ metadata=m.get("metadata"),
281
+ )
282
+ for m in media_data
283
+ ]
284
+ except (json.JSONDecodeError, TypeError):
285
+ # If media is malformed, log and continue with empty list
286
+ media_list = []
287
+
288
+ return MemoryRecord(
289
+ id=memory.id,
290
+ content=memory.content,
291
+ created_at=created_at,
292
+ memory_type=memory.memory_type,
293
+ updated_at=updated_at,
294
+ project_id=memory.project_id,
295
+ user_id=user_id,
296
+ importance=memory.importance,
297
+ tags=memory.tags or [],
298
+ source_type=memory.source_type,
299
+ source_session_id=memory.source_session_id,
300
+ access_count=memory.access_count,
301
+ last_accessed_at=last_accessed,
302
+ media=media_list,
303
+ metadata=metadata or {},
304
+ )
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ from gobby.storage.memories import Memory
6
+
7
+ # Pattern to match common bullet markers at start of string
8
+ _BULLET_PATTERN = re.compile(r"^[\s]*[-*•]\s*")
9
+
10
+
11
+ def _strip_leading_bullet(content: str) -> str:
12
+ """
13
+ Strip leading bullet points and whitespace from content.
14
+
15
+ Handles common bullet markers: -, *, •
16
+ Also strips any leading/trailing whitespace.
17
+
18
+ Returns empty string if content is empty or only whitespace/bullets.
19
+ """
20
+ # Strip outer whitespace first
21
+ content = content.strip()
22
+ if not content:
23
+ return ""
24
+
25
+ # Remove leading bullet marker if present
26
+ result = _BULLET_PATTERN.sub("", content)
27
+ return result.strip()
28
+
29
+
30
+ def build_memory_context(memories: list[Memory]) -> str:
31
+ """
32
+ Build a formatted markdown context string from memories.
33
+
34
+ Args:
35
+ memories: List of Memory objects to include
36
+
37
+ Returns:
38
+ Formatted markdown string wrapped in <project-memory> tags
39
+ """
40
+ if not memories:
41
+ return ""
42
+
43
+ parts = ["<project-memory>"]
44
+
45
+ # Group memories by type
46
+ context_memories = [m for m in memories if m.memory_type == "context"]
47
+ pref_memories = [m for m in memories if m.memory_type == "preference"]
48
+ pattern_memories = [m for m in memories if m.memory_type == "pattern"]
49
+ fact_memories = [m for m in memories if m.memory_type == "fact"]
50
+
51
+ # 1. Project Context
52
+ if context_memories:
53
+ parts.append("## Project Context\n")
54
+ for mem in context_memories:
55
+ parts.append(f"{mem.content}\n")
56
+ parts.append("")
57
+
58
+ # 2. Preferences
59
+ if pref_memories:
60
+ parts.append("## Preferences\n")
61
+ for mem in pref_memories:
62
+ content = _strip_leading_bullet(mem.content)
63
+ if content: # Skip empty content
64
+ parts.append(f"- {content}")
65
+ parts.append("")
66
+
67
+ # 3. Patterns
68
+ if pattern_memories:
69
+ parts.append("## Patterns\n")
70
+ for mem in pattern_memories:
71
+ content = _strip_leading_bullet(mem.content)
72
+ if content: # Skip empty content
73
+ parts.append(f"- {content}")
74
+ parts.append("")
75
+
76
+ # 4. Facts/Other
77
+ if fact_memories:
78
+ parts.append("## Facts\n")
79
+ for mem in fact_memories:
80
+ content = _strip_leading_bullet(mem.content)
81
+ if content: # Skip empty content
82
+ parts.append(f"- {content}")
83
+ parts.append("")
84
+
85
+ parts.append("</project-memory>")
86
+
87
+ return "\n".join(parts)