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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- 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
|
+
)
|
gobby/memory/context.py
ADDED
|
@@ -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)
|