devobin 1.0.0__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.
- devobin/__init__.py +4 -0
- devobin/app.py +42 -0
- devobin/cli/__init__.py +1 -0
- devobin/cli/chat.py +831 -0
- devobin/cli/commands.py +362 -0
- devobin/cli/slash_menu.py +74 -0
- devobin/cli/terminal.py +155 -0
- devobin/cli/ui.py +129 -0
- devobin/config/__init__.py +1 -0
- devobin/config/settings.py +124 -0
- devobin/core/__init__.py +1 -0
- devobin/core/analyzer.py +166 -0
- devobin/core/executor.py +244 -0
- devobin/core/memory.py +78 -0
- devobin/core/optimizer.py +106 -0
- devobin/core/prompt_builder.py +513 -0
- devobin/core/researcher.py +283 -0
- devobin/core/rtl.py +93 -0
- devobin/core/scanner.py +206 -0
- devobin/core/tools.py +283 -0
- devobin/core/validator.py +235 -0
- devobin/main.py +33 -0
- devobin/output/__init__.py +1 -0
- devobin/providers/__init__.py +85 -0
- devobin/providers/anthropic_provider.py +72 -0
- devobin/providers/google_provider.py +90 -0
- devobin/providers/ollama_provider.py +81 -0
- devobin/providers/openai_provider.py +102 -0
- devobin/storage/__init__.py +1 -0
- devobin/storage/database.py +207 -0
- devobin/ui/__init__.py +1 -0
- devobin/ui/ask_modal.py +96 -0
- devobin/ui/chat_screen.py +1029 -0
- devobin/ui/command_palette.py +131 -0
- devobin/ui/connect_modal.py +166 -0
- devobin/ui/export_modal.py +159 -0
- devobin/ui/history_modal.py +137 -0
- devobin/ui/memory_modal.py +88 -0
- devobin/ui/model_modal.py +143 -0
- devobin/ui/permission_modal.py +92 -0
- devobin/ui/project_modal.py +133 -0
- devobin/ui/settings_modal.py +89 -0
- devobin-1.0.0.dist-info/METADATA +364 -0
- devobin-1.0.0.dist-info/RECORD +47 -0
- devobin-1.0.0.dist-info/WHEEL +4 -0
- devobin-1.0.0.dist-info/entry_points.txt +2 -0
- devobin-1.0.0.dist-info/licenses/LICENSE +21 -0
devobin/core/memory.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Memory manager - manages long-term memory across sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from devobin.storage.database import GlobalStorage, ProjectStorage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MemoryManager:
|
|
9
|
+
"""Manages both global and project-specific memory."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, project_name: str | None = None) -> None:
|
|
12
|
+
self._global = GlobalStorage()
|
|
13
|
+
self._project = ProjectStorage(project_name) if project_name else None
|
|
14
|
+
|
|
15
|
+
# ── Global Memory ─────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
def add_global_memory(self, content: str, category: str = "preference") -> dict:
|
|
18
|
+
return self._global.add_memory(content, category)
|
|
19
|
+
|
|
20
|
+
def get_global_memories(self, category: str | None = None) -> list[dict]:
|
|
21
|
+
return self._global.get_memories(category)
|
|
22
|
+
|
|
23
|
+
def search_global_memories(self, query: str) -> list[dict]:
|
|
24
|
+
return self._global.search_memories(query)
|
|
25
|
+
|
|
26
|
+
# ── Project Memory ────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
def add_project_memory(self, content: str, category: str = "general") -> dict | None:
|
|
29
|
+
if self._project:
|
|
30
|
+
return self._project.add_memory(content, category)
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def get_project_memories(self, category: str | None = None) -> list[dict]:
|
|
34
|
+
if self._project:
|
|
35
|
+
return self._project.get_memories(category)
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
def search_project_memories(self, query: str) -> list[dict]:
|
|
39
|
+
if self._project:
|
|
40
|
+
return self._project.search_memories(query)
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
# ── Combined Context ──────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
def get_memory_context(self) -> str:
|
|
46
|
+
"""Build a formatted string of all relevant memory for AI context."""
|
|
47
|
+
lines: list[str] = []
|
|
48
|
+
|
|
49
|
+
global_prefs = self.get_global_memories("preference")
|
|
50
|
+
if global_prefs:
|
|
51
|
+
lines.append("**User Preferences:**")
|
|
52
|
+
for m in global_prefs[-10:]: # Last 10 preferences
|
|
53
|
+
lines.append(f"- {m['content']}")
|
|
54
|
+
|
|
55
|
+
project_mem = self.get_project_memories()
|
|
56
|
+
if project_mem:
|
|
57
|
+
lines.append("\n**Project Context:**")
|
|
58
|
+
for m in project_mem[-10:]:
|
|
59
|
+
lines.append(f"- {m['content']}")
|
|
60
|
+
|
|
61
|
+
return "\n".join(lines)
|
|
62
|
+
|
|
63
|
+
# ── Convenience ───────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
def remember(self, content: str, scope: str = "project", category: str = "general") -> dict | None:
|
|
66
|
+
"""Remember something. scope: 'global' or 'project'."""
|
|
67
|
+
if scope == "global":
|
|
68
|
+
return self.add_global_memory(content, category)
|
|
69
|
+
return self.add_project_memory(content, category)
|
|
70
|
+
|
|
71
|
+
def recall(self, query: str, scope: str = "both") -> list[dict]:
|
|
72
|
+
"""Recall memories matching query."""
|
|
73
|
+
results: list[dict] = []
|
|
74
|
+
if scope in ("global", "both"):
|
|
75
|
+
results.extend(self.search_global_memories(query))
|
|
76
|
+
if scope in ("project", "both") and self._project:
|
|
77
|
+
results.extend(self.search_project_memories(query))
|
|
78
|
+
return results
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Token optimizer - compacts and optimizes prompts for minimal token usage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def optimize_prompt(text: str) -> str:
|
|
9
|
+
"""Optimize a prompt for token efficiency while preserving meaning."""
|
|
10
|
+
lines = text.split("\n")
|
|
11
|
+
optimized: list[str] = []
|
|
12
|
+
prev_blank = False
|
|
13
|
+
|
|
14
|
+
for line in lines:
|
|
15
|
+
stripped = line.strip()
|
|
16
|
+
|
|
17
|
+
# Collapse multiple blank lines
|
|
18
|
+
if not stripped:
|
|
19
|
+
if not prev_blank:
|
|
20
|
+
optimized.append("")
|
|
21
|
+
prev_blank = True
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
prev_blank = False
|
|
25
|
+
|
|
26
|
+
# Skip redundant phrases
|
|
27
|
+
if _is_redundant(stripped):
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
# Compact verbose phrases
|
|
31
|
+
line = _compact_phrases(stripped)
|
|
32
|
+
optimized.append(line)
|
|
33
|
+
|
|
34
|
+
return "\n".join(optimized).strip()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_redundant(line: str) -> bool:
|
|
38
|
+
"""Check if a line is redundant and can be removed."""
|
|
39
|
+
redundant_patterns = [
|
|
40
|
+
r"^(it is important to|it is essential to|make sure to|be sure to|don't forget to)\s",
|
|
41
|
+
r"^(please|kindly|ensure that you|remember to)\s",
|
|
42
|
+
r"^(always remember that|note that|keep in mind that)\s",
|
|
43
|
+
]
|
|
44
|
+
for pattern in redundant_patterns:
|
|
45
|
+
if re.match(pattern, line, re.IGNORECASE):
|
|
46
|
+
return True
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _compact_phrases(text: str) -> str:
|
|
51
|
+
"""Replace verbose phrases with concise equivalents."""
|
|
52
|
+
replacements = [
|
|
53
|
+
(r"in order to", "to"),
|
|
54
|
+
(r"for the purpose of", "for"),
|
|
55
|
+
(r"due to the fact that", "because"),
|
|
56
|
+
(r"at this point in time", "now"),
|
|
57
|
+
(r"in the event that", "if"),
|
|
58
|
+
(r"on a regular basis", "regularly"),
|
|
59
|
+
(r"with regard to", "regarding"),
|
|
60
|
+
(r"in the process of", "while"),
|
|
61
|
+
(r"has the ability to", "can"),
|
|
62
|
+
(r"is able to", "can"),
|
|
63
|
+
]
|
|
64
|
+
for pattern, replacement in replacements:
|
|
65
|
+
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
|
66
|
+
return text
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def estimate_tokens(text: str) -> int:
|
|
70
|
+
"""Rough token count estimate (1 token ≈ 4 chars for English)."""
|
|
71
|
+
return len(text) // 4
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def compress_context(messages: list[dict[str, str]], max_tokens: int = 8000) -> list[dict[str, str]]:
|
|
75
|
+
"""Compress chat context to fit within token budget."""
|
|
76
|
+
total = sum(estimate_tokens(m["content"]) for m in messages)
|
|
77
|
+
|
|
78
|
+
if total <= max_tokens:
|
|
79
|
+
return messages
|
|
80
|
+
|
|
81
|
+
# Keep the system message and recent messages
|
|
82
|
+
result: list[dict[str, str]] = []
|
|
83
|
+
budget = max_tokens
|
|
84
|
+
|
|
85
|
+
# Always keep first message if it's a system
|
|
86
|
+
if messages and messages[0]["role"] == "system":
|
|
87
|
+
system_msg = messages[0]
|
|
88
|
+
system_tokens = estimate_tokens(system_msg["content"])
|
|
89
|
+
result.append(system_msg)
|
|
90
|
+
budget -= system_tokens
|
|
91
|
+
messages = messages[1:]
|
|
92
|
+
|
|
93
|
+
# Keep recent messages, summarize old ones
|
|
94
|
+
kept: list[dict[str, str]] = []
|
|
95
|
+
for msg in reversed(messages):
|
|
96
|
+
msg_tokens = estimate_tokens(msg["content"])
|
|
97
|
+
if budget - msg_tokens >= 0:
|
|
98
|
+
kept.insert(0, msg)
|
|
99
|
+
budget -= msg_tokens
|
|
100
|
+
else:
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
if kept:
|
|
104
|
+
result.extend(kept)
|
|
105
|
+
|
|
106
|
+
return result
|