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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocerebro",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "OCerebro - Sistema de Memoria para Agentes (Claude Code/MCP)",
5
5
  "main": "bin/ocerebro.js",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ocerebro"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "OCerebro - Sistema de Memoria para Agentes (Claude Code/MCP)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
package/src/mcp/server.py CHANGED
@@ -282,7 +282,7 @@ class CerebroMCP:
282
282
  ),
283
283
  Tool(
284
284
  name="cerebro_dream",
285
- description="Extração automática de memórias (replica extractMemories do Claude Code) - analisa transcript e extrai memórias para user/feedback/project/reference",
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
- """Extração automática de memórias"""
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
- result = run_dream(memory_dir=memory_dir, since_days=since_days, dry_run=dry_run)
607
- return generate_dream_report(result)
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"""