ocerebro 0.4.16 → 0.4.17
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/cli/main.py +14 -0
- package/src/core/paths.py +13 -12
- package/src/mcp/server.py +75 -20
- package/src/working/memory_view.py +25 -1
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli/main.py
CHANGED
|
@@ -333,6 +333,20 @@ def _run_init(project_path: Optional[Path] = None):
|
|
|
333
333
|
print()
|
|
334
334
|
setup_claude(auto=True)
|
|
335
335
|
|
|
336
|
+
# FIX 1: Verifica e confirma que hook Stop foi registrado
|
|
337
|
+
print("\n" + "=" * 60)
|
|
338
|
+
print("HOOK AUTOMÁTICO DE FIM DE SESSÃO")
|
|
339
|
+
print("=" * 60)
|
|
340
|
+
print("\nO OCerebro registrou um hook que executa automaticamente")
|
|
341
|
+
print("o comando 'ocerebro dream' toda vez que você encerra uma sessão.")
|
|
342
|
+
print("\nIsso garante que:")
|
|
343
|
+
print(" ✅ Suas memórias são extraídas automaticamente")
|
|
344
|
+
print(" ✅ Zero esforço — use o Claude Code normalmente")
|
|
345
|
+
print(" ✅ Próxima sessão já tem todo o contexto")
|
|
346
|
+
print("\nHook registrado em ~/.claude/settings.json:")
|
|
347
|
+
print(' hooks.Stop → "ocerebro dream --since 1 --apply"')
|
|
348
|
+
print("\nPara desativar: edite settings.json e remova o hook 'Stop'")
|
|
349
|
+
|
|
336
350
|
|
|
337
351
|
def _install_semantic_deps():
|
|
338
352
|
"""Instala dependências de busca semântica (sentence-transformers + spacy)"""
|
package/src/core/paths.py
CHANGED
|
@@ -23,7 +23,7 @@ def sanitize_path(absolute_path: str) -> str:
|
|
|
23
23
|
|
|
24
24
|
Exemplo:
|
|
25
25
|
/home/user/projects/ocerebro → -home-user-projects-ocerebro
|
|
26
|
-
C:\\Users\\dev\\my-project →
|
|
26
|
+
C:\\Users\\dev\\my-project → -c--users-dev-my-project
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
29
|
absolute_path: Path absoluto para sanitizar
|
|
@@ -31,26 +31,27 @@ def sanitize_path(absolute_path: str) -> str:
|
|
|
31
31
|
Returns:
|
|
32
32
|
String sanitizada para uso como nome de diretório
|
|
33
33
|
"""
|
|
34
|
+
import re
|
|
35
|
+
|
|
34
36
|
# Normaliza separadores Windows para Unix
|
|
35
37
|
normalized = absolute_path.replace("\\", "/")
|
|
36
38
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
+
# Normaliza drive letter para lowercase (E:/ → e:/)
|
|
40
|
+
if len(normalized) >= 2 and normalized[1] == ":":
|
|
41
|
+
normalized = normalized[0].lower() + normalized[1:]
|
|
42
|
+
|
|
43
|
+
# Substitui / e : por -
|
|
44
|
+
sanitized = re.sub(r'[/\\:]', '-', normalized)
|
|
39
45
|
|
|
40
|
-
# Remove caracteres especiais
|
|
46
|
+
# Remove caracteres especiais
|
|
41
47
|
sanitized = re.sub(r'[^a-zA-Z0-9_-]', '', sanitized)
|
|
42
48
|
|
|
43
49
|
# Remove múltiplos '-' consecutivos
|
|
44
50
|
sanitized = re.sub(r'-+', '-', sanitized)
|
|
45
51
|
|
|
46
|
-
# Garante
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
sanitized = '-' + sanitized
|
|
50
|
-
elif len(absolute_path) >= 2 and absolute_path[1] == ':':
|
|
51
|
-
# Windows path (ex: C:\)
|
|
52
|
-
if not sanitized.startswith('-'):
|
|
53
|
-
sanitized = '-' + sanitized
|
|
52
|
+
# Garante início com -
|
|
53
|
+
if not sanitized.startswith('-'):
|
|
54
|
+
sanitized = '-' + sanitized
|
|
54
55
|
|
|
55
56
|
return sanitized
|
|
56
57
|
|
package/src/mcp/server.py
CHANGED
|
@@ -439,12 +439,38 @@ class CerebroMCP:
|
|
|
439
439
|
return [TextContent(type="text", text=f"Erro: {str(e)}")]
|
|
440
440
|
|
|
441
441
|
def _memory(self, args: Dict[str, Any]) -> str:
|
|
442
|
-
"""Gera memória ativa"""
|
|
442
|
+
"""Gera memória ativa e escreve no diretório nativo do Claude Code para auto-load."""
|
|
443
443
|
project = args.get("project")
|
|
444
444
|
if not project:
|
|
445
445
|
return "Erro: project é obrigatório"
|
|
446
446
|
|
|
447
|
-
|
|
447
|
+
content = self.memory_view.generate(project)
|
|
448
|
+
|
|
449
|
+
# FIX 4: Escreve MEMORY.md no diretório nativo do Claude Code
|
|
450
|
+
# Assim o Claude Code carrega automaticamente na próxima sessão
|
|
451
|
+
try:
|
|
452
|
+
from src.core.paths import get_auto_mem_path, get_memory_index
|
|
453
|
+
auto_mem_dir = get_auto_mem_path()
|
|
454
|
+
auto_mem_dir.mkdir(parents=True, exist_ok=True)
|
|
455
|
+
index_path = get_memory_index(auto_mem_dir)
|
|
456
|
+
|
|
457
|
+
# Gera conteúdo compatível com o formato que Claude Code espera
|
|
458
|
+
# Formato: # <title>\n\n- [type] filename (date): description
|
|
459
|
+
claude_format_lines = ["# OCerebro - Memória Ativa", ""]
|
|
460
|
+
claude_format_lines.append(f"## {project}")
|
|
461
|
+
claude_format_lines.append("")
|
|
462
|
+
|
|
463
|
+
# Parse do conteúdo gerado para extrair itens
|
|
464
|
+
for line in content.splitlines():
|
|
465
|
+
if line.startswith("- ["):
|
|
466
|
+
claude_format_lines.append(line)
|
|
467
|
+
|
|
468
|
+
claude_content = "\n".join(claude_format_lines)
|
|
469
|
+
index_path.write_text(claude_content, encoding="utf-8")
|
|
470
|
+
except Exception:
|
|
471
|
+
pass # Falha silenciosa - não bloqueia o retorno
|
|
472
|
+
|
|
473
|
+
return content
|
|
448
474
|
|
|
449
475
|
def _search(self, args: Dict[str, Any]) -> str:
|
|
450
476
|
"""Busca memórias"""
|
|
@@ -712,11 +738,11 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
712
738
|
"""
|
|
713
739
|
|
|
714
740
|
def _capture_memory(self, args: Dict[str, Any]) -> str:
|
|
715
|
-
"""Salva uma memória no diretório nativo do Claude Code."""
|
|
741
|
+
"""Salva uma memória no diretório nativo do Claude Code e no OCerebro (dual-write)."""
|
|
716
742
|
import re
|
|
717
743
|
import yaml
|
|
718
744
|
from datetime import datetime
|
|
719
|
-
from src.core.paths import get_memory_index
|
|
745
|
+
from src.core.paths import get_memory_index, get_auto_mem_path
|
|
720
746
|
|
|
721
747
|
content = args.get("memory_content", "")
|
|
722
748
|
if not content:
|
|
@@ -727,11 +753,6 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
727
753
|
return "Erro: frontmatter 'name' é obrigatório no memory_content"
|
|
728
754
|
|
|
729
755
|
mem_name = name_match.group(1).strip().lower().replace(' ', '-')
|
|
730
|
-
mem_dir = get_auto_mem_path()
|
|
731
|
-
mem_dir.mkdir(parents=True, exist_ok=True)
|
|
732
|
-
|
|
733
|
-
file_path = mem_dir / f"{mem_name}.md"
|
|
734
|
-
file_path.write_text(content, encoding="utf-8")
|
|
735
756
|
|
|
736
757
|
# Parse frontmatter uma única vez com yaml.safe_load
|
|
737
758
|
frontmatter_match = re.match(r'^---\n(.*?)\n---\n(.*)$', content, re.DOTALL)
|
|
@@ -767,18 +788,52 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
767
788
|
ts = datetime.now().strftime("%Y-%m-%d")
|
|
768
789
|
entry = f"- [{m_type}] {mem_name}.md ({ts}): {desc}\n"
|
|
769
790
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
791
|
+
# =========================================================================
|
|
792
|
+
# DUAL-WRITE: Salva em ambos os diretórios
|
|
793
|
+
# =========================================================================
|
|
794
|
+
|
|
795
|
+
# 1. Diretório nativo do Claude Code (~/.claude/projects/<slug>/memory/)
|
|
796
|
+
# → Claude Code carrega automaticamente na próxima sessão
|
|
797
|
+
claude_mem_dir = get_auto_mem_path()
|
|
798
|
+
claude_mem_dir.mkdir(parents=True, exist_ok=True)
|
|
799
|
+
claude_file_path = claude_mem_dir / f"{mem_name}.md"
|
|
800
|
+
claude_file_path.write_text(content, encoding="utf-8")
|
|
801
|
+
|
|
802
|
+
# Atualiza MEMORY.md nativo
|
|
803
|
+
claude_index_path = get_memory_index(claude_mem_dir)
|
|
804
|
+
if claude_index_path.exists():
|
|
805
|
+
existing = claude_index_path.read_text(encoding="utf-8")
|
|
773
806
|
if mem_name not in existing:
|
|
774
|
-
with open(
|
|
807
|
+
with open(claude_index_path, "a", encoding="utf-8") as f:
|
|
775
808
|
f.write(entry)
|
|
776
809
|
else:
|
|
777
|
-
|
|
810
|
+
claude_index_path.write_text(f"# Memórias do Projeto\n\n{entry}", encoding="utf-8")
|
|
811
|
+
|
|
812
|
+
# 2. Diretório OCerebro (.ocerebro/official/<subdir>/)
|
|
813
|
+
# → OCerebro indexa e busca via cerebro_memory/cerebro_search
|
|
814
|
+
# Mapeamento: Claude Code type → OCerebro subdir
|
|
815
|
+
type_to_subdir = {
|
|
816
|
+
"user": "decisions", # user → decisions (global)
|
|
817
|
+
"feedback": "preferences", # feedback → preferences
|
|
818
|
+
"project": "decisions", # project → decisions
|
|
819
|
+
"reference": "state", # reference → state
|
|
820
|
+
}
|
|
821
|
+
subdir = type_to_subdir.get(m_type, "decisions") # default: decisions
|
|
822
|
+
|
|
823
|
+
# Para tipo "user", salva em global/; para outros, usa project do frontmatter
|
|
824
|
+
if m_type == "user":
|
|
825
|
+
cerebro_project = "global"
|
|
826
|
+
else:
|
|
827
|
+
cerebro_project = project if project != "unknown" else "default"
|
|
828
|
+
|
|
829
|
+
cerebro_dir = self.cerebro_path / "official" / cerebro_project / subdir
|
|
830
|
+
cerebro_dir.mkdir(parents=True, exist_ok=True)
|
|
831
|
+
cerebro_file_path = cerebro_dir / f"{mem_name}.md"
|
|
832
|
+
cerebro_file_path.write_text(content, encoding="utf-8")
|
|
778
833
|
|
|
834
|
+
# =========================================================================
|
|
779
835
|
# Registrar entidades no grafo (frontmatter + conteúdo)
|
|
780
|
-
#
|
|
781
|
-
# extract_from_content() deleta apenas entidades 'content', preservando 'frontmatter'
|
|
836
|
+
# =========================================================================
|
|
782
837
|
if self.entities_db and frontmatter_match:
|
|
783
838
|
try:
|
|
784
839
|
# 1. Extrai entidades do conteúdo (spaCy NER)
|
|
@@ -791,7 +846,7 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
791
846
|
self.entities_db.extract_from_frontmatter(
|
|
792
847
|
memory_id=mem_name,
|
|
793
848
|
frontmatter=frontmatter,
|
|
794
|
-
project=
|
|
849
|
+
project=cerebro_project
|
|
795
850
|
)
|
|
796
851
|
except Exception:
|
|
797
852
|
pass # Falha silenciosa se frontmatter inválido
|
|
@@ -802,17 +857,17 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
|
|
|
802
857
|
self.metadata_db.insert({
|
|
803
858
|
"id": mem_name,
|
|
804
859
|
"type": m_type,
|
|
805
|
-
"project":
|
|
860
|
+
"project": cerebro_project,
|
|
806
861
|
"title": frontmatter.get("title", mem_name) if frontmatter else mem_name,
|
|
807
862
|
"content": body_content,
|
|
808
863
|
"tags": tags_str,
|
|
809
864
|
"created_at": datetime.now().isoformat(),
|
|
810
865
|
"updated_at": datetime.now().isoformat(),
|
|
811
866
|
"layer": "auto",
|
|
812
|
-
"path": str(
|
|
867
|
+
"path": str(cerebro_file_path),
|
|
813
868
|
})
|
|
814
869
|
|
|
815
|
-
return f"✅ Memória '{mem_name}' salva
|
|
870
|
+
return f"✅ Memória '{mem_name}' salva (dual-write: Claude + OCerebro)"
|
|
816
871
|
|
|
817
872
|
def _remember(self, args: Dict[str, Any]) -> str:
|
|
818
873
|
"""Revisão e promoção de memórias"""
|
|
@@ -136,7 +136,7 @@ class MemoryView:
|
|
|
136
136
|
|
|
137
137
|
def write_to_file(self, project: str) -> Path:
|
|
138
138
|
"""
|
|
139
|
-
Gera e escreve MEMORY.md no arquivo.
|
|
139
|
+
Gera e escreve MEMORY.md no arquivo (dual-write: OCerebro + Claude Code nativo).
|
|
140
140
|
|
|
141
141
|
Args:
|
|
142
142
|
project: Nome do projeto
|
|
@@ -145,6 +145,30 @@ class MemoryView:
|
|
|
145
145
|
Path do arquivo MEMORY.md criado
|
|
146
146
|
"""
|
|
147
147
|
content = self.generate(project)
|
|
148
|
+
|
|
149
|
+
# 1. Escreve em .ocerebro/MEMORY.md
|
|
148
150
|
memory_file = self.cerebro_path / "MEMORY.md"
|
|
149
151
|
memory_file.write_text(content, encoding="utf-8")
|
|
152
|
+
|
|
153
|
+
# 2. Escreve em ~/.claude/projects/<slug>/memory/MEMORY.md (Claude Code nativo)
|
|
154
|
+
try:
|
|
155
|
+
from src.core.paths import get_auto_mem_path, get_memory_index
|
|
156
|
+
auto_mem_dir = get_auto_mem_path()
|
|
157
|
+
auto_mem_dir.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
index_path = get_memory_index(auto_mem_dir)
|
|
159
|
+
|
|
160
|
+
# Gera conteúdo compatível com formato Claude Code
|
|
161
|
+
claude_format_lines = ["# OCerebro - Memória Ativa", ""]
|
|
162
|
+
claude_format_lines.append(f"## {project}")
|
|
163
|
+
claude_format_lines.append("")
|
|
164
|
+
|
|
165
|
+
for line in content.splitlines():
|
|
166
|
+
if line.startswith("- ["):
|
|
167
|
+
claude_format_lines.append(line)
|
|
168
|
+
|
|
169
|
+
claude_content = "\n".join(claude_format_lines)
|
|
170
|
+
index_path.write_text(claude_content, encoding="utf-8")
|
|
171
|
+
except Exception:
|
|
172
|
+
pass # Falha silenciosa
|
|
173
|
+
|
|
150
174
|
return memory_file
|