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.
- {universal_memory-0.1.2 → universal_memory-0.1.4}/PKG-INFO +33 -4
- {universal_memory-0.1.2 → universal_memory-0.1.4}/README.md +30 -2
- {universal_memory-0.1.2 → universal_memory-0.1.4}/pyproject.toml +7 -2
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/__init__.py +1 -1
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/drift_detector.py +3 -2
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/setup_host_use_case.py +45 -33
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/sync_instructions_use_case.py +17 -16
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/get_memory_status_use_case.py +13 -4
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/purge_fact_use_case.py +2 -4
- universal_memory-0.1.4/src/universal_memory/application/onboarding/setup_project.py +902 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/rollback_use_case.py +7 -7
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/safe_write_use_case.py +39 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/__init__.py +44 -0
- universal_memory-0.1.4/src/universal_memory/application/skills/create_skill.py +363 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/generate_skill.py +14 -9
- universal_memory-0.1.4/src/universal_memory/application/skills/import_skill.py +535 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/list_skills.py +210 -11
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/native_skill_sync.py +180 -18
- universal_memory-0.1.4/src/universal_memory/application/skills/promote_skill.py +191 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/propose_skill.py +11 -7
- universal_memory-0.1.4/src/universal_memory/application/skills/recommend_skills.py +222 -0
- universal_memory-0.1.4/src/universal_memory/application/skills/sync_skills.py +302 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/update_skill.py +11 -11
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/update/__init__.py +6 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/update/update_use_cases.py +162 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/bootstrap/cli.py +54 -1
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/bootstrap/mcp.py +48 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/__init__.py +3 -0
- universal_memory-0.1.4/src/universal_memory/domain/entities/agent_skill.py +28 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/runtime.py +7 -1
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/__init__.py +2 -0
- universal_memory-0.1.4/src/universal_memory/domain/ports/agent_skill_repository.py +25 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/__init__.py +4 -0
- universal_memory-0.1.4/src/universal_memory/infrastructure/storage/local_agent_skill_repository.py +256 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_fact_repository.py +1 -7
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_latent_skill_repository.py +1 -7
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_rule_repository.py +1 -2
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/cli/init_command.py +821 -43
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/errors.py +15 -34
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/mcp/server.py +208 -5
- universal_memory-0.1.2/src/universal_memory/application/onboarding/setup_project.py +0 -302
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/__main__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/diagnostics/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/diagnostics/doctor_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/host/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/assemble_context_summary_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/context_hygiene_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/list_facts_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/remember_fact_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/memory/search_facts_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/onboarding/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/list_audit_log_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/security/list_snapshots_use_case.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/application/skills/track_latent_skill.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/bootstrap/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/audit_event.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/base.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/context_summary.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/fact.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/host.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/instruction_target.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/latent_skill.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/rule.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/safe_write_result.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/entities/snapshot.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/exceptions.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/audit_log_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/config_validation_port.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/context_summary_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/fact_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/latent_skill_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/project_layout_port.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/rule_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/secret_scanner_port.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/ports/snapshot_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/domain/project_layout.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/adapters.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/project_layout.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/config/toml_loader.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/entropy_secret_scanner.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/local_audit_log_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/security/local_snapshot_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/infrastructure/storage/local_context_summary_repository.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/cli/__init__.py +0 -0
- {universal_memory-0.1.2 → universal_memory-0.1.4}/src/universal_memory/interfaces/cli/message_catalog.py +0 -0
- {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.
|
|
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://
|
|
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
|
-

|
|
42
|
+

|
|
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
|
-

|
|
15
|
+

|
|
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.
|
|
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://
|
|
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"]
|
|
@@ -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
|
-
"
|
|
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"
|
|
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"
|
|
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 "
|
|
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("
|
|
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"
|
|
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"
|
|
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"
|
|
373
|
-
"
|
|
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"
|
|
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"
|
|
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
|
-
"
|
|
403
|
-
"MCP/FastMCP
|
|
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"
|
|
535
|
-
f"
|
|
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(
|
|
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"
|
|
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
|
|
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(
|
|
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"
|
|
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"
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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"
|
|
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"
|
|
1031
|
-
"
|
|
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"
|
|
1038
|
-
"
|
|
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("
|
|
1166
|
+
raise ValidationFailedError("Invalid relative path.")
|
|
1155
1167
|
path = PurePosixPath(normalized)
|
|
1156
1168
|
if path.is_absolute() or ".." in path.parts:
|
|
1157
|
-
raise ValidationFailedError("
|
|
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(
|
|
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"
|
|
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"
|
|
360
|
+
raise ValidationFailedError(f"Unsupported hosts: {', '.join(unsupported)}")
|
|
360
361
|
return normalized
|
|
361
362
|
|
|
362
|
-
def _host_ids_for_command(
|
|
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}'
|
|
377
|
-
"
|
|
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"
|
|
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("
|
|
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("
|
|
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="
|
|
86
|
+
recommended_action="Run 'umem init' from the project root directory.",
|
|
83
87
|
)
|
|
84
88
|
|
|
85
|
-
#
|
|
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
|
-
|
|
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=
|
|
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("
|
|
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)
|