universal-memory 0.1.2__tar.gz → 0.1.4__tar.gz

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 (94) hide show
  1. {universal_memory-0.1.2 → universal_memory-0.1.4}/PKG-INFO +33 -4
  2. {universal_memory-0.1.2 → universal_memory-0.1.4}/README.md +30 -2
  3. {universal_memory-0.1.2 → universal_memory-0.1.4}/pyproject.toml +7 -2
  4. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/__init__.py +1 -1
  5. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/drift_detector.py +3 -2
  6. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/setup_host_use_case.py +45 -33
  7. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/sync_instructions_use_case.py +17 -16
  8. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/get_memory_status_use_case.py +13 -4
  9. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/purge_fact_use_case.py +2 -4
  10. universal_memory-0.1.4/src/universal_memory/application/onboarding/setup_project.py +902 -0
  11. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/rollback_use_case.py +7 -7
  12. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/safe_write_use_case.py +39 -0
  13. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/__init__.py +44 -0
  14. universal_memory-0.1.4/src/universal_memory/application/skills/create_skill.py +363 -0
  15. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/generate_skill.py +14 -9
  16. universal_memory-0.1.4/src/universal_memory/application/skills/import_skill.py +535 -0
  17. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/list_skills.py +210 -11
  18. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/native_skill_sync.py +180 -18
  19. universal_memory-0.1.4/src/universal_memory/application/skills/promote_skill.py +191 -0
  20. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/propose_skill.py +11 -7
  21. universal_memory-0.1.4/src/universal_memory/application/skills/recommend_skills.py +222 -0
  22. universal_memory-0.1.4/src/universal_memory/application/skills/sync_skills.py +302 -0
  23. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/update_skill.py +11 -11
  24. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/update/__init__.py +6 -0
  25. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/update/update_use_cases.py +162 -0
  26. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/bootstrap/cli.py +54 -1
  27. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/bootstrap/mcp.py +48 -0
  28. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/__init__.py +3 -0
  29. universal_memory-0.1.4/src/universal_memory/domain/entities/agent_skill.py +28 -0
  30. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/runtime.py +7 -1
  31. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/__init__.py +2 -0
  32. universal_memory-0.1.4/src/universal_memory/domain/ports/agent_skill_repository.py +25 -0
  33. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/__init__.py +4 -0
  34. universal_memory-0.1.4/src/universal_memory/infrastructure/storage/local_agent_skill_repository.py +256 -0
  35. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_fact_repository.py +1 -7
  36. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_latent_skill_repository.py +1 -7
  37. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_rule_repository.py +1 -2
  38. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/cli/init_command.py +821 -43
  39. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/errors.py +15 -34
  40. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/mcp/server.py +208 -5
  41. universal_memory-0.1.2/src/universal_memory/application/onboarding/setup_project.py +0 -302
  42. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/__main__.py +0 -0
  43. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/__init__.py +0 -0
  44. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/diagnostics/__init__.py +0 -0
  45. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/diagnostics/doctor_use_case.py +0 -0
  46. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/__init__.py +0 -0
  47. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/__init__.py +0 -0
  48. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/assemble_context_summary_use_case.py +0 -0
  49. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/context_hygiene_use_case.py +0 -0
  50. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/list_facts_use_case.py +0 -0
  51. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/remember_fact_use_case.py +0 -0
  52. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/search_facts_use_case.py +0 -0
  53. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/onboarding/__init__.py +0 -0
  54. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/__init__.py +0 -0
  55. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/list_audit_log_use_case.py +0 -0
  56. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/list_snapshots_use_case.py +0 -0
  57. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/track_latent_skill.py +0 -0
  58. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/bootstrap/__init__.py +0 -0
  59. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/__init__.py +0 -0
  60. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/audit_event.py +0 -0
  61. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/base.py +0 -0
  62. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/context_summary.py +0 -0
  63. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/fact.py +0 -0
  64. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/host.py +0 -0
  65. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/instruction_target.py +0 -0
  66. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/latent_skill.py +0 -0
  67. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/rule.py +0 -0
  68. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/safe_write_result.py +0 -0
  69. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/snapshot.py +0 -0
  70. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/exceptions.py +0 -0
  71. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/audit_log_repository.py +0 -0
  72. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/config_validation_port.py +0 -0
  73. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/context_summary_repository.py +0 -0
  74. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/fact_repository.py +0 -0
  75. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/latent_skill_repository.py +0 -0
  76. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/project_layout_port.py +0 -0
  77. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/rule_repository.py +0 -0
  78. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/secret_scanner_port.py +0 -0
  79. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/snapshot_repository.py +0 -0
  80. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/project_layout.py +0 -0
  81. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/__init__.py +0 -0
  82. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/__init__.py +0 -0
  83. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/adapters.py +0 -0
  84. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/project_layout.py +0 -0
  85. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/toml_loader.py +0 -0
  86. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/__init__.py +0 -0
  87. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/entropy_secret_scanner.py +0 -0
  88. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/local_audit_log_repository.py +0 -0
  89. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/local_snapshot_repository.py +0 -0
  90. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_context_summary_repository.py +0 -0
  91. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/__init__.py +0 -0
  92. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/cli/__init__.py +0 -0
  93. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/cli/message_catalog.py +0 -0
  94. {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/mcp/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: universal-memory
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Vendor-agnostic cognitive persistence layer for AI agents.
5
5
  Keywords: agent-memory,ai,ai-agents,agent-skills,claude-code,codex,context-engineering,developer-tools,llm,mcp,memory
6
6
  Author: Yan L. Amorelli
@@ -20,12 +20,13 @@ Requires-Dist: rich>=15.0.0
20
20
  Requires-Dist: tomli-w>=1.0.0
21
21
  Requires-Dist: typer>=0.9.0
22
22
  Requires-Python: >=3.12
23
- Project-URL: Homepage, https://github.com/YanAmorelli/universal-memory
23
+ Project-URL: Homepage, https://universal-memory.com
24
+ Project-URL: Documentation, https://docs.universal-memory.com
24
25
  Project-URL: Repository, https://github.com/YanAmorelli/universal-memory
25
26
  Description-Content-Type: text/markdown
26
27
 
27
28
  <p align="center">
28
- <img src="assets/umem-logo.png" alt="Universal Memory logo" width="720">
29
+ <img src="https://docs.universal-memory.com/assets/umem-logo-transparent.png" alt="Universal Memory logo" width="720">
29
30
  </p>
30
31
 
31
32
  # Universal Memory (umem)
@@ -38,7 +39,7 @@ A vendor-agnostic cognitive persistence layer for AI agents. Eliminate the "repe
38
39
 
39
40
  To see the core idea visually, check out the [Excalidraw design](https://excalidraw.com/#json=j3XjQIWMYEnkIzHpypuBb,rNJaVOECDGZ3WSuEYcCDjQ) or the proposal structure:
40
41
 
41
- ![Universal Memory MVP Proposal](docs/UNIVERSAL-MEMORY-MVP-PROPOSAL.png)
42
+ ![Universal Memory MVP Proposal](https://docs.universal-memory.com/UNIVERSAL-MEMORY-MVP-PROPOSAL.png)
42
43
 
43
44
  ### Diagram Breakdown
44
45
 
@@ -102,6 +103,34 @@ uvx --from universal-memory umem --help
102
103
  pip install universal-memory
103
104
  ```
104
105
 
106
+ ### Upgrade Universal Memory
107
+ `umem update` does not upgrade the Python package from PyPI. It performs local, offline
108
+ maintenance for the current `.umem` workspace, such as schema migrations, benchmark refreshes,
109
+ and skill synchronization.
110
+
111
+ To upgrade the installed `umem` executable, use the package manager that installed it:
112
+
113
+ ```bash
114
+ # If installed with uv tool
115
+ uv tool upgrade universal-memory
116
+
117
+ # If installed with pipx
118
+ pipx upgrade universal-memory
119
+
120
+ # If installed with pip
121
+ python -m pip install --upgrade universal-memory
122
+
123
+ # If running temporarily with uvx
124
+ uvx --refresh --from universal-memory umem --version
125
+ ```
126
+
127
+ Confirm the executable you are running:
128
+
129
+ ```bash
130
+ umem --version
131
+ which umem
132
+ ```
133
+
105
134
  ---
106
135
 
107
136
  ## Quick Start Guide
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/umem-logo.png" alt="Universal Memory logo" width="720">
2
+ <img src="https://docs.universal-memory.com/assets/umem-logo-transparent.png" alt="Universal Memory logo" width="720">
3
3
  </p>
4
4
 
5
5
  # Universal Memory (umem)
@@ -12,7 +12,7 @@ A vendor-agnostic cognitive persistence layer for AI agents. Eliminate the "repe
12
12
 
13
13
  To see the core idea visually, check out the [Excalidraw design](https://excalidraw.com/#json=j3XjQIWMYEnkIzHpypuBb,rNJaVOECDGZ3WSuEYcCDjQ) or the proposal structure:
14
14
 
15
- ![Universal Memory MVP Proposal](docs/UNIVERSAL-MEMORY-MVP-PROPOSAL.png)
15
+ ![Universal Memory MVP Proposal](https://docs.universal-memory.com/UNIVERSAL-MEMORY-MVP-PROPOSAL.png)
16
16
 
17
17
  ### Diagram Breakdown
18
18
 
@@ -76,6 +76,34 @@ uvx --from universal-memory umem --help
76
76
  pip install universal-memory
77
77
  ```
78
78
 
79
+ ### Upgrade Universal Memory
80
+ `umem update` does not upgrade the Python package from PyPI. It performs local, offline
81
+ maintenance for the current `.umem` workspace, such as schema migrations, benchmark refreshes,
82
+ and skill synchronization.
83
+
84
+ To upgrade the installed `umem` executable, use the package manager that installed it:
85
+
86
+ ```bash
87
+ # If installed with uv tool
88
+ uv tool upgrade universal-memory
89
+
90
+ # If installed with pipx
91
+ pipx upgrade universal-memory
92
+
93
+ # If installed with pip
94
+ python -m pip install --upgrade universal-memory
95
+
96
+ # If running temporarily with uvx
97
+ uvx --refresh --from universal-memory umem --version
98
+ ```
99
+
100
+ Confirm the executable you are running:
101
+
102
+ ```bash
103
+ umem --version
104
+ which umem
105
+ ```
106
+
79
107
  ---
80
108
 
81
109
  ## Quick Start Guide
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "universal-memory"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "Vendor-agnostic cognitive persistence layer for AI agents."
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -40,7 +40,8 @@ dependencies = [
40
40
  ]
41
41
 
42
42
  [project.urls]
43
- Homepage = "https://github.com/YanAmorelli/universal-memory"
43
+ Homepage = "https://universal-memory.com"
44
+ Documentation = "https://docs.universal-memory.com"
44
45
  Repository = "https://github.com/YanAmorelli/universal-memory"
45
46
 
46
47
  [project.scripts]
@@ -54,6 +55,10 @@ dev = [
54
55
  "pytest>=8.0.0",
55
56
  "ruff>=0.3.0",
56
57
  ]
58
+ docs = [
59
+ "mkdocs-material>=9.5.0",
60
+ "mkdocs>=1.6.0",
61
+ ]
57
62
 
58
63
  [build-system]
59
64
  requires = ["uv_build>=0.11.7,<0.12.0"]
@@ -5,4 +5,4 @@ from importlib.metadata import PackageNotFoundError, version
5
5
  try:
6
6
  __version__ = version("universal-memory")
7
7
  except PackageNotFoundError:
8
- __version__ = "0.1.2"
8
+ __version__ = "0.1.4"
@@ -16,7 +16,7 @@ class InstructionDriftDetector:
16
16
  normalized = _normalize_line(claude_line)
17
17
  if normalized in agents_by_normalized:
18
18
  warnings.append(
19
- "Instrucao duplicada em AGENTS.md e CLAUDE.md: "
19
+ "Duplicate instruction in AGENTS.md and CLAUDE.md: "
20
20
  f"{agents_by_normalized[normalized]}"
21
21
  )
22
22
 
@@ -73,7 +73,8 @@ def _detect_always_never_contradictions(
73
73
  agents_polarity = _rule_polarity(agents_norm)
74
74
  if agents_polarity != claude_polarity:
75
75
  warnings.append(
76
- f"Contradicao explicita entre AGENTS.md e CLAUDE.md: {agents_line} / {claude_line}"
76
+ f"Explicit contradiction between AGENTS.md and CLAUDE.md: "
77
+ f"{agents_line} / {claude_line}"
77
78
  )
78
79
  return warnings
79
80
 
@@ -176,7 +176,7 @@ def partition_instruction_blocks(
176
176
  for block in blocks:
177
177
  if UMEM_START in block.content or UMEM_END in block.content:
178
178
  raise ValidationFailedError(
179
- f"O conteudo do bloco '{block.title}' nao pode conter os delimitadores UMEM."
179
+ f"Block '{block.title}' content cannot contain UMEM delimiters."
180
180
  )
181
181
 
182
182
  classification = block.resolved_classification
@@ -184,12 +184,10 @@ def partition_instruction_blocks(
184
184
  relative_path = block.relative_path or _canonical_doc_path(block.title, docs_directory)
185
185
  _safe_relative_path(relative_path)
186
186
  if relative_path in seen_paths:
187
- raise ValidationFailedError(
188
- f"Caminho canonico duplicado detectado: {relative_path}"
189
- )
187
+ raise ValidationFailedError(f"Duplicate canonical path detected: {relative_path}")
190
188
  seen_paths.add(relative_path)
191
189
  document = CanonicalDocument(
192
- title=block.title.strip() or "Documento canônico",
190
+ title=block.title.strip() or "Canonical document",
193
191
  content=block.content.strip(),
194
192
  relative_path=relative_path,
195
193
  )
@@ -342,11 +340,11 @@ class ConfigureHostUseCase:
342
340
  try:
343
341
  path.relative_to(project_root_resolved)
344
342
  except ValueError:
345
- failures.append("Falha de Arquivo de Instrução: caminho fora do projeto.")
343
+ failures.append("Instruction file failure: path is outside the project.")
346
344
  return self._host_read_validation_result(method, checks, failures)
347
345
 
348
346
  if not path.exists() or not path.is_file():
349
- failures.append(f"Falha de Arquivo de Instrução: {target.relative_path} ausente.")
347
+ failures.append(f"Instruction file failure: {target.relative_path} is missing.")
350
348
  return self._host_read_validation_result(method, checks, failures)
351
349
 
352
350
  checks["instruction_file_exists"] = True
@@ -355,8 +353,7 @@ class ConfigureHostUseCase:
355
353
  content = path.read_text(encoding="utf-8")
356
354
  except (OSError, UnicodeDecodeError) as exc:
357
355
  failures.append(
358
- f"Falha de Permissão de Leitura ou Escrita: não foi possível ler "
359
- f"{target.relative_path}: {exc}"
356
+ f"Read or write permission failure: could not read {target.relative_path}: {exc}"
360
357
  )
361
358
  return self._host_read_validation_result(method, checks, failures)
362
359
 
@@ -369,8 +366,8 @@ class ConfigureHostUseCase:
369
366
  )
370
367
  except ValidationFailedError:
371
368
  failures.append(
372
- f"Falha de Arquivo de Instrução: {target.relative_path} deve conter "
373
- "delimitadores UMEM válidos."
369
+ f"Instruction file failure: {target.relative_path} must contain valid UMEM "
370
+ "delimiters."
374
371
  )
375
372
  return self._host_read_validation_result(method, checks, failures)
376
373
 
@@ -384,14 +381,14 @@ class ConfigureHostUseCase:
384
381
  )
385
382
  self._validate_no_raw_memory_dump(content, target_path=target.relative_path)
386
383
  except ValidationFailedError as exc:
387
- failures.append(f"Falha de Arquivo de Instrução: {exc}")
384
+ failures.append(f"Instruction file failure: {exc}")
388
385
 
389
386
  inner_content = self._managed_block_inner_content(managed_block).strip()
390
387
  if inner_content:
391
388
  checks["managed_block_has_content"] = True
392
389
  else:
393
390
  failures.append(
394
- f"Falha de Arquivo de Instrução: bloco UMEM em {target.relative_path} está vazio."
391
+ f"Instruction file failure: UMEM block in {target.relative_path} is empty."
395
392
  )
396
393
 
397
394
  if self._has_mcp_reference(inner_content):
@@ -399,8 +396,8 @@ class ConfigureHostUseCase:
399
396
  checks["mcp_configuration_documented_or_active"] = True
400
397
  else:
401
398
  failures.append(
402
- "Falha de Configuração MCP: bloco UMEM não referencia universal-memory, "
403
- "MCP/FastMCP ou comandos como umem context/status."
399
+ "MCP configuration failure: UMEM block does not reference universal-memory, "
400
+ "MCP/FastMCP, or commands such as umem context/status."
404
401
  )
405
402
 
406
403
  result = self._host_read_validation_result(method, checks, failures)
@@ -531,14 +528,16 @@ class ConfigureHostUseCase:
531
528
  target_name_val = getattr(target.name, "value", target.name)
532
529
  if classification_val not in supported:
533
530
  warnings.append(
534
- f"Instrucao '{block.title}' com classificacao '{classification_val}' "
535
- f"foi ignorada pois nao e suportada pelo target {target_name_val}."
531
+ f"Instruction '{block.title}' with classification '{classification_val}' "
532
+ f"was ignored because target {target_name_val} does not support it."
536
533
  )
537
534
 
538
535
  canonical_documents: list[CanonicalDocument] = []
539
536
  if target.name == InstructionTargetType.claude_md:
540
537
  if partition.canonical_documents:
541
- raise ValidationFailedError("Host Claude Code nao suporta documentos canonicos.")
538
+ raise ValidationFailedError(
539
+ "Claude Code host does not support canonical documents."
540
+ )
542
541
  managed_content = self._render_claude_managed_block(
543
542
  self._target_manifest_blocks(
544
543
  partition,
@@ -667,7 +666,7 @@ class ConfigureHostUseCase:
667
666
  try:
668
667
  host_name = HostName(host_id)
669
668
  except ValueError as exc:
670
- raise ValidationFailedError(f"Host nao suportado: {host_id}") from exc
669
+ raise ValidationFailedError(f"Unsupported host: {host_id}") from exc
671
670
  timestamp = datetime.now(UTC)
672
671
  if host_name == HostName.claude_code:
673
672
  return Host(
@@ -696,7 +695,7 @@ class ConfigureHostUseCase:
696
695
  audit_event_type="host_setup",
697
696
  )
698
697
  else:
699
- raise ValidationFailedError(f"Host ainda nao suportado para setup: {host_id}")
698
+ raise ValidationFailedError(f"Host setup is not yet supported for: {host_id}")
700
699
 
701
700
  def _agents_md_target(self, host: Host) -> InstructionTarget:
702
701
  return self._instruction_target_for(host, InstructionTargetType.agents_md)
@@ -707,7 +706,9 @@ class ConfigureHostUseCase:
707
706
  target_type: InstructionTargetType,
708
707
  ) -> InstructionTarget:
709
708
  if host is not None and target_type not in host.supported_targets:
710
- raise ValidationFailedError(f"Host {host.name.value} nao suporta {target_type.value}")
709
+ raise ValidationFailedError(
710
+ f"Host {host.name.value} does not support {target_type.value}"
711
+ )
711
712
  timestamp = datetime.now(UTC)
712
713
  if target_type == InstructionTargetType.claude_md:
713
714
  return InstructionTarget(
@@ -738,19 +739,19 @@ class ConfigureHostUseCase:
738
739
  ],
739
740
  )
740
741
  else:
741
- raise ValidationFailedError(f"Target nao suportado: {target_type.value}")
742
+ raise ValidationFailedError(f"Unsupported target: {target_type.value}")
742
743
 
743
744
  def _read_existing(self, relative_path: str) -> str:
744
745
  path = (self.project_root / relative_path).resolve()
745
746
  if not path.exists():
746
747
  return ""
747
748
  if not path.is_file():
748
- raise ValidationFailedError(f"Caminho alvo nao e arquivo: {relative_path}")
749
+ raise ValidationFailedError(f"Target path is not a file: {relative_path}")
749
750
  try:
750
751
  path.relative_to(self.project_root)
751
752
  except ValueError as exc:
752
753
  raise ValidationFailedError(
753
- "Caminho fora do diretorio do projeto nao permitido."
754
+ "Path outside the project directory is not allowed."
754
755
  ) from exc
755
756
  return path.read_text(encoding="utf-8")
756
757
 
@@ -771,6 +772,8 @@ class ConfigureHostUseCase:
771
772
  "> Read and follow `.umem/skills/use-universal-memory/SKILL.md`. If a relevant "
772
773
  "active skill exists, inspect it with "
773
774
  "`umem skills detail <skill-id-or-name> --format json` before acting.",
775
+ "> For repeated durable workflows, consider latent skill tracking via the UMEM guide; "
776
+ "do not track one-off work, raw evidence, secrets, or private data.",
774
777
  "> If `umem` is unavailable or not initialized, report that explicitly before "
775
778
  "continuing without external memory.",
776
779
  "> CRITICAL: proactively capture new memory or clean up outdated ones only when a "
@@ -800,7 +803,7 @@ class ConfigureHostUseCase:
800
803
  "fix, or decision was made. If so:",
801
804
  '1. Record: Proactively run `umem remember "Short fact." --scope project/global --tag <tag>` '
802
805
  "or `umem facts purge --id <id>` to remove outdated facts.",
803
- "2. Propose Skills: Check `umem skills list` and run `umem skills propose` if recurring patterns exist.",
806
+ "2. Track Skills: For newly observed repeated workflows, run `umem skills track` with sanitized evidence; use `umem skills propose` only for existing latent candidates after approval.",
804
807
  "3. Sync: If you ran any mutation (remember or purge), run `umem host sync --apply` to bake it.",
805
808
  '4. Report: Append the UMEM status line `[UMEM: Remembered "..."]` or `[UMEM: No new facts/skills to record]` to your final output.',
806
809
  "",
@@ -848,6 +851,10 @@ class ConfigureHostUseCase:
848
851
  "relevant active skill exists, inspect it with "
849
852
  "`umem skills detail <skill-id-or-name> --format json` before acting."
850
853
  )
854
+ latent_skill_line = (
855
+ "> For repeated durable workflows, consider latent skill tracking via the UMEM guide; "
856
+ "do not track one-off work, raw evidence, secrets, or private data."
857
+ )
851
858
  unavailable_line = (
852
859
  "> If `umem` is unavailable or not initialized, report that explicitly before "
853
860
  "continuing without external memory."
@@ -885,6 +892,10 @@ class ConfigureHostUseCase:
885
892
  "relevant active skill exists, inspect it with "
886
893
  "`umem skills detail <skill-id-or-name> --format json` before acting."
887
894
  )
895
+ latent_skill_line = (
896
+ "> For repeated durable workflows, consider latent skill tracking via the UMEM guide; "
897
+ "do not track one-off work, raw evidence, secrets, or private data."
898
+ )
888
899
  unavailable_line = (
889
900
  "> If `umem` is unavailable or not initialized, report that explicitly before "
890
901
  "continuing without external memory."
@@ -912,6 +923,7 @@ class ConfigureHostUseCase:
912
923
  scope_line,
913
924
  memory_line,
914
925
  policy_line,
926
+ latent_skill_line,
915
927
  unavailable_line,
916
928
  recording_line,
917
929
  report_line,
@@ -940,7 +952,7 @@ class ConfigureHostUseCase:
940
952
  "fix, or decision was made. If so:",
941
953
  '1. Record: Proactively run `umem remember "Short fact." --scope project/global --tag <tag>` '
942
954
  "or `umem facts purge --id <id>` to remove outdated facts.",
943
- "2. Propose Skills: Check `umem skills list` and run `umem skills propose` if recurring patterns exist.",
955
+ "2. Track Skills: For newly observed repeated workflows, run `umem skills track` with sanitized evidence; use `umem skills propose` only for existing latent candidates after approval.",
944
956
  "3. Sync: If you ran any mutation (remember or purge), run `umem host sync --apply` to bake it.",
945
957
  '4. Report: Append the UMEM status line `[UMEM: Remembered "..."]` or `[UMEM: No new facts/skills to record]` to your final output.',
946
958
  "",
@@ -1015,7 +1027,7 @@ class ConfigureHostUseCase:
1015
1027
  chars_count = len(content)
1016
1028
  if chars_count > max_chars or lines_count > max_lines:
1017
1029
  raise ValidationFailedError(
1018
- f"Manifesto {target_path} deve permanecer compacto; mova conteudo longo para docs/."
1030
+ f"Manifest {target_path} must remain compact; move long content to docs/."
1019
1031
  )
1020
1032
 
1021
1033
  def _validate_no_raw_memory_dump(self, content: str, *, target_path: str = "AGENTS.md") -> None:
@@ -1027,15 +1039,15 @@ class ConfigureHostUseCase:
1027
1039
  )
1028
1040
  if json_fact_hits >= 2: # noqa: PLR2004
1029
1041
  raise ValidationFailedError(
1030
- f"Manifesto {target_path} deve permanecer compacto e nao pode conter dump bruto "
1031
- "de fatos ou memorias."
1042
+ f"Manifest {target_path} must remain compact and cannot contain raw fact or "
1043
+ "memory dumps."
1032
1044
  )
1033
1045
 
1034
1046
  raw_fact_hits = len(re.findall(r"\b(?:raw memory fact|fact_id|source_fact_ids)\b", managed))
1035
1047
  if raw_fact_hits >= 5: # noqa: PLR2004
1036
1048
  raise ValidationFailedError(
1037
- f"Manifesto {target_path} deve permanecer compacto e nao pode conter dump bruto "
1038
- "de fatos ou memorias."
1049
+ f"Manifest {target_path} must remain compact and cannot contain raw fact or "
1050
+ "memory dumps."
1039
1051
  )
1040
1052
 
1041
1053
  def _planned_changes(
@@ -1151,8 +1163,8 @@ def _canonical_doc_path(title: str, docs_directory: str) -> str:
1151
1163
  def _safe_relative_path(value: str) -> str:
1152
1164
  normalized = value.replace("\\", "/")
1153
1165
  if ":" in normalized or normalized.startswith("/") or not normalized:
1154
- raise ValidationFailedError("Caminho relativo invalido.")
1166
+ raise ValidationFailedError("Invalid relative path.")
1155
1167
  path = PurePosixPath(normalized)
1156
1168
  if path.is_absolute() or ".." in path.parts:
1157
- raise ValidationFailedError("Caminho relativo invalido.")
1169
+ raise ValidationFailedError("Invalid relative path.")
1158
1170
  return path.as_posix()
@@ -95,7 +95,10 @@ class SyncInstructionsUseCase:
95
95
  )
96
96
  command = replace(command, max_managed_lines=max_lines, max_managed_chars=max_chars)
97
97
 
98
- host_ids, config_warnings = self._host_ids_for_command(command.host_ids)
98
+ host_ids, config_warnings = self._host_ids_for_command(
99
+ command.host_ids,
100
+ apply=command.apply,
101
+ )
99
102
  all_blocks = self._active_rule_blocks()
100
103
  plans = self._plan_commands(host_ids, all_blocks, command)
101
104
 
@@ -306,8 +309,6 @@ class SyncInstructionsUseCase:
306
309
  should_write_agents,
307
310
  )
308
311
  )
309
- if not plans and host_ids:
310
- raise ValidationFailedError("Nenhum host suportado informado para sincronizacao.")
311
312
  return plans
312
313
 
313
314
  def _active_rule_blocks(self) -> list[InstructionBlock]:
@@ -345,7 +346,7 @@ class SyncInstructionsUseCase:
345
346
  return InstructionClassification(str(raw))
346
347
  except ValueError as exc:
347
348
  raise ValidationFailedError(
348
- f"Classificacao de regra nao suportada para '{rule.name}': {raw}"
349
+ f"Unsupported rule classification for '{rule.name}': {raw}"
349
350
  ) from exc
350
351
 
351
352
  def _normalized_host_ids(self, host_ids: list[str] | None) -> list[str]:
@@ -356,10 +357,12 @@ class SyncInstructionsUseCase:
356
357
  normalized.append(host_id)
357
358
  unsupported = [host_id for host_id in normalized if host_id not in DEFAULT_SYNC_HOSTS]
358
359
  if unsupported:
359
- raise ValidationFailedError(f"Hosts nao suportados: {', '.join(unsupported)}")
360
+ raise ValidationFailedError(f"Unsupported hosts: {', '.join(unsupported)}")
360
361
  return normalized
361
362
 
362
- def _host_ids_for_command(self, host_ids: list[str] | None) -> tuple[list[str], list[str]]:
363
+ def _host_ids_for_command(
364
+ self, host_ids: list[str] | None, *, apply: bool
365
+ ) -> tuple[list[str], list[str]]:
363
366
  normalized = self._normalized_host_ids(host_ids)
364
367
  enabled_hosts = self._enabled_hosts_from_config()
365
368
  if enabled_hosts is None:
@@ -371,14 +374,14 @@ class SyncInstructionsUseCase:
371
374
  warnings = []
372
375
  to_enable = []
373
376
  for host_id in normalized:
374
- if host_id not in enabled_hosts:
377
+ if host_id not in enabled_hosts and apply:
375
378
  warnings.append(
376
- f"Host '{host_id}' nao esta habilitado em .umem/config.toml; "
377
- "ativando automaticamente."
379
+ f"Host '{host_id}' is not enabled in .umem/config.toml; enabling it "
380
+ "automatically."
378
381
  )
379
382
  to_enable.append(host_id)
380
383
 
381
- if to_enable:
384
+ if to_enable and apply:
382
385
  new_enabled = list(enabled_hosts)
383
386
  for h in to_enable:
384
387
  if h not in new_enabled:
@@ -391,20 +394,18 @@ class SyncInstructionsUseCase:
391
394
  try:
392
395
  loaded = load_config(self.project_root)
393
396
  except (OSError, InvalidConfigError, StorageError) as exc:
394
- raise ValidationFailedError(f"Falha ao ler configuracao do projeto: {exc}") from exc
397
+ raise ValidationFailedError(f"Failed to read project configuration: {exc}") from exc
395
398
 
396
399
  raw_runtimes = loaded.merged.get("runtimes")
397
400
  if raw_runtimes is None:
398
401
  return None
399
402
  if not isinstance(raw_runtimes, dict):
400
- raise ValidationFailedError("Configuracao invalida: runtimes deve ser uma tabela.")
403
+ raise ValidationFailedError("Invalid configuration: runtimes must be a table.")
401
404
  raw_enabled = raw_runtimes.get("enabled")
402
405
  if raw_enabled is None:
403
406
  return None
404
407
  if not isinstance(raw_enabled, list):
405
- raise ValidationFailedError(
406
- "Configuracao invalida: runtimes.enabled deve ser uma lista."
407
- )
408
+ raise ValidationFailedError("Invalid configuration: runtimes.enabled must be a list.")
408
409
  enabled = [str(host_id) for host_id in raw_enabled]
409
410
  return [host_id for host_id in enabled if host_id in DEFAULT_SYNC_HOSTS]
410
411
 
@@ -420,7 +421,7 @@ class SyncInstructionsUseCase:
420
421
  def _manual_steps(self, results: list[Any], apply: bool) -> list[str]:
421
422
  steps: list[str] = []
422
423
  if not apply:
423
- steps.append("Revise os caminhos afetados antes de aplicar.")
424
+ steps.append("Review affected paths before applying.")
424
425
  for result in results:
425
426
  for step in result.manual_steps:
426
427
  if step not in steps:
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
 
10
10
  from universal_memory import __version__
11
11
  from universal_memory.domain.entities import (
12
+ AgentSkillStatus,
12
13
  AuditEventScope,
13
14
  FactScope,
14
15
  FactStatus,
@@ -17,6 +18,7 @@ from universal_memory.domain.entities import (
17
18
  )
18
19
  from universal_memory.domain.entities.base import format_utc_iso
19
20
  from universal_memory.domain.ports import (
21
+ AgentSkillRepository,
20
22
  AuditLogRepository,
21
23
  FactRepository,
22
24
  LatentSkillRepository,
@@ -52,6 +54,7 @@ class GetMemoryStatusUseCase:
52
54
  rule_repository: RuleRepository,
53
55
  latent_skill_repository: LatentSkillRepository,
54
56
  layout_port: ProjectLayoutPort,
57
+ agent_skill_repository: AgentSkillRepository | None = None,
55
58
  audit_log_repository: AuditLogRepository | None = None,
56
59
  data_root: Path | None = None,
57
60
  clock: Callable[[], datetime] = lambda: datetime.now(UTC),
@@ -59,6 +62,7 @@ class GetMemoryStatusUseCase:
59
62
  self.fact_repository = fact_repository
60
63
  self.rule_repository = rule_repository
61
64
  self.latent_skill_repository = latent_skill_repository
65
+ self.agent_skill_repository = agent_skill_repository
62
66
  self.layout_port = layout_port
63
67
  self.audit_log_repository = audit_log_repository
64
68
  self.data_root = data_root
@@ -79,10 +83,10 @@ class GetMemoryStatusUseCase:
79
83
  approximate_size_bytes=0,
80
84
  last_health_check=None,
81
85
  host_validation={},
82
- recommended_action="Execute 'umem init' a partir do diretorio raiz do projeto.",
86
+ recommended_action="Run 'umem init' from the project root directory.",
83
87
  )
84
88
 
85
- # Diagnóstico de health check: verificar permissão de leitura/escrita
89
+ # Health check: verify read/write permissions.
86
90
  health_ok = True
87
91
  try:
88
92
  if not data_root.exists() or not os.access(data_root, os.R_OK | os.W_OK):
@@ -96,20 +100,25 @@ class GetMemoryStatusUseCase:
96
100
  fact_counts[fact.scope.value][fact.status.value] += 1
97
101
 
98
102
  active_rules = self.rule_repository.list(status=RuleStatus.active)
99
- active_skills = self.latent_skill_repository.list(status=LatentSkillStatus.active)
103
+ registered_skills_count = self._registered_skills_count()
100
104
 
101
105
  return GetMemoryStatusResult(
102
106
  initialized=True,
103
107
  project_path=project_path,
104
108
  fact_counts=fact_counts,
105
109
  active_rules_count=len(active_rules),
106
- registered_skills_count=len(active_skills),
110
+ registered_skills_count=registered_skills_count,
107
111
  approximate_size_bytes=_directory_size(data_root),
108
112
  last_health_check=format_utc_iso(self.clock()) if health_ok else None,
109
113
  host_validation=self._host_validation(),
110
114
  recommended_action=None,
111
115
  )
112
116
 
117
+ def _registered_skills_count(self) -> int:
118
+ if self.agent_skill_repository is not None:
119
+ return len(self.agent_skill_repository.list(status=AgentSkillStatus.active))
120
+ return len(self.latent_skill_repository.list(status=LatentSkillStatus.active))
121
+
113
122
  def _host_validation(self) -> dict[str, dict[str, str | None]]:
114
123
  unconfigured = {
115
124
  "claude_code": _unconfigured_host_validation(),
@@ -25,12 +25,10 @@ class PurgeFactUseCase:
25
25
 
26
26
  def execute(self, command: PurgeFactCommand) -> PurgeFactResult:
27
27
  if command.id is None and command.scope is None:
28
- raise ValidationFailedError("Deve ser fornecido um id de fato ou um escopo para purga.")
28
+ raise ValidationFailedError("Provide either a fact ID or a scope to purge.")
29
29
 
30
30
  if command.id is not None and command.scope is not None:
31
- raise ValidationFailedError(
32
- "Nao e permitido fornecer simultaneamente id e escopo para purga."
33
- )
31
+ raise ValidationFailedError("Provide either a fact ID or a scope to purge, not both.")
34
32
 
35
33
  if command.id is not None:
36
34
  fact = self.fact_repository.read(command.id)