ocerebro 0.1.2 → 0.1.3

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.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OCerebro - Sistema de Memoria para Agentes (Claude Code/MCP)",
5
5
  "main": "bin/ocerebro.js",
6
6
  "bin": {
@@ -46,4 +46,4 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  }
49
- }
49
+ }
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.1"
7
+ version = "0.1.3"
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
@@ -38,35 +38,24 @@ class CerebroCLI:
38
38
  """
39
39
 
40
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
41
  self.cerebro_path = cerebro_path
48
42
  self.cerebro_path.mkdir(parents=True, exist_ok=True)
49
43
 
50
- # Inicializa storages
51
44
  self.raw_storage = JSONLStorage(cerebro_path / "raw")
52
45
  self.working_storage = YAMLStorage(cerebro_path / "working")
53
46
  self.official_storage = MarkdownStorage(cerebro_path / "official")
54
47
  self.session_manager = SessionManager(cerebro_path)
55
48
 
56
- # Inicializa índice
57
49
  self.metadata_db = MetadataDB(cerebro_path / "index" / "metadata.db")
58
50
  self.embeddings_db = EmbeddingsDB(cerebro_path / "index" / "embeddings.db")
59
51
  self.query_engine = QueryEngine(self.metadata_db, self.embeddings_db)
60
52
 
61
- # Inicializa consolidação
62
53
  self.checkpoint_manager = CheckpointManager(cerebro_path / "config")
63
54
  self.extractor = Extractor(self.raw_storage, self.working_storage)
64
55
  self.promoter = Promoter(self.working_storage, self.official_storage)
65
56
 
66
- # Inicializa memory view
67
57
  self.memory_view = MemoryView(cerebro_path, self.official_storage, self.working_storage)
68
58
 
69
- # Inicializa memory diff
70
59
  self.memory_diff = MemoryDiff(
71
60
  self.official_storage,
72
61
  self.working_storage,
@@ -74,21 +63,9 @@ class CerebroCLI:
74
63
  )
75
64
 
76
65
  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
66
  if session_id is None:
89
67
  session_id = self.session_manager.get_session_id()
90
68
 
91
- # Extrai sessão
92
69
  try:
93
70
  result = self.extractor.extract_session(project, session_id)
94
71
  except Exception as e:
@@ -97,14 +74,10 @@ class CerebroCLI:
97
74
  if not result.events:
98
75
  return f"Nenhum evento encontrado para sessão {session_id}"
99
76
 
100
- # Cria draft
101
77
  draft = self.extractor.create_draft(result, "session")
102
78
  draft["checkpoint_reason"] = reason
103
-
104
- # Escreve draft
105
79
  draft_name = self.extractor.write_draft(project, draft, "session")
106
80
 
107
- # Registra evento de checkpoint
108
81
  from src.core.event_schema import Event, EventType, EventOrigin
109
82
  checkpoint_event = Event(
110
83
  project=project,
@@ -119,26 +92,13 @@ class CerebroCLI:
119
92
  }
120
93
  )
121
94
  self.raw_storage.append(checkpoint_event)
122
-
123
95
  return f"Checkpoint criado: {draft_name} ({len(result.events)} eventos)"
124
96
 
125
97
  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
98
  content = self.memory_view.generate(project)
137
-
138
99
  if output:
139
100
  output.write_text(content, encoding="utf-8")
140
101
  return f"MEMORY.md gerado em: {output}"
141
-
142
102
  return content
143
103
 
144
104
  def search(
@@ -149,19 +109,6 @@ class CerebroCLI:
149
109
  limit: int = 10,
150
110
  use_semantic: bool = True
151
111
  ) -> 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
112
  results = self.query_engine.search(
166
113
  query=query,
167
114
  project=project,
@@ -180,7 +127,6 @@ class CerebroCLI:
180
127
  if r.metadata:
181
128
  if r.metadata.get("tags"):
182
129
  lines.append(f" Tags: {r.metadata['tags']}")
183
-
184
130
  return "\n".join(lines)
185
131
 
186
132
  def promote(
@@ -190,18 +136,6 @@ class CerebroCLI:
190
136
  draft_type: str = "session",
191
137
  promote_to: str = "decision"
192
138
  ) -> 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
139
  if draft_type == "session":
206
140
  result = self.promoter.promote_session(project, draft_id, promote_to)
207
141
  elif draft_type == "feature":
@@ -213,10 +147,8 @@ class CerebroCLI:
213
147
  return "Draft não encontrado ou não pôde ser promovido."
214
148
 
215
149
  if result.success:
216
- # Marca como promovido
217
150
  self.promoter.mark_promoted(project, draft_id, draft_type, result)
218
151
 
219
- # Registra evento
220
152
  from src.core.event_schema import Event, EventType, EventOrigin
221
153
  promotion_event = Event(
222
154
  project=project,
@@ -231,38 +163,21 @@ class CerebroCLI:
231
163
  }
232
164
  )
233
165
  self.raw_storage.append(promotion_event)
234
-
235
166
  return f"Promovido para: {result.target_path}"
236
167
  else:
237
168
  return f"Promoção falhou: {result.metadata.get('reason', 'desconhecido')}"
238
169
 
239
170
  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
171
  from src.forgetting.guard_rails import GuardRails
251
172
  from src.forgetting.gc import GarbageCollector
252
173
 
253
174
  guard_rails = GuardRails(self.cerebro_path / "config" / "cerebro.yaml")
254
175
  gc = GarbageCollector(self.cerebro_path / "config")
255
-
256
- # Lista memórias do índice
257
176
  memories = self.metadata_db.search(project)
258
-
259
- # Encontra candidatos para archive
260
177
  archive_candidates = gc.find_candidates_for_archive(
261
178
  memories,
262
179
  guard_rails.get_archive_threshold("raw")
263
180
  )
264
-
265
- # Filtra por guard rails
266
181
  delete_candidates = [
267
182
  m for m in archive_candidates
268
183
  if guard_rails.can_delete(m)
@@ -284,34 +199,17 @@ class CerebroCLI:
284
199
  return "\n".join(lines)
285
200
 
286
201
  def status(self) -> str:
287
- """
288
- Status do sistema.
289
-
290
- Returns:
291
- Relatório de status
292
- """
293
202
  lines = ["Status do Cerebro:\n"]
294
-
295
- # Session atual
296
203
  session_id = self.session_manager.get_session_id()
297
204
  lines.append(f"Session ID: {session_id}")
298
-
299
- # Stats do raw
300
205
  lines.append(f"\nRaw storage: {self.cerebro_path / 'raw'}")
301
-
302
- # Stats do working
303
206
  lines.append(f"Working storage: {self.cerebro_path / 'working'}")
304
-
305
- # Stats do official
306
207
  lines.append(f"Official storage: {self.cerebro_path / 'official'}")
307
-
308
- # Stats do índice
309
208
  try:
310
209
  stats = self.metadata_db.search()
311
210
  lines.append(f"\nÍndice: {len(stats)} memórias")
312
211
  except Exception:
313
212
  lines.append("\nÍndice: não disponível")
314
-
315
213
  return "\n".join(lines)
316
214
 
317
215
  def diff(
@@ -324,21 +222,6 @@ class CerebroCLI:
324
222
  output: Optional[Path] = None,
325
223
  format: str = "markdown"
326
224
  ) -> 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
225
  result = self.memory_diff.analyze(
343
226
  project=project,
344
227
  period_days=period_days,
@@ -346,26 +229,13 @@ class CerebroCLI:
346
229
  end_date=end_date,
347
230
  gc_threshold=gc_threshold
348
231
  )
349
-
350
232
  report = self.memory_diff.generate_report(result, format=format)
351
-
352
233
  if output:
353
234
  output.write_text(report, encoding="utf-8")
354
235
  return f"Memory Diff report gerado em: {output}"
355
-
356
236
  return report
357
237
 
358
238
  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
239
  from src.core.paths import get_auto_mem_path
370
240
  from src.consolidation.dream import run_dream, generate_dream_report
371
241
 
@@ -374,31 +244,12 @@ class CerebroCLI:
374
244
  return generate_dream_report(result)
375
245
 
376
246
  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
247
  from src.consolidation.remember import run_remember, generate_remember_report
387
248
 
388
249
  report = run_remember(dry_run=dry_run)
389
250
  return generate_remember_report(report)
390
251
 
391
252
  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
253
  from src.core.paths import get_auto_mem_path
403
254
  from src.forgetting.gc import GarbageCollector
404
255
 
@@ -413,6 +264,33 @@ class CerebroCLI:
413
264
  return gc.generate_gc_report(results)
414
265
 
415
266
 
267
+ def _run_init(project_path: Optional[Path] = None):
268
+ """Lógica de init compartilhada entre 'init' e 'setup init'"""
269
+ from cerebro.cerebro_setup import setup_cerebro_dir, setup_hooks
270
+
271
+ print("Como quer usar o OCerebro?")
272
+ print(" 1. Neste projeto (cria .ocerebro/ aqui)")
273
+ print(" 2. Global (usa ~/.ocerebro/ para todos os projetos)")
274
+ choice = input("\nEscolha [1/2] (padrão: 1): ").strip() or "1"
275
+
276
+ if choice == "2":
277
+ base_path = Path.home() / ".ocerebro"
278
+ print(f"\n✓ Modo global: {base_path}")
279
+ else:
280
+ base_path = (project_path or Path.cwd()) / ".ocerebro"
281
+ print(f"\n✓ Modo projeto: {base_path}")
282
+
283
+ config_file = Path.home() / ".ocerebro_config"
284
+ config_file.parent.mkdir(parents=True, exist_ok=True)
285
+ config_file.write_text(f"base_path={base_path}\n", encoding="utf-8")
286
+ print(f"✓ Configuração salva em {config_file}")
287
+
288
+ setup_cerebro_dir(base_path)
289
+ setup_hooks(base_path)
290
+ print("\nSetup completo! Agora execute:")
291
+ print(" ocerebro setup claude")
292
+
293
+
416
294
  def main():
417
295
  """Entry point da CLI"""
418
296
  parser = argparse.ArgumentParser(
@@ -429,6 +307,10 @@ def main():
429
307
 
430
308
  subparsers = parser.add_subparsers(dest="command", help="Comandos")
431
309
 
310
+ # Comando: init (alias direto para setup init)
311
+ init_parser = subparsers.add_parser("init", help="Inicializar OCerebro no projeto atual")
312
+ init_parser.add_argument("--project", type=Path, help="Diretório do projeto (padrão: atual)")
313
+
432
314
  # Comando: checkpoint
433
315
  checkpoint_parser = subparsers.add_parser("checkpoint", help="Trigger manual de checkpoint")
434
316
  checkpoint_parser.add_argument("project", help="Nome do projeto")
@@ -465,28 +347,28 @@ def main():
465
347
  subparsers.add_parser("status", help="Status do sistema")
466
348
 
467
349
  # Comando: diff
468
- diff_parser = subparsers.add_parser("diff", help="Análise diferencial de memória entre dois pontos no tempo")
350
+ diff_parser = subparsers.add_parser("diff", help="Análise diferencial de memória")
469
351
  diff_parser.add_argument("project", help="Nome do projeto")
470
352
  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)")
353
+ diff_parser.add_argument("--start", dest="start_date", help="Data de início (ISO format)")
354
+ diff_parser.add_argument("--end", dest="end_date", help="Data de fim (ISO format)")
355
+ diff_parser.add_argument("--gc-threshold", type=float, default=0.3, help="Threshold para GC risk")
474
356
  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")
357
+ diff_parser.add_argument("--format", choices=["markdown", "json"], default="markdown")
476
358
 
477
359
  # Comando: dream
478
360
  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)")
361
+ dream_parser.add_argument("--since", type=int, default=7, dest="since_days")
362
+ dream_parser.add_argument("--apply", action="store_true", dest="apply")
481
363
 
482
364
  # Comando: remember
483
365
  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)")
366
+ remember_parser.add_argument("--apply", action="store_true", dest="apply")
485
367
 
486
368
  # Comando: gc
487
369
  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)")
370
+ gc_parser.add_argument("--threshold", type=int, default=7, dest="threshold_days")
371
+ gc_parser.add_argument("--apply", action="store_true", dest="apply")
490
372
 
491
373
  args = parser.parse_args()
492
374
 
@@ -494,7 +376,12 @@ def main():
494
376
  parser.print_help()
495
377
  sys.exit(1)
496
378
 
497
- # Comando setup nao precisa de CLI
379
+ # Comando: init (alias direto)
380
+ if args.command == "init":
381
+ _run_init(getattr(args, 'project', None))
382
+ sys.exit(0)
383
+
384
+ # Comando setup
498
385
  if args.command == "setup":
499
386
  from cerebro.cerebro_setup import setup_claude_desktop, setup_hooks, setup_cerebro_dir
500
387
 
@@ -505,32 +392,9 @@ def main():
505
392
  success = setup_hooks(args.project)
506
393
  sys.exit(0 if success else 1)
507
394
  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")
395
+ _run_init(getattr(args, 'project', None))
531
396
  sys.exit(0)
532
397
  else:
533
- # Setup completo
534
398
  setup_cerebro_dir(args.project)
535
399
  setup_hooks(args.project)
536
400
  setup_claude_desktop()
@@ -539,7 +403,6 @@ def main():
539
403
  # Inicializa CLI
540
404
  cli = CerebroCLI(args.cerebro_path)
541
405
 
542
- # Executa comando
543
406
  if args.command == "checkpoint":
544
407
  result = cli.checkpoint(args.project, args.session, args.reason)
545
408
  elif args.command == "memory":