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,640 @@
1
+ """MCP Server do Cerebro: Integração com Claude Code"""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from mcp.server import Server
9
+ from mcp.server.stdio import stdio_server
10
+ from mcp.types import Tool, TextContent
11
+
12
+
13
+ def _safe_print_error(msg: str) -> None:
14
+ """Print seguro para stderr com fallback de encoding."""
15
+ try:
16
+ print(msg, file=sys.stderr)
17
+ except UnicodeEncodeError:
18
+ print(msg.encode("ascii", errors="replace").decode("ascii"), file=sys.stderr)
19
+
20
+ from src.core.jsonl_storage import JSONLStorage
21
+ from src.core.session_manager import SessionManager
22
+ from src.working.yaml_storage import YAMLStorage
23
+ from src.working.memory_view import MemoryView
24
+ from src.official.markdown_storage import MarkdownStorage
25
+ from src.consolidation.extractor import Extractor
26
+ from src.consolidation.promoter import Promoter
27
+ from src.index.metadata_db import MetadataDB
28
+ from src.index.embeddings_db import EmbeddingsDB
29
+ from src.index.queries import QueryEngine
30
+ from src.hooks.custom_loader import HooksLoader, HookRunner
31
+ from src.diff.memory_diff import MemoryDiff
32
+ from src.consolidation.dream import run_dream, generate_dream_report
33
+ from src.consolidation.remember import run_remember, generate_remember_report
34
+ from src.forgetting.gc import GarbageCollector
35
+ from src.core.paths import get_auto_mem_path
36
+
37
+
38
+ class CerebroMCP:
39
+ """
40
+ MCP Server para integração do Cerebro com Claude Code.
41
+
42
+ Ferramentas disponíveis:
43
+ - cerebro_memory: Visualizar memória ativa
44
+ - cerebro_search: Buscar memórias
45
+ - cerebro_checkpoint: Criar checkpoint manual
46
+ - cerebro_promote: Promover draft para official
47
+ - cerebro_status: Status do sistema
48
+ - cerebro_hooks: Listar e gerenciar hooks customizados
49
+ """
50
+
51
+ def __init__(self, cerebro_path: Optional[Path] = None):
52
+ """
53
+ Inicializa o MCP Server.
54
+
55
+ Args:
56
+ cerebro_path: Diretório base do OCerebro (default: .ocerebro)
57
+ """
58
+ self.cerebro_path = cerebro_path or Path(".ocerebro")
59
+ self.cerebro_path.mkdir(parents=True, exist_ok=True)
60
+
61
+ # Inicializa componentes
62
+ self.raw_storage = JSONLStorage(self.cerebro_path / "raw")
63
+ self.working_storage = YAMLStorage(self.cerebro_path / "working")
64
+ self.official_storage = MarkdownStorage(self.cerebro_path / "official")
65
+ self.session_manager = SessionManager(self.cerebro_path)
66
+
67
+ self.metadata_db = MetadataDB(self.cerebro_path / "index" / "metadata.db")
68
+ self.embeddings_db = EmbeddingsDB(self.cerebro_path / "index" / "embeddings.db")
69
+ self.query_engine = QueryEngine(self.metadata_db, self.embeddings_db)
70
+
71
+ self.extractor = Extractor(self.raw_storage, self.working_storage)
72
+ self.promoter = Promoter(self.working_storage, self.official_storage)
73
+
74
+ self.memory_view = MemoryView(
75
+ self.cerebro_path,
76
+ self.official_storage,
77
+ self.working_storage
78
+ )
79
+
80
+ # Inicializa memory diff
81
+ self.memory_diff = MemoryDiff(
82
+ self.official_storage,
83
+ self.working_storage,
84
+ self.raw_storage
85
+ )
86
+
87
+ # Inicializa hooks com tratamento de erro
88
+ # WARN-04 FIX: hooks.yaml com erro não derruba o servidor
89
+ hooks_config = self.cerebro_path.parent / "hooks.yaml"
90
+ try:
91
+ self.hooks_loader = HooksLoader(hooks_config) if hooks_config.exists() else None
92
+ self.hooks_runner = HookRunner(self.hooks_loader) if self.hooks_loader else None
93
+ except Exception as e:
94
+ # WINDOWS FIX: Usa _safe_print_error para evitar UnicodeEncodeError
95
+ _safe_print_error(f"[CEREBRO] Aviso: hooks.yaml com erro ({e}). Hooks desativados.")
96
+ self.hooks_loader = None
97
+ self.hooks_runner = None
98
+
99
+ def get_tools(self) -> List[Tool]:
100
+ """
101
+ Retorna lista de ferramentas MCP.
102
+
103
+ Returns:
104
+ Lista de ferramentas disponíveis
105
+ """
106
+ return [
107
+ Tool(
108
+ name="cerebro_memory",
109
+ description="Visualizar memória ativa do projeto (MEMORY.md)",
110
+ inputSchema={
111
+ "type": "object",
112
+ "properties": {
113
+ "project": {
114
+ "type": "string",
115
+ "description": "Nome do projeto"
116
+ }
117
+ },
118
+ "required": ["project"]
119
+ }
120
+ ),
121
+ Tool(
122
+ name="cerebro_search",
123
+ description="Buscar memórias por texto, tags ou metadados",
124
+ inputSchema={
125
+ "type": "object",
126
+ "properties": {
127
+ "query": {
128
+ "type": "string",
129
+ "description": "Texto de busca"
130
+ },
131
+ "project": {
132
+ "type": "string",
133
+ "description": "Filtrar por projeto"
134
+ },
135
+ "type": {
136
+ "type": "string",
137
+ "description": "Tipo (decision, error, etc)"
138
+ },
139
+ "limit": {
140
+ "type": "integer",
141
+ "description": "Limite de resultados",
142
+ "default": 10
143
+ }
144
+ },
145
+ "required": ["query"]
146
+ }
147
+ ),
148
+ Tool(
149
+ name="cerebro_checkpoint",
150
+ description="Criar checkpoint manual de uma sessão",
151
+ inputSchema={
152
+ "type": "object",
153
+ "properties": {
154
+ "project": {
155
+ "type": "string",
156
+ "description": "Nome do projeto"
157
+ },
158
+ "session_id": {
159
+ "type": "string",
160
+ "description": "ID da sessão (opcional, usa atual)"
161
+ },
162
+ "reason": {
163
+ "type": "string",
164
+ "description": "Motivo do checkpoint",
165
+ "default": "manual"
166
+ }
167
+ },
168
+ "required": ["project"]
169
+ }
170
+ ),
171
+ Tool(
172
+ name="cerebro_promote",
173
+ description="Promover draft da working layer para official",
174
+ inputSchema={
175
+ "type": "object",
176
+ "properties": {
177
+ "project": {
178
+ "type": "string",
179
+ "description": "Nome do projeto"
180
+ },
181
+ "draft_id": {
182
+ "type": "string",
183
+ "description": "ID do draft"
184
+ },
185
+ "draft_type": {
186
+ "type": "string",
187
+ "description": "Tipo do draft",
188
+ "default": "session"
189
+ },
190
+ "promote_to": {
191
+ "type": "string",
192
+ "description": "Tipo de destino",
193
+ "default": "decision"
194
+ }
195
+ },
196
+ "required": ["project", "draft_id"]
197
+ }
198
+ ),
199
+ Tool(
200
+ name="cerebro_status",
201
+ description="Obter status do sistema Cerebro",
202
+ inputSchema={
203
+ "type": "object",
204
+ "properties": {}
205
+ }
206
+ ),
207
+ Tool(
208
+ name="cerebro_hooks",
209
+ description="Listar e gerenciar hooks customizados",
210
+ inputSchema={
211
+ "type": "object",
212
+ "properties": {
213
+ "action": {
214
+ "type": "string",
215
+ "description": "Ação: list, test, info",
216
+ "enum": ["list", "test", "info"],
217
+ "default": "list"
218
+ },
219
+ "event_type": {
220
+ "type": "string",
221
+ "description": "Filtrar por tipo de evento"
222
+ },
223
+ "hook_name": {
224
+ "type": "string",
225
+ "description": "Nome do hook (para ação 'info')"
226
+ }
227
+ }
228
+ }
229
+ ),
230
+ Tool(
231
+ name="cerebro_diff",
232
+ description="Análise diferencial de memória entre dois pontos no tempo - mostra decisões adicionadas, erros documentados, drafts pendentes e memórias em risco de GC",
233
+ inputSchema={
234
+ "type": "object",
235
+ "properties": {
236
+ "project": {
237
+ "type": "string",
238
+ "description": "Nome do projeto"
239
+ },
240
+ "period_days": {
241
+ "type": "integer",
242
+ "description": "Dias do período (padrão: 7)",
243
+ "default": 7
244
+ },
245
+ "start_date": {
246
+ "type": "string",
247
+ "description": "Data de início (ISO format, ex: 2026-03-01)"
248
+ },
249
+ "end_date": {
250
+ "type": "string",
251
+ "description": "Data de fim (ISO format, ex: 2026-03-31)"
252
+ },
253
+ "gc_threshold": {
254
+ "type": "number",
255
+ "description": "Threshold para GC risk (padrão: 0.3)",
256
+ "default": 0.3
257
+ },
258
+ "format": {
259
+ "type": "string",
260
+ "description": "Formato de saída",
261
+ "enum": ["markdown", "json"],
262
+ "default": "markdown"
263
+ }
264
+ },
265
+ "required": ["project"]
266
+ }
267
+ ),
268
+ Tool(
269
+ name="cerebro_dream",
270
+ description="Extração automática de memórias (replica extractMemories do Claude Code) - analisa transcript e extrai memórias para user/feedback/project/reference",
271
+ inputSchema={
272
+ "type": "object",
273
+ "properties": {
274
+ "since_days": {
275
+ "type": "integer",
276
+ "description": "Dias para analisar (padrão: 7)",
277
+ "default": 7
278
+ },
279
+ "dry_run": {
280
+ "type": "boolean",
281
+ "description": "Se True, apenas simula (padrão: True)",
282
+ "default": True
283
+ }
284
+ }
285
+ }
286
+ ),
287
+ Tool(
288
+ name="cerebro_remember",
289
+ description="Revisão e promoção de memórias (replica /remember do Claude Code) - classifica memórias por tipo e detecta duplicatas/conflitos entre camadas",
290
+ inputSchema={
291
+ "type": "object",
292
+ "properties": {
293
+ "dry_run": {
294
+ "type": "boolean",
295
+ "description": "Se True, apenas gera relatório (padrão: True)",
296
+ "default": True
297
+ }
298
+ }
299
+ }
300
+ ),
301
+ Tool(
302
+ name="cerebro_gc",
303
+ description="Garbage collection de memórias - lista memórias stale por mtime e remove candidatas (nunca remove user/feedback)",
304
+ inputSchema={
305
+ "type": "object",
306
+ "properties": {
307
+ "threshold_days": {
308
+ "type": "integer",
309
+ "description": "Dias para considerar memória stale (padrão: 7)",
310
+ "default": 7
311
+ },
312
+ "dry_run": {
313
+ "type": "boolean",
314
+ "description": "Se True, apenas lista candidatas (padrão: True)",
315
+ "default": True
316
+ }
317
+ }
318
+ }
319
+ )
320
+ ]
321
+
322
+ async def handle_tool(self, name: str, arguments: Dict[str, Any]) -> List[TextContent]:
323
+ """
324
+ Processa chamada de ferramenta.
325
+
326
+ Args:
327
+ name: Nome da ferramenta
328
+ arguments: Argumentos da chamada
329
+
330
+ Returns:
331
+ Resultado como lista de TextContent
332
+ """
333
+ try:
334
+ if name == "cerebro_memory":
335
+ result = self._memory(arguments)
336
+ elif name == "cerebro_search":
337
+ result = self._search(arguments)
338
+ elif name == "cerebro_checkpoint":
339
+ result = self._checkpoint(arguments)
340
+ elif name == "cerebro_promote":
341
+ result = self._promote(arguments)
342
+ elif name == "cerebro_status":
343
+ result = self._status()
344
+ elif name == "cerebro_hooks":
345
+ result = self._hooks(arguments)
346
+ elif name == "cerebro_diff":
347
+ result = self._diff(arguments)
348
+ elif name == "cerebro_dream":
349
+ result = self._dream(arguments)
350
+ elif name == "cerebro_remember":
351
+ result = self._remember(arguments)
352
+ elif name == "cerebro_gc":
353
+ result = self._gc(arguments)
354
+ else:
355
+ return [TextContent(type="text", text=f"Ferramenta desconhecida: {name}")]
356
+
357
+ return [TextContent(type="text", text=result)]
358
+ except Exception as e:
359
+ return [TextContent(type="text", text=f"Erro: {str(e)}")]
360
+
361
+ def _memory(self, args: Dict[str, Any]) -> str:
362
+ """Gera memória ativa"""
363
+ project = args.get("project")
364
+ if not project:
365
+ return "Erro: project é obrigatório"
366
+
367
+ return self.memory_view.generate(project)
368
+
369
+ def _search(self, args: Dict[str, Any]) -> str:
370
+ """Busca memórias"""
371
+ query = args.get("query", "")
372
+ project = args.get("project")
373
+ mem_type = args.get("type")
374
+ limit = args.get("limit", 10)
375
+
376
+ results = self.query_engine.search(
377
+ query=query,
378
+ project=project,
379
+ mem_type=mem_type,
380
+ limit=limit
381
+ )
382
+
383
+ if not results:
384
+ return "Nenhum resultado encontrado."
385
+
386
+ lines = [f"Resultados para '{query}':\n"]
387
+ for i, r in enumerate(results, 1):
388
+ lines.append(f"{i}. [{r.type}] {r.title}")
389
+ lines.append(f" Projeto: {r.project} | Score: {r.score:.3f} | Fonte: {r.source}")
390
+
391
+ return "\n".join(lines)
392
+
393
+ def _checkpoint(self, args: Dict[str, Any]) -> str:
394
+ """Cria checkpoint"""
395
+ project = args.get("project")
396
+ if not project:
397
+ return "Erro: project é obrigatório"
398
+
399
+ session_id = args.get("session_id") or self.session_manager.get_session_id()
400
+ reason = args.get("reason", "manual")
401
+
402
+ try:
403
+ result = self.extractor.extract_session(project, session_id)
404
+ except Exception as e:
405
+ return f"Erro ao extrair sessão: {e}"
406
+
407
+ if not result.events:
408
+ return f"Nenhum evento encontrado para sessão {session_id}"
409
+
410
+ draft = self.extractor.create_draft(result, "session")
411
+ draft["checkpoint_reason"] = reason
412
+ draft_name = self.extractor.write_draft(project, draft, "session")
413
+
414
+ # Registra evento
415
+ from src.core.event_schema import Event, EventType, EventOrigin
416
+ checkpoint_event = Event(
417
+ project=project,
418
+ origin=EventOrigin.USER,
419
+ event_type=EventType.CHECKPOINT_CREATED,
420
+ subtype="mcp",
421
+ payload={
422
+ "session_id": session_id,
423
+ "draft_name": draft_name,
424
+ "reason": reason
425
+ }
426
+ )
427
+ self.raw_storage.append(checkpoint_event)
428
+
429
+ return f"Checkpoint criado: {draft_name} ({len(result.events)} eventos)"
430
+
431
+ def _promote(self, args: Dict[str, Any]) -> str:
432
+ """Promove draft"""
433
+ project = args.get("project")
434
+ draft_id = args.get("draft_id")
435
+
436
+ if not project:
437
+ return "Erro: project é obrigatório"
438
+ if not draft_id:
439
+ return "Erro: draft_id é obrigatório"
440
+
441
+ draft_type = args.get("draft_type", "session")
442
+ promote_to = args.get("promote_to", "decision")
443
+
444
+ if draft_type == "session":
445
+ result = self.promoter.promote_session(project, draft_id, promote_to)
446
+ elif draft_type == "feature":
447
+ result = self.promoter.promote_feature(project, draft_id, promote_to)
448
+ else:
449
+ return f"Erro: Tipo de draft desconhecido: {draft_type}"
450
+
451
+ if result is None:
452
+ return "Draft não encontrado."
453
+
454
+ if result.success:
455
+ self.promoter.mark_promoted(project, draft_id, draft_type, result)
456
+ return f"Promovido para: {result.target_path}"
457
+ else:
458
+ return f"Promoção falhou: {result.metadata.get('reason', 'desconhecido')}"
459
+
460
+ def _status(self) -> str:
461
+ """Status do sistema"""
462
+ session_id = self.session_manager.get_session_id()
463
+
464
+ lines = [
465
+ "Status do Cerebro:",
466
+ f" Session ID: {session_id}",
467
+ f" Path: {self.cerebro_path.absolute()}",
468
+ "",
469
+ "Storages:",
470
+ f" Raw: {self.cerebro_path / 'raw'}",
471
+ f" Working: {self.cerebro_path / 'working'}",
472
+ f" Official: {self.cerebro_path / 'official'}",
473
+ "",
474
+ "Índice:",
475
+ f" Metadata DB: {self.cerebro_path / 'index' / 'metadata.db'}",
476
+ f" Embeddings DB: {self.cerebro_path / 'index' / 'embeddings.db'}"
477
+ ]
478
+
479
+ return "\n".join(lines)
480
+
481
+ def _hooks(self, args: Dict[str, Any]) -> str:
482
+ """Lista e gerencia hooks customizados"""
483
+ action = args.get("action", "list")
484
+ event_type = args.get("event_type")
485
+ hook_name = args.get("hook_name")
486
+
487
+ if self.hooks_loader is None:
488
+ return "Hooks não configurados. Crie um arquivo hooks.yaml na raiz do projeto."
489
+
490
+ if action == "list":
491
+ hooks = self.hooks_loader.hooks
492
+
493
+ if event_type:
494
+ hooks = [h for h in hooks if h.event_type == event_type or h.event_type == "*"]
495
+
496
+ if not hooks:
497
+ return "Nenhum hook configurado."
498
+
499
+ lines = ["Hooks customizados configurados:\n"]
500
+ for i, hook in enumerate(hooks, 1):
501
+ lines.append(f"{i}. **{hook.name}**")
502
+ lines.append(f" - Evento: {hook.event_type}:{hook.event_subtype or '*'}")
503
+ lines.append(f" - Módulo: {hook.module_path}")
504
+ lines.append(f" - Função: {hook.function}")
505
+ if hook.config:
506
+ lines.append(f" - Config: {hook.config}")
507
+ lines.append("")
508
+
509
+ return "\n".join(lines)
510
+
511
+ elif action == "info":
512
+ if not hook_name:
513
+ return "Erro: hook_name é obrigatório para ação 'info'"
514
+
515
+ hook = next((h for h in self.hooks_loader.hooks if h.name == hook_name), None)
516
+ if not hook:
517
+ return f"Hook '{hook_name}' não encontrado."
518
+
519
+ lines = [
520
+ f"## Hook: {hook.name}",
521
+ "",
522
+ f"- **Evento:** {hook.event_type}:{hook.event_subtype or '*'}",
523
+ f"- **Módulo:** {hook.module_path}",
524
+ f"- **Função:** {hook.function}",
525
+ f"- **Config:** {hook.config or {}}",
526
+ "",
527
+ "### Assinatura da função",
528
+ "",
529
+ "```python",
530
+ f"def {hook.function}(event: Event, context: dict, config: dict) -> dict:",
531
+ " # Seu código aqui",
532
+ "```"
533
+ ]
534
+
535
+ return "\n".join(lines)
536
+
537
+ elif action == "test":
538
+ # Simula execução de hook de teste
539
+ from src.core.event_schema import Event, EventType, EventOrigin
540
+
541
+ test_event = Event(
542
+ project="test",
543
+ origin=EventOrigin.CLAUDE_CODE,
544
+ event_type=EventType.TOOL_CALL,
545
+ subtype="bash",
546
+ payload={"command": "echo test", "duration": 0.1}
547
+ )
548
+
549
+ if self.hooks_runner is None:
550
+ return "Hooks runner não inicializado."
551
+
552
+ results = self.hooks_runner.execute(test_event)
553
+
554
+ lines = ["Resultado do teste de hooks:\n"]
555
+ for name, result in results.items():
556
+ status = "✅" if result["success"] else "❌"
557
+ lines.append(f"{status} {name}: {result.get('result', result.get('error', 'N/A'))}")
558
+
559
+ return "\n".join(lines)
560
+
561
+ return f"Ação desconhecida: {action}"
562
+
563
+ def _diff(self, args: Dict[str, Any]) -> str:
564
+ """Análise diferencial de memória"""
565
+ project = args.get("project")
566
+ if not project:
567
+ return "Erro: project é obrigatório"
568
+
569
+ period_days = args.get("period_days", 7)
570
+ start_date = args.get("start_date")
571
+ end_date = args.get("end_date")
572
+ gc_threshold = args.get("gc_threshold", 0.3)
573
+ format = args.get("format", "markdown")
574
+
575
+ result = self.memory_diff.analyze(
576
+ project=project,
577
+ period_days=period_days if not start_date else None,
578
+ start_date=start_date,
579
+ end_date=end_date,
580
+ gc_threshold=gc_threshold
581
+ )
582
+
583
+ return self.memory_diff.generate_report(result, format=format)
584
+
585
+ def _dream(self, args: Dict[str, Any]) -> str:
586
+ """Extração automática de memórias"""
587
+ since_days = args.get("since_days", 7)
588
+ dry_run = args.get("dry_run", True)
589
+
590
+ memory_dir = get_auto_mem_path()
591
+ result = run_dream(memory_dir=memory_dir, since_days=since_days, dry_run=dry_run)
592
+ return generate_dream_report(result)
593
+
594
+ def _remember(self, args: Dict[str, Any]) -> str:
595
+ """Revisão e promoção de memórias"""
596
+ dry_run = args.get("dry_run", True)
597
+
598
+ report = run_remember(dry_run=dry_run)
599
+ return generate_remember_report(report)
600
+
601
+ def _gc(self, args: Dict[str, Any]) -> str:
602
+ """Garbage collection de memórias"""
603
+ threshold_days = args.get("threshold_days", 7)
604
+ dry_run = args.get("dry_run", True)
605
+
606
+ memory_dir = get_auto_mem_path()
607
+ gc = GarbageCollector(memory_dir)
608
+ results = gc.run_gc(
609
+ memory_dir=memory_dir,
610
+ archive_threshold_days=threshold_days,
611
+ deletion_threshold_days=threshold_days * 4,
612
+ dry_run=dry_run
613
+ )
614
+ return gc.generate_gc_report(results)
615
+
616
+
617
+ async def main():
618
+ """Entry point do MCP Server"""
619
+ server = Server("cerebro")
620
+ cerebro = CerebroMCP()
621
+
622
+ @server.list_tools()
623
+ async def list_tools() -> List[Tool]:
624
+ return cerebro.get_tools()
625
+
626
+ @server.call_tool()
627
+ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
628
+ return await cerebro.handle_tool(name, arguments)
629
+
630
+ async with stdio_server() as (read_stream, write_stream):
631
+ await server.run(
632
+ read_stream,
633
+ write_stream,
634
+ server.create_initialization_options()
635
+ )
636
+
637
+
638
+ if __name__ == "__main__":
639
+ import asyncio
640
+ asyncio.run(main())
@@ -0,0 +1,19 @@
1
+ """Memdir: sistema de memória do Claude Code"""
2
+
3
+ from src.memdir.scanner import (
4
+ MemoryHeader,
5
+ MemoryType,
6
+ scan_memory_files,
7
+ format_memory_manifest,
8
+ get_existing_memories_summary,
9
+ parse_frontmatter,
10
+ )
11
+
12
+ __all__ = [
13
+ "MemoryHeader",
14
+ "MemoryType",
15
+ "scan_memory_files",
16
+ "format_memory_manifest",
17
+ "get_existing_memories_summary",
18
+ "parse_frontmatter",
19
+ ]