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,583 @@
1
+ """CLI do Cerebro: comandos manuais"""
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from src.core.jsonl_storage import JSONLStorage
11
+ from src.core.session_manager import SessionManager
12
+ from src.working.yaml_storage import YAMLStorage
13
+ from src.working.memory_view import MemoryView
14
+ from src.official.markdown_storage import MarkdownStorage
15
+ from src.consolidation.checkpoints import CheckpointManager, CheckpointTrigger
16
+ from src.consolidation.extractor import Extractor
17
+ from src.consolidation.promoter import Promoter
18
+ from src.index.metadata_db import MetadataDB
19
+ from src.index.embeddings_db import EmbeddingsDB
20
+ from src.index.queries import QueryEngine
21
+ from src.diff.memory_diff import MemoryDiff, MemoryDiffResult
22
+ from src.consolidation.dream import run_dream, generate_dream_report
23
+ from src.consolidation.remember import run_remember, generate_remember_report
24
+ from src.forgetting.gc import GarbageCollector
25
+
26
+
27
+ class CerebroCLI:
28
+ """
29
+ Interface de linha de comando do Cerebro.
30
+
31
+ Comandos disponíveis:
32
+ - checkpoint: Trigger manual de checkpoint
33
+ - memory: Visualizar memória ativa
34
+ - search: Buscar memórias
35
+ - promote: Promover draft para official
36
+ - gc: Garbage collection manual
37
+ - status: Status do sistema
38
+ """
39
+
40
+ def __init__(self, cerebro_path: Path):
41
+ """
42
+ Inicializa a CLI.
43
+
44
+ Args:
45
+ cerebro_path: Diretório base do Cerebro
46
+ """
47
+ self.cerebro_path = cerebro_path
48
+ self.cerebro_path.mkdir(parents=True, exist_ok=True)
49
+
50
+ # Inicializa storages
51
+ self.raw_storage = JSONLStorage(cerebro_path / "raw")
52
+ self.working_storage = YAMLStorage(cerebro_path / "working")
53
+ self.official_storage = MarkdownStorage(cerebro_path / "official")
54
+ self.session_manager = SessionManager(cerebro_path)
55
+
56
+ # Inicializa índice
57
+ self.metadata_db = MetadataDB(cerebro_path / "index" / "metadata.db")
58
+ self.embeddings_db = EmbeddingsDB(cerebro_path / "index" / "embeddings.db")
59
+ self.query_engine = QueryEngine(self.metadata_db, self.embeddings_db)
60
+
61
+ # Inicializa consolidação
62
+ self.checkpoint_manager = CheckpointManager(cerebro_path / "config")
63
+ self.extractor = Extractor(self.raw_storage, self.working_storage)
64
+ self.promoter = Promoter(self.working_storage, self.official_storage)
65
+
66
+ # Inicializa memory view
67
+ self.memory_view = MemoryView(cerebro_path, self.official_storage, self.working_storage)
68
+
69
+ # Inicializa memory diff
70
+ self.memory_diff = MemoryDiff(
71
+ self.official_storage,
72
+ self.working_storage,
73
+ self.raw_storage
74
+ )
75
+
76
+ def checkpoint(self, project: str, session_id: Optional[str] = None, reason: str = "manual") -> str:
77
+ """
78
+ Trigger manual de checkpoint.
79
+
80
+ Args:
81
+ project: Nome do projeto
82
+ session_id: ID da sessão (usa atual se None)
83
+ reason: Motivo do checkpoint
84
+
85
+ Returns:
86
+ Mensagem de resultado
87
+ """
88
+ if session_id is None:
89
+ session_id = self.session_manager.get_session_id()
90
+
91
+ # Extrai sessão
92
+ try:
93
+ result = self.extractor.extract_session(project, session_id)
94
+ except Exception as e:
95
+ return f"Erro ao extrair sessão: {e}"
96
+
97
+ if not result.events:
98
+ return f"Nenhum evento encontrado para sessão {session_id}"
99
+
100
+ # Cria draft
101
+ draft = self.extractor.create_draft(result, "session")
102
+ draft["checkpoint_reason"] = reason
103
+
104
+ # Escreve draft
105
+ draft_name = self.extractor.write_draft(project, draft, "session")
106
+
107
+ # Registra evento de checkpoint
108
+ from src.core.event_schema import Event, EventType, EventOrigin
109
+ checkpoint_event = Event(
110
+ project=project,
111
+ origin=EventOrigin.USER,
112
+ event_type=EventType.CHECKPOINT_CREATED,
113
+ subtype="manual",
114
+ payload={
115
+ "session_id": session_id,
116
+ "draft_name": draft_name,
117
+ "reason": reason,
118
+ "events_count": len(result.events)
119
+ }
120
+ )
121
+ self.raw_storage.append(checkpoint_event)
122
+
123
+ return f"Checkpoint criado: {draft_name} ({len(result.events)} eventos)"
124
+
125
+ def memory(self, project: str, output: Optional[Path] = None) -> str:
126
+ """
127
+ Gera visualização da memória ativa.
128
+
129
+ Args:
130
+ project: Nome do projeto
131
+ output: Arquivo de saída (opcional)
132
+
133
+ Returns:
134
+ Conteúdo do MEMORY.md
135
+ """
136
+ content = self.memory_view.generate(project)
137
+
138
+ if output:
139
+ output.write_text(content, encoding="utf-8")
140
+ return f"MEMORY.md gerado em: {output}"
141
+
142
+ return content
143
+
144
+ def search(
145
+ self,
146
+ query: str,
147
+ project: Optional[str] = None,
148
+ mem_type: Optional[str] = None,
149
+ limit: int = 10,
150
+ use_semantic: bool = True
151
+ ) -> str:
152
+ """
153
+ Busca memórias.
154
+
155
+ Args:
156
+ query: Texto de busca
157
+ project: Filtrar por projeto
158
+ mem_type: Filtrar por tipo
159
+ limit: Limite de resultados
160
+ use_semantic: Usar busca semântica
161
+
162
+ Returns:
163
+ Resultados formatados
164
+ """
165
+ results = self.query_engine.search(
166
+ query=query,
167
+ project=project,
168
+ mem_type=mem_type,
169
+ limit=limit,
170
+ use_semantic=use_semantic
171
+ )
172
+
173
+ if not results:
174
+ return "Nenhum resultado encontrado."
175
+
176
+ lines = [f"Resultados para '{query}':\n"]
177
+ for i, r in enumerate(results, 1):
178
+ lines.append(f"{i}. [{r.type}] {r.title}")
179
+ lines.append(f" Projeto: {r.project} | Score: {r.score:.3f} | Fonte: {r.source}")
180
+ if r.metadata:
181
+ if r.metadata.get("tags"):
182
+ lines.append(f" Tags: {r.metadata['tags']}")
183
+
184
+ return "\n".join(lines)
185
+
186
+ def promote(
187
+ self,
188
+ project: str,
189
+ draft_id: str,
190
+ draft_type: str = "session",
191
+ promote_to: str = "decision"
192
+ ) -> str:
193
+ """
194
+ Promove draft para official.
195
+
196
+ Args:
197
+ project: Nome do projeto
198
+ draft_id: ID do draft
199
+ draft_type: Tipo do draft
200
+ promote_to: Tipo de promoção
201
+
202
+ Returns:
203
+ Mensagem de resultado
204
+ """
205
+ if draft_type == "session":
206
+ result = self.promoter.promote_session(project, draft_id, promote_to)
207
+ elif draft_type == "feature":
208
+ result = self.promoter.promote_feature(project, draft_id, promote_to)
209
+ else:
210
+ return f"Tipo de draft desconhecido: {draft_type}"
211
+
212
+ if result is None:
213
+ return "Draft não encontrado ou não pôde ser promovido."
214
+
215
+ if result.success:
216
+ # Marca como promovido
217
+ self.promoter.mark_promoted(project, draft_id, draft_type, result)
218
+
219
+ # Registra evento
220
+ from src.core.event_schema import Event, EventType, EventOrigin
221
+ promotion_event = Event(
222
+ project=project,
223
+ origin=EventOrigin.USER,
224
+ event_type=EventType.PROMOTION_PERFORMED,
225
+ subtype="manual",
226
+ payload={
227
+ "draft_id": draft_id,
228
+ "draft_type": draft_type,
229
+ "target_type": result.target_type,
230
+ "target_path": result.target_path
231
+ }
232
+ )
233
+ self.raw_storage.append(promotion_event)
234
+
235
+ return f"Promovido para: {result.target_path}"
236
+ else:
237
+ return f"Promoção falhou: {result.metadata.get('reason', 'desconhecido')}"
238
+
239
+ def gc(self, project: Optional[str] = None, dry_run: bool = True) -> str:
240
+ """
241
+ Garbage collection manual.
242
+
243
+ Args:
244
+ project: Nome do projeto (None para todos)
245
+ dry_run: Apenas simular
246
+
247
+ Returns:
248
+ Relatório de GC
249
+ """
250
+ from src.forgetting.guard_rails import GuardRails
251
+ from src.forgetting.gc import GarbageCollector
252
+
253
+ guard_rails = GuardRails(self.cerebro_path / "config" / "cerebro.yaml")
254
+ gc = GarbageCollector(self.cerebro_path / "config")
255
+
256
+ # Lista memórias do índice
257
+ memories = self.metadata_db.search(project)
258
+
259
+ # Encontra candidatos para archive
260
+ archive_candidates = gc.find_candidates_for_archive(
261
+ memories,
262
+ guard_rails.get_archive_threshold("raw")
263
+ )
264
+
265
+ # Filtra por guard rails
266
+ delete_candidates = [
267
+ m for m in archive_candidates
268
+ if guard_rails.can_delete(m)
269
+ ]
270
+
271
+ lines = ["Relatório de Garbage Collection:\n"]
272
+ lines.append(f"Total de memórias: {len(memories)}")
273
+ lines.append(f"Candidatas para archive: {len(archive_candidates)}")
274
+ lines.append(f"Candidatas para delete: {len(delete_candidates)}")
275
+
276
+ if dry_run:
277
+ lines.append("\n[DRY RUN] Nenhuma ação foi tomada.\n")
278
+
279
+ if delete_candidates:
280
+ lines.append("\nCandidatas para delete:")
281
+ for m in delete_candidates[:10]:
282
+ lines.append(f" - [{m['type']}] {m.get('title', m['id'])} (score: {m.get('total_score', 0):.3f})")
283
+
284
+ return "\n".join(lines)
285
+
286
+ def status(self) -> str:
287
+ """
288
+ Status do sistema.
289
+
290
+ Returns:
291
+ Relatório de status
292
+ """
293
+ lines = ["Status do Cerebro:\n"]
294
+
295
+ # Session atual
296
+ session_id = self.session_manager.get_session_id()
297
+ lines.append(f"Session ID: {session_id}")
298
+
299
+ # Stats do raw
300
+ lines.append(f"\nRaw storage: {self.cerebro_path / 'raw'}")
301
+
302
+ # Stats do working
303
+ lines.append(f"Working storage: {self.cerebro_path / 'working'}")
304
+
305
+ # Stats do official
306
+ lines.append(f"Official storage: {self.cerebro_path / 'official'}")
307
+
308
+ # Stats do índice
309
+ try:
310
+ stats = self.metadata_db.search()
311
+ lines.append(f"\nÍndice: {len(stats)} memórias")
312
+ except Exception:
313
+ lines.append("\nÍndice: não disponível")
314
+
315
+ return "\n".join(lines)
316
+
317
+ def diff(
318
+ self,
319
+ project: str,
320
+ period_days: Optional[int] = None,
321
+ start_date: Optional[str] = None,
322
+ end_date: Optional[str] = None,
323
+ gc_threshold: float = 0.3,
324
+ output: Optional[Path] = None,
325
+ format: str = "markdown"
326
+ ) -> str:
327
+ """
328
+ Analisa diferenças de memória entre dois pontos no tempo.
329
+
330
+ Args:
331
+ project: Nome do projeto
332
+ period_days: Dias do período (ex: 7, 30)
333
+ start_date: Data de início explícita (ISO string)
334
+ end_date: Data de fim explícita (ISO string)
335
+ gc_threshold: Threshold para garbage collection risk
336
+ output: Arquivo de saída (opcional)
337
+ format: Formato de saída (markdown, json)
338
+
339
+ Returns:
340
+ Relatório de Memory Diff
341
+ """
342
+ result = self.memory_diff.analyze(
343
+ project=project,
344
+ period_days=period_days,
345
+ start_date=start_date,
346
+ end_date=end_date,
347
+ gc_threshold=gc_threshold
348
+ )
349
+
350
+ report = self.memory_diff.generate_report(result, format=format)
351
+
352
+ if output:
353
+ output.write_text(report, encoding="utf-8")
354
+ return f"Memory Diff report gerado em: {output}"
355
+
356
+ return report
357
+
358
+ def dream(self, since_days: int = 7, dry_run: bool = True) -> str:
359
+ """
360
+ Extração automática de memórias.
361
+
362
+ Args:
363
+ since_days: Dias para analisar
364
+ dry_run: Se True, apenas simula
365
+
366
+ Returns:
367
+ Relatório da extração
368
+ """
369
+ from src.core.paths import get_auto_mem_path
370
+ from src.consolidation.dream import run_dream, generate_dream_report
371
+
372
+ memory_dir = get_auto_mem_path()
373
+ result = run_dream(memory_dir=memory_dir, since_days=since_days, dry_run=dry_run)
374
+ return generate_dream_report(result)
375
+
376
+ def remember(self, dry_run: bool = True) -> str:
377
+ """
378
+ Revisão e promoção de memórias.
379
+
380
+ Args:
381
+ dry_run: Se True, apenas gera relatório
382
+
383
+ Returns:
384
+ Relatório do remember
385
+ """
386
+ from src.consolidation.remember import run_remember, generate_remember_report
387
+
388
+ report = run_remember(dry_run=dry_run)
389
+ return generate_remember_report(report)
390
+
391
+ def gc_cmd(self, threshold_days: int = 7, dry_run: bool = True) -> str:
392
+ """
393
+ Garbage collection de memórias.
394
+
395
+ Args:
396
+ threshold_days: Dias para considerar memória stale
397
+ dry_run: Se True, apenas lista candidatas
398
+
399
+ Returns:
400
+ Relatório do GC
401
+ """
402
+ from src.core.paths import get_auto_mem_path
403
+ from src.forgetting.gc import GarbageCollector
404
+
405
+ memory_dir = get_auto_mem_path()
406
+ gc = GarbageCollector(memory_dir)
407
+ results = gc.run_gc(
408
+ memory_dir=memory_dir,
409
+ archive_threshold_days=threshold_days,
410
+ deletion_threshold_days=threshold_days * 4,
411
+ dry_run=dry_run
412
+ )
413
+ return gc.generate_gc_report(results)
414
+
415
+
416
+ def main():
417
+ """Entry point da CLI"""
418
+ parser = argparse.ArgumentParser(
419
+ prog="ocerebro",
420
+ description="OCerebro - Sistema de Memoria para Agentes (Claude Code/MCP)"
421
+ )
422
+
423
+ parser.add_argument(
424
+ "--cerebro-path",
425
+ type=Path,
426
+ default=Path(".ocerebro"),
427
+ help="Diretório base do OCerebro"
428
+ )
429
+
430
+ subparsers = parser.add_subparsers(dest="command", help="Comandos")
431
+
432
+ # Comando: checkpoint
433
+ checkpoint_parser = subparsers.add_parser("checkpoint", help="Trigger manual de checkpoint")
434
+ checkpoint_parser.add_argument("project", help="Nome do projeto")
435
+ checkpoint_parser.add_argument("--session", help="ID da sessão")
436
+ checkpoint_parser.add_argument("--reason", default="manual", help="Motivo do checkpoint")
437
+
438
+ # Comando: memory
439
+ memory_parser = subparsers.add_parser("memory", help="Visualizar memória ativa")
440
+ memory_parser.add_argument("project", help="Nome do projeto")
441
+ memory_parser.add_argument("--output", type=Path, help="Arquivo de saída")
442
+
443
+ # Comando: search
444
+ search_parser = subparsers.add_parser("search", help="Buscar memórias")
445
+ search_parser.add_argument("query", help="Texto de busca")
446
+ search_parser.add_argument("--project", help="Filtrar por projeto")
447
+ search_parser.add_argument("--type", dest="mem_type", help="Filtrar por tipo")
448
+ search_parser.add_argument("--limit", type=int, default=10, help="Limite de resultados")
449
+ search_parser.add_argument("--no-semantic", action="store_true", help="Desativar busca semântica")
450
+
451
+ # Comando: promote
452
+ promote_parser = subparsers.add_parser("promote", help="Promover draft para official")
453
+ promote_parser.add_argument("project", help="Nome do projeto")
454
+ promote_parser.add_argument("draft_id", help="ID do draft")
455
+ promote_parser.add_argument("--type", default="session", help="Tipo do draft")
456
+ promote_parser.add_argument("--to", dest="promote_to", default="decision", help="Tipo de promoção")
457
+
458
+ # Comando: setup
459
+ setup_parser = subparsers.add_parser("setup", help="Configurar MCP Server e projeto automaticamente")
460
+ setup_parser.add_argument("subcommand", nargs="?", choices=["claude", "hooks", "init"], default="all",
461
+ help="O que configurar: claude (MCP), hooks (hooks.yaml), init (tudo)")
462
+ setup_parser.add_argument("--project", type=Path, help="Diretório do projeto (padrão: atual)")
463
+
464
+ # Comando: status
465
+ subparsers.add_parser("status", help="Status do sistema")
466
+
467
+ # Comando: diff
468
+ diff_parser = subparsers.add_parser("diff", help="Análise diferencial de memória entre dois pontos no tempo")
469
+ diff_parser.add_argument("project", help="Nome do projeto")
470
+ diff_parser.add_argument("--period", type=int, default=7, help="Dias do período (padrão: 7)")
471
+ diff_parser.add_argument("--start", dest="start_date", help="Data de início (ISO format, ex: 2026-03-01)")
472
+ diff_parser.add_argument("--end", dest="end_date", help="Data de fim (ISO format, ex: 2026-03-31)")
473
+ diff_parser.add_argument("--gc-threshold", type=float, default=0.3, help="Threshold para GC risk (padrão: 0.3)")
474
+ diff_parser.add_argument("--output", type=Path, help="Arquivo de saída")
475
+ diff_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Formato de saída")
476
+
477
+ # Comando: dream
478
+ dream_parser = subparsers.add_parser("dream", help="Extração automática de memórias")
479
+ dream_parser.add_argument("--since", type=int, default=7, dest="since_days", help="Dias para analisar (padrão: 7)")
480
+ dream_parser.add_argument("--apply", action="store_true", dest="apply", help="Aplicar extração (padrão: dry-run)")
481
+
482
+ # Comando: remember
483
+ remember_parser = subparsers.add_parser("remember", help="Revisão e promoção de memórias")
484
+ remember_parser.add_argument("--apply", action="store_true", dest="apply", help="Aplicar promoções (padrão: dry-run)")
485
+
486
+ # Comando: gc
487
+ gc_parser = subparsers.add_parser("gc", help="Garbage collection de memórias")
488
+ gc_parser.add_argument("--threshold", type=int, default=7, dest="threshold_days", help="Dias para considerar memória stale (padrão: 7)")
489
+ gc_parser.add_argument("--apply", action="store_true", dest="apply", help="Aplicar GC (padrão: dry-run)")
490
+
491
+ args = parser.parse_args()
492
+
493
+ if not args.command:
494
+ parser.print_help()
495
+ sys.exit(1)
496
+
497
+ # Comando setup nao precisa de CLI
498
+ if args.command == "setup":
499
+ from cerebro.cerebro_setup import setup_claude_desktop, setup_hooks, setup_cerebro_dir
500
+
501
+ if args.subcommand == "claude":
502
+ success = setup_claude_desktop()
503
+ sys.exit(0 if success else 1)
504
+ elif args.subcommand == "hooks":
505
+ success = setup_hooks(args.project)
506
+ sys.exit(0 if success else 1)
507
+ elif args.subcommand == "init":
508
+ # Pergunta: global ou projeto?
509
+ print("Como quer usar o OCerebro?")
510
+ print(" 1. Neste projeto (cria .ocerebro/ aqui)")
511
+ print(" 2. Global (usa ~/.ocerebro/ para todos os projetos)")
512
+ choice = input("\nEscolha [1/2] (padrão: 1): ").strip() or "1"
513
+
514
+ if choice == "2":
515
+ base_path = Path.home() / ".ocerebro"
516
+ print(f"\n✓ Modo global: {base_path}")
517
+ else:
518
+ base_path = Path.cwd() / ".ocerebro"
519
+ print(f"\n✓ Modo projeto: {base_path}")
520
+
521
+ # Salva a escolha num arquivo de config global
522
+ config_file = Path.home() / ".ocerebro_config"
523
+ config_file.parent.mkdir(parents=True, exist_ok=True)
524
+ config_file.write_text(f"base_path={base_path}\n", encoding="utf-8")
525
+ print(f"✓ Configuração salva em {config_file}")
526
+
527
+ setup_cerebro_dir(base_path)
528
+ setup_hooks(base_path)
529
+ print("\nSetup completo! Agora execute:")
530
+ print(" cerebro setup claude")
531
+ sys.exit(0)
532
+ else:
533
+ # Setup completo
534
+ setup_cerebro_dir(args.project)
535
+ setup_hooks(args.project)
536
+ setup_claude_desktop()
537
+ sys.exit(0)
538
+
539
+ # Inicializa CLI
540
+ cli = CerebroCLI(args.cerebro_path)
541
+
542
+ # Executa comando
543
+ if args.command == "checkpoint":
544
+ result = cli.checkpoint(args.project, args.session, args.reason)
545
+ elif args.command == "memory":
546
+ result = cli.memory(args.project, args.output)
547
+ elif args.command == "search":
548
+ result = cli.search(
549
+ args.query,
550
+ args.project,
551
+ args.mem_type,
552
+ args.limit,
553
+ not args.no_semantic
554
+ )
555
+ elif args.command == "promote":
556
+ result = cli.promote(args.project, args.draft_id, args.type, args.promote_to)
557
+ elif args.command == "status":
558
+ result = cli.status()
559
+ elif args.command == "diff":
560
+ result = cli.diff(
561
+ args.project,
562
+ period_days=args.period if not args.start_date else None,
563
+ start_date=args.start_date,
564
+ end_date=args.end_date,
565
+ gc_threshold=args.gc_threshold,
566
+ output=args.output,
567
+ format=args.format
568
+ )
569
+ elif args.command == "dream":
570
+ result = cli.dream(since_days=args.since_days, dry_run=not args.apply)
571
+ elif args.command == "remember":
572
+ result = cli.remember(dry_run=not args.apply)
573
+ elif args.command == "gc":
574
+ result = cli.gc_cmd(threshold_days=args.threshold_days, dry_run=not args.apply)
575
+ else:
576
+ parser.print_help()
577
+ sys.exit(1)
578
+
579
+ print(result)
580
+
581
+
582
+ if __name__ == "__main__":
583
+ main()
@@ -0,0 +1,74 @@
1
+ """Comando cerebro remember - Revisão e promoção de memórias.
2
+
3
+ Replica o comando /remember do Claude Code (bloqueado por USER_TYPE === 'ant').
4
+
5
+ Uso:
6
+ cerebro remember # Revisão (dry-run)
7
+ cerebro remember --apply # Aplica promoções automaticamente
8
+ """
9
+
10
+ import argparse
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ from src.consolidation.remember import run_remember, generate_remember_report
15
+
16
+
17
+ def cmd_remember(
18
+ project_root: Optional[Path] = None,
19
+ dry_run: bool = True
20
+ ) -> str:
21
+ """
22
+ Executa fluxo de revisão e promoção de memórias (remember).
23
+
24
+ Args:
25
+ project_root: Raiz do projeto (default: git root)
26
+ dry_run: Se True, apenas gera relatório, não aplica
27
+
28
+ Returns:
29
+ Relatório do remember
30
+ """
31
+ # Executa remember
32
+ report = run_remember(
33
+ project_root=project_root,
34
+ dry_run=dry_run
35
+ )
36
+
37
+ # Gera relatório
38
+ return generate_remember_report(report)
39
+
40
+
41
+ def main():
42
+ """Entry point do comando remember"""
43
+ parser = argparse.ArgumentParser(
44
+ prog="cerebro remember",
45
+ description="Revisão e promoção de memórias (replica /remember do Claude Code)"
46
+ )
47
+
48
+ parser.add_argument(
49
+ "--apply",
50
+ action="store_true",
51
+ dest="apply",
52
+ help="Aplicar promoções (padrão: dry-run)"
53
+ )
54
+
55
+ parser.add_argument(
56
+ "--project",
57
+ type=Path,
58
+ default=None,
59
+ dest="project_root",
60
+ help="Raiz do projeto (padrão: git root)"
61
+ )
62
+
63
+ args = parser.parse_args()
64
+
65
+ result = cmd_remember(
66
+ project_root=args.project_root,
67
+ dry_run=not args.apply
68
+ )
69
+
70
+ print(result)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -0,0 +1,8 @@
1
+ """Consolidação do Cerebro: extração, scoring, promoção"""
2
+ from .checkpoints import CheckpointManager, CheckpointTrigger
3
+ from .scorer import Scorer, ScoringConfig
4
+ from .extractor import Extractor, ExtractionResult
5
+ from .promoter import Promoter, PromotionResult
6
+
7
+
8
+ __all__ = ["CheckpointManager", "CheckpointTrigger", "Extractor", "ExtractionResult", "Scorer", "ScoringConfig", "Promoter", "PromotionResult"]