ocerebro 0.1.0

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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +288 -0
  3. package/bin/ocerebro.js +98 -0
  4. package/cerebro/__init__.py +7 -0
  5. package/cerebro/__main__.py +19 -0
  6. package/cerebro/cerebro_setup.py +459 -0
  7. package/hooks/__init__.py +1 -0
  8. package/hooks/cost_hook.py +45 -0
  9. package/hooks/coverage_hook.py +39 -0
  10. package/hooks/error_hook.py +48 -0
  11. package/hooks/expensive_hook.py +41 -0
  12. package/hooks/global_logger.py +32 -0
  13. package/package.json +49 -0
  14. package/pyproject.toml +77 -0
  15. package/src/__init__.py +2 -0
  16. package/src/cli/__init__.py +2 -0
  17. package/src/cli/dream.py +91 -0
  18. package/src/cli/gc.py +93 -0
  19. package/src/cli/main.py +583 -0
  20. package/src/cli/remember.py +74 -0
  21. package/src/consolidation/__init__.py +8 -0
  22. package/src/consolidation/checkpoints.py +96 -0
  23. package/src/consolidation/dream.py +465 -0
  24. package/src/consolidation/extractor.py +313 -0
  25. package/src/consolidation/promoter.py +435 -0
  26. package/src/consolidation/remember.py +544 -0
  27. package/src/consolidation/scorer.py +191 -0
  28. package/src/core/__init__.py +6 -0
  29. package/src/core/event_schema.py +55 -0
  30. package/src/core/jsonl_storage.py +238 -0
  31. package/src/core/paths.py +254 -0
  32. package/src/core/session_manager.py +76 -0
  33. package/src/diff/__init__.py +5 -0
  34. package/src/diff/memory_diff.py +571 -0
  35. package/src/forgetting/__init__.py +6 -0
  36. package/src/forgetting/decay.py +86 -0
  37. package/src/forgetting/gc.py +296 -0
  38. package/src/forgetting/guard_rails.py +126 -0
  39. package/src/hooks/__init__.py +11 -0
  40. package/src/hooks/core_captures.py +170 -0
  41. package/src/hooks/custom_loader.py +389 -0
  42. package/src/index/__init__.py +7 -0
  43. package/src/index/embeddings_db.py +419 -0
  44. package/src/index/metadata_db.py +230 -0
  45. package/src/index/queries.py +357 -0
  46. package/src/mcp/__init__.py +2 -0
  47. package/src/mcp/server.py +640 -0
  48. package/src/memdir/__init__.py +19 -0
  49. package/src/memdir/scanner.py +260 -0
  50. package/src/official/__init__.py +5 -0
  51. package/src/official/markdown_storage.py +173 -0
  52. package/src/official/templates.py +128 -0
  53. package/src/working/__init__.py +5 -0
  54. package/src/working/memory_view.py +150 -0
  55. package/src/working/yaml_storage.py +234 -0
@@ -0,0 +1,357 @@
1
+ """QueryEngine: consultas híbridas para Cerebro"""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Optional, Tuple
5
+ from dataclasses import dataclass
6
+
7
+ from .metadata_db import MetadataDB
8
+ from .embeddings_db import EmbeddingsDB
9
+
10
+
11
+ @dataclass
12
+ class QueryResult:
13
+ """Resultado de consulta"""
14
+ memory_id: str
15
+ type: str
16
+ project: str
17
+ title: str
18
+ score: float
19
+ source: str # 'fts', 'semantic', 'metadata'
20
+ metadata: Dict[str, Any] = None
21
+
22
+
23
+ class QueryEngine:
24
+ """
25
+ Engine de consultas híbridas.
26
+
27
+ Combina três tipos de busca:
28
+ - Metadata: filtros estruturados (projeto, tipo, tags)
29
+ - FTS: busca full-text no conteúdo
30
+ - Semantic: busca por similaridade de embeddings
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ metadata_db: MetadataDB,
36
+ embeddings_db: EmbeddingsDB
37
+ ):
38
+ """
39
+ Inicializa o QueryEngine.
40
+
41
+ Args:
42
+ metadata_db: Instância do MetadataDB
43
+ embeddings_db: Instância do EmbeddingsDB
44
+ """
45
+ self.metadata_db = metadata_db
46
+ self.embeddings_db = embeddings_db
47
+
48
+ def search(
49
+ self,
50
+ query: str,
51
+ project: Optional[str] = None,
52
+ mem_type: Optional[str] = None,
53
+ limit: int = 10,
54
+ use_fts: bool = True,
55
+ use_semantic: bool = True,
56
+ fts_weight: float = 0.4,
57
+ semantic_weight: float = 0.6
58
+ ) -> List[QueryResult]:
59
+ """
60
+ Busca híbrida combinando FTS e semantic.
61
+
62
+ Args:
63
+ query: Texto de busca
64
+ project: Filtrar por projeto
65
+ mem_type: Filtrar por tipo
66
+ limit: Limite de resultados
67
+ use_fts: Usar busca FTS
68
+ use_semantic: Usar busca semantic
69
+ fts_weight: Peso da busca FTS
70
+ semantic_weight: Peso da busca semantic
71
+
72
+ Returns:
73
+ Lista de resultados ordenados por relevância
74
+ """
75
+ results: Dict[str, QueryResult] = {}
76
+
77
+ # Busca FTS
78
+ if use_fts:
79
+ fts_results = self._search_fts(query, project, mem_type, limit)
80
+ for r in fts_results:
81
+ results[r.memory_id] = r
82
+
83
+ # Busca Semantic
84
+ if use_semantic:
85
+ semantic_results = self._search_semantic(query, project, limit)
86
+ for r in semantic_results:
87
+ if r.memory_id in results:
88
+ # Combina scores
89
+ existing = results[r.memory_id]
90
+ combined_score = (
91
+ existing.score * fts_weight +
92
+ r.score * semantic_weight
93
+ )
94
+ results[r.memory_id] = QueryResult(
95
+ memory_id=r.memory_id,
96
+ type=r.type,
97
+ project=r.project,
98
+ title=r.title,
99
+ score=combined_score,
100
+ source="hybrid",
101
+ metadata=r.metadata
102
+ )
103
+ else:
104
+ r.score *= semantic_weight
105
+ results[r.memory_id] = r
106
+
107
+ # Filtra por tipo se especificado
108
+ if mem_type:
109
+ results = {
110
+ k: v for k, v in results.items()
111
+ if v.type == mem_type
112
+ }
113
+
114
+ # Ordena por score e limita
115
+ sorted_results = sorted(
116
+ results.values(),
117
+ key=lambda x: x.score,
118
+ reverse=True
119
+ )
120
+
121
+ return sorted_results[:limit]
122
+
123
+ def _search_fts(
124
+ self,
125
+ query: str,
126
+ project: Optional[str],
127
+ mem_type: Optional[str],
128
+ limit: int
129
+ ) -> List[QueryResult]:
130
+ """
131
+ Busca full-text.
132
+
133
+ Args:
134
+ query: Texto de busca
135
+ project: Filtrar por projeto
136
+ mem_type: Filtrar por tipo
137
+ limit: Limite de resultados
138
+
139
+ Returns:
140
+ Lista de resultados
141
+ """
142
+ fts_results = self.metadata_db.search_fts(query, project)
143
+
144
+ results = []
145
+ for row in fts_results[:limit]:
146
+ results.append(QueryResult(
147
+ memory_id=row["id"],
148
+ type=row["type"],
149
+ project=row["project"],
150
+ title=row.get("title", row["id"]),
151
+ score=1.0, # FTS não retorna score normalizado
152
+ source="fts",
153
+ metadata={
154
+ "tags": row.get("tags"),
155
+ "severity": row.get("severity"),
156
+ "access_count": row.get("access_count", 0)
157
+ }
158
+ ))
159
+
160
+ return results
161
+
162
+ def _search_semantic(
163
+ self,
164
+ query: str,
165
+ project: Optional[str],
166
+ limit: int
167
+ ) -> List[QueryResult]:
168
+ """
169
+ Busca semântica por similaridade.
170
+
171
+ Args:
172
+ query: Texto de busca
173
+ project: Filtrar por projeto
174
+ limit: Limite de resultados
175
+
176
+ Returns:
177
+ Lista de resultados
178
+ """
179
+ similar = self.embeddings_db.search_similar(query, project, limit * 2)
180
+
181
+ results = []
182
+ for item in similar:
183
+ # Busca metadados adicionais
184
+ memory = self.metadata_db.get_by_id(item["memory_id"])
185
+
186
+ results.append(QueryResult(
187
+ memory_id=item["memory_id"],
188
+ type=item["type"],
189
+ project=item["project"],
190
+ title=memory.get("title", item["memory_id"]) if memory else item["memory_id"],
191
+ score=item["similarity"],
192
+ source="semantic",
193
+ metadata={
194
+ "similarity": item["similarity"],
195
+ "content_hash": item.get("content_hash")
196
+ }
197
+ ))
198
+
199
+ return results
200
+
201
+ def search_by_metadata(
202
+ self,
203
+ project: Optional[str] = None,
204
+ mem_type: Optional[str] = None,
205
+ tags: Optional[List[str]] = None,
206
+ min_score: Optional[float] = None,
207
+ limit: int = 50
208
+ ) -> List[QueryResult]:
209
+ """
210
+ Busca apenas por metadados.
211
+
212
+ Args:
213
+ project: Filtrar por projeto
214
+ mem_type: Filtrar por tipo
215
+ tags: Filtrar por tags
216
+ min_score: Score mínimo
217
+ limit: Limite de resultados
218
+
219
+ Returns:
220
+ Lista de resultados
221
+ """
222
+ memories = self.metadata_db.search(project, mem_type)
223
+
224
+ results = []
225
+ for m in memories:
226
+ # Filtra por tags
227
+ if tags:
228
+ memory_tags = (m.get("tags") or "").split(",")
229
+ if not any(t in memory_tags for t in tags):
230
+ continue
231
+
232
+ # Filtra por score
233
+ if min_score and m.get("total_score", 0) < min_score:
234
+ continue
235
+
236
+ results.append(QueryResult(
237
+ memory_id=m["id"],
238
+ type=m["type"],
239
+ project=m["project"],
240
+ title=m.get("title", m["id"]),
241
+ score=m.get("total_score", 0),
242
+ source="metadata",
243
+ metadata={
244
+ "tags": m.get("tags"),
245
+ "severity": m.get("severity"),
246
+ "access_count": m.get("access_count", 0),
247
+ "created_at": m.get("created_at"),
248
+ "updated_at": m.get("updated_at")
249
+ }
250
+ ))
251
+
252
+ if len(results) >= limit:
253
+ break
254
+
255
+ return results
256
+
257
+ def find_similar_to_memory(
258
+ self,
259
+ memory_id: str,
260
+ limit: int = 5,
261
+ threshold: float = 0.7
262
+ ) -> List[QueryResult]:
263
+ """
264
+ Encontra memórias similares a uma memória específica.
265
+
266
+ Args:
267
+ memory_id: ID da memória de referência
268
+ limit: Limite de resultados
269
+ threshold: Threshold de similaridade
270
+
271
+ Returns:
272
+ Lista de memórias similares
273
+ """
274
+ # Obtém memória de referência
275
+ memory = self.metadata_db.get_by_id(memory_id)
276
+ if not memory:
277
+ return []
278
+
279
+ # Busca similares
280
+ query = memory.get("title", "") + " " + (memory.get("content", "") or "")[:200]
281
+ similar = self.embeddings_db.search_similar(
282
+ query,
283
+ memory.get("project"),
284
+ limit + 1,
285
+ threshold
286
+ )
287
+
288
+ # Remove a própria memória dos resultados
289
+ similar = [s for s in similar if s["memory_id"] != memory_id]
290
+
291
+ results = []
292
+ for item in similar[:limit]:
293
+ mem = self.metadata_db.get_by_id(item["memory_id"])
294
+ results.append(QueryResult(
295
+ memory_id=item["memory_id"],
296
+ type=item["type"],
297
+ project=item["project"],
298
+ title=mem.get("title", item["memory_id"]) if mem else item["memory_id"],
299
+ score=item["similarity"],
300
+ source="semantic",
301
+ metadata={"similarity": item["similarity"]}
302
+ ))
303
+
304
+ return results
305
+
306
+ def get_related(
307
+ self,
308
+ memory_id: str,
309
+ by_tags: bool = True,
310
+ by_semantic: bool = True,
311
+ limit: int = 10
312
+ ) -> List[QueryResult]:
313
+ """
314
+ Obtém memórias relacionadas por múltiplos critérios.
315
+
316
+ Args:
317
+ memory_id: ID da memória
318
+ by_tags: Buscar por tags similares
319
+ by_semantic: Buscar por similaridade semântica
320
+ limit: Limite de resultados
321
+
322
+ Returns:
323
+ Lista de memórias relacionadas
324
+ """
325
+ memory = self.metadata_db.get_by_id(memory_id)
326
+ if not memory:
327
+ return []
328
+
329
+ related: Dict[str, QueryResult] = {}
330
+
331
+ # Por tags
332
+ if by_tags and memory.get("tags"):
333
+ tags = memory["tags"].split(",")
334
+ for tag in tags:
335
+ tag_results = self.search_by_metadata(tags=[tag.strip()], limit=limit)
336
+ for r in tag_results:
337
+ if r.memory_id != memory_id:
338
+ if r.memory_id not in related:
339
+ related[r.memory_id] = r
340
+ else:
341
+ related[r.memory_id].score += 0.1
342
+
343
+ # Por similaridade semântica
344
+ if by_semantic:
345
+ semantic_results = self.find_similar_to_memory(memory_id, limit)
346
+ for r in semantic_results:
347
+ if r.memory_id not in related:
348
+ related[r.memory_id] = r
349
+
350
+ # Ordena e limita
351
+ sorted_related = sorted(
352
+ related.values(),
353
+ key=lambda x: x.score,
354
+ reverse=True
355
+ )
356
+
357
+ return sorted_related[:limit]
@@ -0,0 +1,2 @@
1
+ """MCP Server do Cerebro"""
2
+ __version__ = "0.1.0"