ocerebro 0.1.9 → 0.2.1

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.
@@ -187,41 +187,77 @@ def merge_configs(existing: dict, new: dict) -> dict:
187
187
  return result
188
188
 
189
189
 
190
- def setup_slash_commands(project_path: Path) -> bool:
191
- """Cria slash commands /cerebro no .claude/commands/ do projeto."""
190
+ def setup_slash_commands(project_path: Path | None = None, global_commands: bool = True) -> bool:
191
+ """Cria slash commands /cerebro no .claude/commands/ do projeto e global."""
192
192
 
193
- commands_dir = project_path / ".claude" / "commands"
194
- commands_dir.mkdir(parents=True, exist_ok=True)
193
+ if project_path:
194
+ commands_dir = project_path / ".claude" / "commands"
195
+ commands_dir.mkdir(parents=True, exist_ok=True)
195
196
 
196
- dream_cmd = commands_dir / "cerebro-dream.md"
197
- if not dream_cmd.exists():
198
- dream_cmd.write_text("""---
197
+ dream_cmd = commands_dir / "cerebro-dream.md"
198
+ if not dream_cmd.exists():
199
+ dream_cmd.write_text("""---
199
200
  description: Extrair memórias da sessão atual
200
201
  ---
201
202
  Execute: ocerebro dream --since 7 --apply
202
203
  Mostre o relatório completo do que foi salvo.
203
204
  """, encoding="utf-8")
204
- print(f"[OK] Slash command criado: {dream_cmd}")
205
+ print(f"[OK] Slash command criado: {dream_cmd}")
205
206
 
206
- status_cmd = commands_dir / "cerebro-status.md"
207
- if not status_cmd.exists():
208
- status_cmd.write_text("""---
207
+ status_cmd = commands_dir / "cerebro-status.md"
208
+ if not status_cmd.exists():
209
+ status_cmd.write_text("""---
209
210
  description: Ver status da memória do projeto
210
211
  ---
211
212
  Execute: ocerebro status
212
213
  Liste quantas memórias existem por tipo.
213
214
  """, encoding="utf-8")
214
- print(f"[OK] Slash command criado: {status_cmd}")
215
+ print(f"[OK] Slash command criado: {status_cmd}")
215
216
 
216
- gc_cmd = commands_dir / "cerebro-gc.md"
217
- if not gc_cmd.exists():
218
- gc_cmd.write_text("""---
217
+ gc_cmd = commands_dir / "cerebro-gc.md"
218
+ if not gc_cmd.exists():
219
+ gc_cmd.write_text("""---
219
220
  description: Limpeza de memórias antigas
220
221
  ---
221
222
  Execute: ocerebro gc --threshold 30
222
223
  Mostre o que será arquivado antes de confirmar.
223
224
  """, encoding="utf-8")
224
- print(f"[OK] Slash command criado: {gc_cmd}")
225
+ print(f"[OK] Slash command criado: {gc_cmd}")
226
+
227
+ # Slash commands globais em ~/.claude/commands/
228
+ if global_commands:
229
+ global_commands_dir = Path.home() / ".claude" / "commands"
230
+ global_commands_dir.mkdir(parents=True, exist_ok=True)
231
+
232
+ dream_global = global_commands_dir / "cerebro-dream.md"
233
+ if not dream_global.exists():
234
+ dream_global.write_text("""---
235
+ description: Extrair memórias da sessão atual (global)
236
+ ---
237
+ Execute: ocerebro dream --since 7 --apply
238
+ Mostre o relatório completo do que foi salvo.
239
+ """, encoding="utf-8")
240
+ print(f"[OK] Slash command global criado: {dream_global}")
241
+
242
+ status_global = global_commands_dir / "cerebro-status.md"
243
+ if not status_global.exists():
244
+ status_global.write_text("""---
245
+ description: Ver status da memória (global)
246
+ ---
247
+ Execute: ocerebro status
248
+ Liste quantas memórias existem por tipo.
249
+ """, encoding="utf-8")
250
+ print(f"[OK] Slash command global criado: {status_global}")
251
+
252
+ gc_global = global_commands_dir / "cerebro-gc.md"
253
+ if not gc_global.exists():
254
+ gc_global.write_text("""---
255
+ description: Limpeza de memórias antigas (global)
256
+ ---
257
+ Execute: ocerebro gc --threshold 30
258
+ Mostre o que será arquivado antes de confirmar.
259
+ """, encoding="utf-8")
260
+ print(f"[OK] Slash command global criado: {gc_global}")
225
261
 
226
262
  return True
227
263
 
@@ -358,6 +394,23 @@ def setup_claude(auto: bool = True) -> bool:
358
394
  existing_config["mcp"] = {}
359
395
  existing_config["mcp"]["enabled"] = True
360
396
 
397
+ # Adiciona hook para dream automatico ao final da sessao
398
+ if "hooks" not in existing_config:
399
+ existing_config["hooks"] = {}
400
+
401
+ # Hook Stop: roda dream ao final de cada sessao
402
+ existing_config["hooks"]["Stop"] = [
403
+ {
404
+ "matcher": "",
405
+ "hooks": [
406
+ {
407
+ "type": "command",
408
+ "command": f"{python_cmd} -m src.cli.main dream --since 1 --apply --silent"
409
+ }
410
+ ]
411
+ }
412
+ ]
413
+
361
414
  config_path.write_text(
362
415
  json.dumps(existing_config, indent=2, ensure_ascii=False),
363
416
  encoding="utf-8"
@@ -503,7 +556,7 @@ def main():
503
556
  project = Path(sys.argv[2]) if len(sys.argv) > 2 else Path.cwd()
504
557
  setup_ocerebro_dir(project)
505
558
  setup_hooks(project)
506
- setup_slash_commands(project)
559
+ setup_slash_commands(project=project)
507
560
  setup_claude(auto=True)
508
561
  sys.exit(0)
509
562
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocerebro",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
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.1.9"
7
+ version = "0.2.1"
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/cli/main.py CHANGED
@@ -363,6 +363,7 @@ def main():
363
363
  dream_parser = subparsers.add_parser("dream", help="Extração automática de memórias")
364
364
  dream_parser.add_argument("--since", type=int, default=7, dest="since_days")
365
365
  dream_parser.add_argument("--apply", action="store_true", dest="apply")
366
+ dream_parser.add_argument("--silent", action="store_true", dest="silent", help="Não imprimir output (para hooks)")
366
367
 
367
368
  # Comando: remember
368
369
  remember_parser = subparsers.add_parser("remember", help="Revisão e promoção de memórias")
@@ -434,6 +435,8 @@ def main():
434
435
  )
435
436
  elif args.command == "dream":
436
437
  result = cli.dream(since_days=args.since_days, dry_run=not args.apply)
438
+ if getattr(args, 'silent', False):
439
+ sys.exit(0)
437
440
  elif args.command == "remember":
438
441
  result = cli.remember(dry_run=not args.apply)
439
442
  elif args.command == "gc":
@@ -442,6 +445,8 @@ def main():
442
445
  parser.print_help()
443
446
  sys.exit(1)
444
447
 
448
+ if getattr(args, 'silent', False):
449
+ sys.exit(0)
445
450
  print(result)
446
451
 
447
452
 
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,28 @@ 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="Captura memórias da conversa atual e salva nos arquivos - requer prompt do cerebro_dream",
300
+ inputSchema={
301
+ "type": "object",
302
+ "properties": {
303
+ "prompt": {
304
+ "type": "string",
305
+ "description": "Prompt de extração retornado por cerebro_dream"
306
+ },
307
+ "memory_dir": {
308
+ "type": "string",
309
+ "description": "Diretório de memória (opcional)"
310
+ }
311
+ },
312
+ "required": ["prompt"]
313
+ }
314
+ ),
302
315
  Tool(
303
316
  name="cerebro_remember",
304
317
  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 +379,8 @@ class CerebroMCP:
366
379
  result = self._remember(arguments)
367
380
  elif name == "cerebro_gc":
368
381
  result = self._gc(arguments)
382
+ elif name == "cerebro_capture_memory":
383
+ result = self._capture_memory(arguments)
369
384
  else:
370
385
  return [TextContent(type="text", text=f"Ferramenta desconhecida: {name}")]
371
386
 
@@ -598,13 +613,78 @@ class CerebroMCP:
598
613
  return self.memory_diff.generate_report(result, format=format)
599
614
 
600
615
  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)
616
+ """Prepara prompt para extração de memórias.
617
+
618
+ Retorna o prompt de extração + instruções para usar cerebro_capture_memory.
619
+ """
620
+ from src.consolidation.dream import (
621
+ run_dream,
622
+ build_extract_dream_prompt,
623
+ scan_memory_files,
624
+ format_memory_manifest,
625
+ count_transcript_messages
626
+ )
604
627
 
628
+ since_days = args.get("since_days", 7)
605
629
  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)
630
+
631
+ # Scan de memórias existentes
632
+ existing = scan_memory_files(memory_dir)
633
+ existing_manifest = format_memory_manifest(existing)
634
+
635
+ # Contagem de mensagens novas
636
+ message_count = count_transcript_messages(since_days)
637
+
638
+ if message_count == 0:
639
+ return "Nenhuma mensagem nova nos últimos {} dias. O prompt de extração não será gerado.".format(since_days)
640
+
641
+ # Build do prompt
642
+ prompt_sections = build_extract_dream_prompt(
643
+ new_message_count=message_count,
644
+ existing_memories=existing_manifest,
645
+ memory_dir=memory_dir,
646
+ )
647
+ full_prompt = "\n".join(prompt_sections)
648
+
649
+ return f"""=== PROMPT DE EXTRAÇÃO ({message_count} mensagens, {since_days} dias) ===
650
+
651
+ {full_prompt}
652
+
653
+ ---
654
+ INSTRUÇÃO: Copie este prompt e use a ferramenta cerebro_capture_memory com:
655
+ {{"prompt": "<cole o prompt acima>"}}
656
+
657
+ Ou execute manualmente: ocerebro dream --since {since_days} --apply
658
+ """
659
+
660
+ def _capture_memory(self, args: Dict[str, Any]) -> str:
661
+ """Captura memórias usando o prompt fornecido.
662
+
663
+ Esta ferramenta deve ser chamada após cerebro_dream retornar o prompt.
664
+ """
665
+ prompt = args.get("prompt")
666
+ if not prompt:
667
+ return "Erro: 'prompt' é obrigatório para cerebro_capture_memory"
668
+
669
+ memory_dir_str = args.get("memory_dir")
670
+ memory_dir = Path(memory_dir_str) if memory_dir_str else get_auto_mem_path()
671
+
672
+ # Instrui o Claude a usar o prompt para extrair memórias
673
+ return f"""Prompt de extração recebido.
674
+
675
+ Diretório de memória: {memory_dir}
676
+
677
+ O Claude deve agora analisar a conversa usando este prompt e salvar as memórias
678
+ nos arquivos .md apropriados em {memory_dir}
679
+
680
+ Formato esperado:
681
+ - user_*.md para memórias sobre o usuário
682
+ - feedback_*.md para feedbacks e convenções
683
+ - project_*.md para decisões e fatos do projeto
684
+ - reference_*.md para ponteiros externos
685
+
686
+ Use FileWrite para criar os arquivos de memória.
687
+ """
608
688
 
609
689
  def _remember(self, args: Dict[str, Any]) -> str:
610
690
  """Revisão e promoção de memórias"""