ocerebro 0.2.0 → 0.2.2
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.
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/mcp/server.py +100 -11
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/mcp/server.py
CHANGED
|
@@ -282,7 +282,7 @@ class CerebroMCP:
|
|
|
282
282
|
),
|
|
283
283
|
Tool(
|
|
284
284
|
name="cerebro_dream",
|
|
285
|
-
description="
|
|
285
|
+
description="Prepara prompt para extração de memórias - use cerebro_capture_memory após receber o prompt",
|
|
286
286
|
inputSchema={
|
|
287
287
|
"type": "object",
|
|
288
288
|
"properties": {
|
|
@@ -290,15 +290,24 @@ class CerebroMCP:
|
|
|
290
290
|
"type": "integer",
|
|
291
291
|
"description": "Dias para analisar (padrão: 7)",
|
|
292
292
|
"default": 7
|
|
293
|
-
},
|
|
294
|
-
"dry_run": {
|
|
295
|
-
"type": "boolean",
|
|
296
|
-
"description": "Se True, apenas simula (padrão: True)",
|
|
297
|
-
"default": True
|
|
298
293
|
}
|
|
299
294
|
}
|
|
300
295
|
}
|
|
301
296
|
),
|
|
297
|
+
Tool(
|
|
298
|
+
name="cerebro_capture_memory",
|
|
299
|
+
description="Salva uma memória diretamente em ~/.claude/memory/ (formato nativo Claude Code). Chame uma vez por memória com o conteúdo Markdown completo.",
|
|
300
|
+
inputSchema={
|
|
301
|
+
"type": "object",
|
|
302
|
+
"properties": {
|
|
303
|
+
"memory_content": {
|
|
304
|
+
"type": "string",
|
|
305
|
+
"description": "Conteúdo Markdown completo com frontmatter obrigatório: name, description, type (user|feedback|project|reference)"
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
"required": ["memory_content"]
|
|
309
|
+
}
|
|
310
|
+
),
|
|
302
311
|
Tool(
|
|
303
312
|
name="cerebro_remember",
|
|
304
313
|
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",
|
|
@@ -366,6 +375,8 @@ class CerebroMCP:
|
|
|
366
375
|
result = self._remember(arguments)
|
|
367
376
|
elif name == "cerebro_gc":
|
|
368
377
|
result = self._gc(arguments)
|
|
378
|
+
elif name == "cerebro_capture_memory":
|
|
379
|
+
result = self._capture_memory(arguments)
|
|
369
380
|
else:
|
|
370
381
|
return [TextContent(type="text", text=f"Ferramenta desconhecida: {name}")]
|
|
371
382
|
|
|
@@ -598,13 +609,91 @@ class CerebroMCP:
|
|
|
598
609
|
return self.memory_diff.generate_report(result, format=format)
|
|
599
610
|
|
|
600
611
|
def _dream(self, args: Dict[str, Any]) -> str:
|
|
601
|
-
"""
|
|
602
|
-
since_days = args.get("since_days", 7)
|
|
603
|
-
dry_run = args.get("dry_run", True)
|
|
612
|
+
"""Prepara prompt para extração de memórias.
|
|
604
613
|
|
|
614
|
+
Retorna o prompt de extração + instruções para usar cerebro_capture_memory.
|
|
615
|
+
"""
|
|
616
|
+
from src.consolidation.dream import (
|
|
617
|
+
run_dream,
|
|
618
|
+
build_extract_dream_prompt,
|
|
619
|
+
scan_memory_files,
|
|
620
|
+
format_memory_manifest,
|
|
621
|
+
count_transcript_messages
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
since_days = args.get("since_days", 7)
|
|
605
625
|
memory_dir = get_auto_mem_path()
|
|
606
|
-
|
|
607
|
-
|
|
626
|
+
|
|
627
|
+
# Scan de memórias existentes
|
|
628
|
+
existing = scan_memory_files(memory_dir)
|
|
629
|
+
existing_manifest = format_memory_manifest(existing)
|
|
630
|
+
|
|
631
|
+
# Contagem de mensagens novas
|
|
632
|
+
message_count = count_transcript_messages(since_days)
|
|
633
|
+
|
|
634
|
+
if message_count == 0:
|
|
635
|
+
return "Nenhuma mensagem nova nos últimos {} dias. O prompt de extração não será gerado.".format(since_days)
|
|
636
|
+
|
|
637
|
+
# Build do prompt
|
|
638
|
+
prompt_sections = build_extract_dream_prompt(
|
|
639
|
+
new_message_count=message_count,
|
|
640
|
+
existing_memories=existing_manifest,
|
|
641
|
+
memory_dir=memory_dir,
|
|
642
|
+
)
|
|
643
|
+
full_prompt = "\n".join(prompt_sections)
|
|
644
|
+
|
|
645
|
+
return f"""=== PROMPT DE EXTRAÇÃO ({message_count} mensagens, {since_days} dias) ===
|
|
646
|
+
|
|
647
|
+
{full_prompt}
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
INSTRUÇÃO CRÍTICA: Analise esta conversa usando o prompt acima.
|
|
651
|
+
Para CADA memória identificada, chame cerebro_capture_memory UMA vez:
|
|
652
|
+
|
|
653
|
+
cerebro_capture_memory(memory_content="---\\nname: <nome>\\ndescription: <descrição>\\ntype: <user|feedback|project|reference>\\n---\\n\\n<conteúdo>")
|
|
654
|
+
|
|
655
|
+
NÃO use FileWrite. NÃO use FileEdit. APENAS cerebro_capture_memory.
|
|
656
|
+
Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
657
|
+
"""
|
|
658
|
+
|
|
659
|
+
def _capture_memory(self, args: Dict[str, Any]) -> str:
|
|
660
|
+
"""Salva uma memória no diretório nativo do Claude Code."""
|
|
661
|
+
import re
|
|
662
|
+
from datetime import datetime
|
|
663
|
+
from src.core.paths import get_memory_index
|
|
664
|
+
|
|
665
|
+
content = args.get("memory_content", "")
|
|
666
|
+
if not content:
|
|
667
|
+
return "Erro: 'memory_content' é obrigatório"
|
|
668
|
+
|
|
669
|
+
name_match = re.search(r'name:\s*(.*)', content)
|
|
670
|
+
if not name_match:
|
|
671
|
+
return "Erro: frontmatter 'name' é obrigatório no memory_content"
|
|
672
|
+
|
|
673
|
+
mem_name = name_match.group(1).strip().lower().replace(' ', '-')
|
|
674
|
+
mem_dir = get_auto_mem_path()
|
|
675
|
+
mem_dir.mkdir(parents=True, exist_ok=True)
|
|
676
|
+
|
|
677
|
+
file_path = mem_dir / f"{mem_name}.md"
|
|
678
|
+
file_path.write_text(content, encoding="utf-8")
|
|
679
|
+
|
|
680
|
+
desc_match = re.search(r'description:\s*(.*)', content)
|
|
681
|
+
type_match = re.search(r'type:\s*(.*)', content)
|
|
682
|
+
desc = desc_match.group(1).strip() if desc_match else "sem descrição"
|
|
683
|
+
m_type = type_match.group(1).strip() if type_match else "project"
|
|
684
|
+
ts = datetime.now().strftime("%Y-%m-%d")
|
|
685
|
+
entry = f"- [{m_type}] {mem_name}.md ({ts}): {desc}\n"
|
|
686
|
+
|
|
687
|
+
index_path = get_memory_index(mem_dir)
|
|
688
|
+
if index_path.exists():
|
|
689
|
+
existing = index_path.read_text(encoding="utf-8")
|
|
690
|
+
if mem_name not in existing:
|
|
691
|
+
with open(index_path, "a", encoding="utf-8") as f:
|
|
692
|
+
f.write(entry)
|
|
693
|
+
else:
|
|
694
|
+
index_path.write_text(f"# Memórias do Projeto\n\n{entry}", encoding="utf-8")
|
|
695
|
+
|
|
696
|
+
return f"✅ Memória '{mem_name}' salva em {file_path}"
|
|
608
697
|
|
|
609
698
|
def _remember(self, args: Dict[str, Any]) -> str:
|
|
610
699
|
"""Revisão e promoção de memórias"""
|