agmem 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/METADATA +157 -16
- agmem-0.1.3.dist-info/RECORD +105 -0
- memvcs/__init__.py +1 -1
- memvcs/cli.py +45 -31
- memvcs/commands/__init__.py +9 -9
- memvcs/commands/add.py +83 -76
- memvcs/commands/audit.py +59 -0
- memvcs/commands/blame.py +46 -53
- memvcs/commands/branch.py +13 -33
- memvcs/commands/checkout.py +27 -32
- memvcs/commands/clean.py +18 -23
- memvcs/commands/clone.py +11 -1
- memvcs/commands/commit.py +40 -39
- memvcs/commands/daemon.py +109 -76
- memvcs/commands/decay.py +77 -0
- memvcs/commands/diff.py +56 -57
- memvcs/commands/distill.py +90 -0
- memvcs/commands/federated.py +53 -0
- memvcs/commands/fsck.py +86 -61
- memvcs/commands/garden.py +40 -35
- memvcs/commands/gc.py +51 -0
- memvcs/commands/graph.py +41 -48
- memvcs/commands/init.py +16 -24
- memvcs/commands/log.py +25 -40
- memvcs/commands/merge.py +69 -27
- memvcs/commands/pack.py +129 -0
- memvcs/commands/prove.py +66 -0
- memvcs/commands/pull.py +31 -1
- memvcs/commands/push.py +4 -2
- memvcs/commands/recall.py +145 -0
- memvcs/commands/reflog.py +13 -22
- memvcs/commands/remote.py +1 -0
- memvcs/commands/repair.py +66 -0
- memvcs/commands/reset.py +23 -33
- memvcs/commands/resolve.py +130 -0
- memvcs/commands/resurrect.py +82 -0
- memvcs/commands/search.py +3 -4
- memvcs/commands/serve.py +2 -1
- memvcs/commands/show.py +66 -36
- memvcs/commands/stash.py +34 -34
- memvcs/commands/status.py +27 -35
- memvcs/commands/tag.py +23 -47
- memvcs/commands/test.py +30 -44
- memvcs/commands/timeline.py +111 -0
- memvcs/commands/tree.py +26 -27
- memvcs/commands/verify.py +110 -0
- memvcs/commands/when.py +115 -0
- memvcs/core/access_index.py +167 -0
- memvcs/core/audit.py +124 -0
- memvcs/core/config_loader.py +3 -1
- memvcs/core/consistency.py +214 -0
- memvcs/core/crypto_verify.py +280 -0
- memvcs/core/decay.py +185 -0
- memvcs/core/diff.py +158 -143
- memvcs/core/distiller.py +277 -0
- memvcs/core/encryption.py +169 -0
- memvcs/core/federated.py +86 -0
- memvcs/core/gardener.py +176 -145
- memvcs/core/hooks.py +48 -14
- memvcs/core/ipfs_remote.py +39 -0
- memvcs/core/knowledge_graph.py +135 -138
- memvcs/core/llm/__init__.py +10 -0
- memvcs/core/llm/anthropic_provider.py +50 -0
- memvcs/core/llm/base.py +27 -0
- memvcs/core/llm/factory.py +30 -0
- memvcs/core/llm/openai_provider.py +36 -0
- memvcs/core/merge.py +260 -170
- memvcs/core/objects.py +110 -101
- memvcs/core/pack.py +92 -0
- memvcs/core/pii_scanner.py +147 -146
- memvcs/core/privacy_budget.py +63 -0
- memvcs/core/refs.py +132 -115
- memvcs/core/remote.py +38 -0
- memvcs/core/repository.py +254 -164
- memvcs/core/schema.py +155 -113
- memvcs/core/staging.py +60 -65
- memvcs/core/storage/__init__.py +20 -18
- memvcs/core/storage/base.py +74 -70
- memvcs/core/storage/gcs.py +70 -68
- memvcs/core/storage/local.py +42 -40
- memvcs/core/storage/s3.py +105 -110
- memvcs/core/temporal_index.py +121 -0
- memvcs/core/test_runner.py +101 -93
- memvcs/core/trust.py +103 -0
- memvcs/core/vector_store.py +56 -36
- memvcs/core/zk_proofs.py +26 -0
- memvcs/integrations/mcp_server.py +1 -3
- memvcs/integrations/web_ui/server.py +25 -26
- memvcs/retrieval/__init__.py +22 -0
- memvcs/retrieval/base.py +54 -0
- memvcs/retrieval/pack.py +128 -0
- memvcs/retrieval/recaller.py +105 -0
- memvcs/retrieval/strategies.py +314 -0
- memvcs/utils/__init__.py +3 -3
- memvcs/utils/helpers.py +52 -52
- agmem-0.1.1.dist-info/RECORD +0 -67
- {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/WHEEL +0 -0
- {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/entry_points.txt +0 -0
- {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recall strategies: recency, importance, similarity, hybrid.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Any, Optional
|
|
9
|
+
|
|
10
|
+
from .base import RetrievalStrategy, RecallResult
|
|
11
|
+
from ..core.constants import MEMORY_TYPES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _matches_exclude(path: str, exclude: List[str]) -> bool:
|
|
15
|
+
"""Return True if path matches any exclude pattern."""
|
|
16
|
+
if not exclude:
|
|
17
|
+
return False
|
|
18
|
+
for pattern in exclude:
|
|
19
|
+
if fnmatch.fnmatch(path, pattern):
|
|
20
|
+
return True
|
|
21
|
+
if fnmatch.fnmatch(path, f"*/{pattern}"):
|
|
22
|
+
return True
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RecencyStrategy(RetrievalStrategy):
|
|
27
|
+
"""Sort by commit timestamp / last_updated (newest first)."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, repo: Any):
|
|
30
|
+
self.repo = repo
|
|
31
|
+
|
|
32
|
+
def recall(
|
|
33
|
+
self,
|
|
34
|
+
context: str,
|
|
35
|
+
limit: int,
|
|
36
|
+
exclude: List[str],
|
|
37
|
+
**kwargs: Any,
|
|
38
|
+
) -> List[RecallResult]:
|
|
39
|
+
"""Recall by recency - scan current/ and sort by mtime or frontmatter."""
|
|
40
|
+
results = []
|
|
41
|
+
current_dir = self.repo.current_dir
|
|
42
|
+
if not current_dir.exists():
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
for subdir in MEMORY_TYPES:
|
|
46
|
+
dir_path = current_dir / subdir
|
|
47
|
+
if not dir_path.exists():
|
|
48
|
+
continue
|
|
49
|
+
for f in dir_path.rglob("*"):
|
|
50
|
+
if not f.is_file() or f.suffix.lower() not in (".md", ".txt"):
|
|
51
|
+
continue
|
|
52
|
+
try:
|
|
53
|
+
rel_path = str(f.relative_to(current_dir))
|
|
54
|
+
except ValueError:
|
|
55
|
+
continue
|
|
56
|
+
if _matches_exclude(rel_path, exclude):
|
|
57
|
+
continue
|
|
58
|
+
try:
|
|
59
|
+
content = f.read_text(encoding="utf-8", errors="replace")
|
|
60
|
+
except Exception:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
# Use mtime as recency proxy
|
|
64
|
+
mtime = f.stat().st_mtime
|
|
65
|
+
ts = datetime.fromtimestamp(mtime).isoformat() + "Z"
|
|
66
|
+
|
|
67
|
+
results.append(
|
|
68
|
+
RecallResult(
|
|
69
|
+
path=rel_path,
|
|
70
|
+
content=content[:2000] + ("..." if len(content) > 2000 else ""),
|
|
71
|
+
relevance_score=1.0 / (1.0 + mtime), # newer = higher
|
|
72
|
+
source={"indexed_at": ts, "commit_hash": None, "author": None},
|
|
73
|
+
importance=None,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Sort by mtime descending (newest first)
|
|
78
|
+
results.sort(key=lambda r: r.source.get("indexed_at", ""), reverse=True)
|
|
79
|
+
return results[:limit]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ImportanceStrategy(RetrievalStrategy):
|
|
83
|
+
"""Sort by importance from commit metadata."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, repo: Any):
|
|
86
|
+
self.repo = repo
|
|
87
|
+
|
|
88
|
+
def recall(
|
|
89
|
+
self,
|
|
90
|
+
context: str,
|
|
91
|
+
limit: int,
|
|
92
|
+
exclude: List[str],
|
|
93
|
+
**kwargs: Any,
|
|
94
|
+
) -> List[RecallResult]:
|
|
95
|
+
"""Recall by importance - use commit metadata and frontmatter."""
|
|
96
|
+
from ..core.objects import Commit, Blob, Tree
|
|
97
|
+
from ..core.schema import FrontmatterParser
|
|
98
|
+
|
|
99
|
+
results = []
|
|
100
|
+
head = self.repo.get_head_commit()
|
|
101
|
+
if not head:
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
tree = self.repo.get_commit_tree(head.store(self.repo.object_store))
|
|
105
|
+
if not tree:
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
commit_importance = head.metadata.get("importance", 0.5)
|
|
109
|
+
|
|
110
|
+
def collect_files(entries: list, prefix: str = "") -> None:
|
|
111
|
+
for entry in entries:
|
|
112
|
+
# Support both hierarchical trees and flat trees (entry.path = parent dir)
|
|
113
|
+
path = (
|
|
114
|
+
f"{prefix}/{entry.path}/{entry.name}".replace("//", "/").lstrip("/")
|
|
115
|
+
if prefix or entry.path
|
|
116
|
+
else entry.name
|
|
117
|
+
)
|
|
118
|
+
if entry.obj_type == "tree":
|
|
119
|
+
subtree = Tree.load(self.repo.object_store, entry.hash)
|
|
120
|
+
if subtree:
|
|
121
|
+
collect_files(subtree.entries, path)
|
|
122
|
+
else:
|
|
123
|
+
if _matches_exclude(path, exclude):
|
|
124
|
+
continue
|
|
125
|
+
blob = Blob.load(self.repo.object_store, entry.hash)
|
|
126
|
+
if not blob:
|
|
127
|
+
continue
|
|
128
|
+
try:
|
|
129
|
+
content = blob.content.decode("utf-8", errors="replace")
|
|
130
|
+
except Exception:
|
|
131
|
+
continue
|
|
132
|
+
fm, body = FrontmatterParser.parse(content)
|
|
133
|
+
importance = None
|
|
134
|
+
if fm and fm.importance is not None:
|
|
135
|
+
importance = fm.importance
|
|
136
|
+
elif fm and fm.confidence_score is not None:
|
|
137
|
+
importance = fm.confidence_score
|
|
138
|
+
else:
|
|
139
|
+
importance = commit_importance
|
|
140
|
+
|
|
141
|
+
results.append(
|
|
142
|
+
RecallResult(
|
|
143
|
+
path=path,
|
|
144
|
+
content=content[:2000] + ("..." if len(content) > 2000 else ""),
|
|
145
|
+
relevance_score=float(importance) if importance else 0.5,
|
|
146
|
+
source={
|
|
147
|
+
"commit_hash": head.store(self.repo.object_store),
|
|
148
|
+
"author": head.author,
|
|
149
|
+
"indexed_at": head.timestamp,
|
|
150
|
+
},
|
|
151
|
+
importance=float(importance) if importance else None,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
collect_files(tree.entries)
|
|
156
|
+
results.sort(key=lambda r: r.relevance_score, reverse=True)
|
|
157
|
+
return results[:limit]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class SimilarityStrategy(RetrievalStrategy):
|
|
161
|
+
"""Use vector store for embedding similarity."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, repo: Any, vector_store: Any):
|
|
164
|
+
self.repo = repo
|
|
165
|
+
self.vector_store = vector_store
|
|
166
|
+
|
|
167
|
+
def recall(
|
|
168
|
+
self,
|
|
169
|
+
context: str,
|
|
170
|
+
limit: int,
|
|
171
|
+
exclude: List[str],
|
|
172
|
+
**kwargs: Any,
|
|
173
|
+
) -> List[RecallResult]:
|
|
174
|
+
"""Recall by semantic similarity to context."""
|
|
175
|
+
raw = self.vector_store.search_with_provenance(context, limit=limit * 2)
|
|
176
|
+
results = []
|
|
177
|
+
for item in raw:
|
|
178
|
+
path = item.get("path", "")
|
|
179
|
+
if _matches_exclude(path, exclude):
|
|
180
|
+
continue
|
|
181
|
+
results.append(
|
|
182
|
+
RecallResult(
|
|
183
|
+
path=path,
|
|
184
|
+
content=item.get("content", ""),
|
|
185
|
+
relevance_score=item.get("similarity", 1.0 - item.get("distance", 0)),
|
|
186
|
+
source={
|
|
187
|
+
"commit_hash": item.get("commit_hash"),
|
|
188
|
+
"author": item.get("author"),
|
|
189
|
+
"indexed_at": item.get("indexed_at"),
|
|
190
|
+
"blob_hash": item.get("blob_hash"),
|
|
191
|
+
},
|
|
192
|
+
importance=None,
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
if len(results) >= limit:
|
|
196
|
+
break
|
|
197
|
+
return results
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class HybridStrategy(RetrievalStrategy):
|
|
201
|
+
"""Weighted combo of similarity, recency, importance."""
|
|
202
|
+
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
repo: Any,
|
|
206
|
+
vector_store: Optional[Any] = None,
|
|
207
|
+
weights: Optional[dict] = None,
|
|
208
|
+
):
|
|
209
|
+
self.repo = repo
|
|
210
|
+
self.vector_store = vector_store
|
|
211
|
+
self.weights = weights or {
|
|
212
|
+
"similarity": 0.4,
|
|
213
|
+
"recency": 0.3,
|
|
214
|
+
"importance": 0.3,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
def recall(
|
|
218
|
+
self,
|
|
219
|
+
context: str,
|
|
220
|
+
limit: int,
|
|
221
|
+
exclude: List[str],
|
|
222
|
+
**kwargs: Any,
|
|
223
|
+
) -> List[RecallResult]:
|
|
224
|
+
"""Combine strategies with configurable weights."""
|
|
225
|
+
from ..core.schema import FrontmatterParser
|
|
226
|
+
|
|
227
|
+
# Collect candidates from all sources
|
|
228
|
+
path_to_result: dict = {}
|
|
229
|
+
|
|
230
|
+
# Similarity (if vector store available)
|
|
231
|
+
if self.vector_store:
|
|
232
|
+
sim = SimilarityStrategy(self.repo, self.vector_store)
|
|
233
|
+
for r in sim.recall(context, limit * 2, exclude):
|
|
234
|
+
path_to_result[r.path] = {
|
|
235
|
+
"result": r,
|
|
236
|
+
"sim_score": r.relevance_score,
|
|
237
|
+
"rec_score": 0.5,
|
|
238
|
+
"imp_score": r.importance or 0.5,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Recency and importance from current/
|
|
242
|
+
current_dir = self.repo.current_dir
|
|
243
|
+
if current_dir.exists():
|
|
244
|
+
import time
|
|
245
|
+
|
|
246
|
+
head = self.repo.get_head_commit()
|
|
247
|
+
commit_imp = head.metadata.get("importance", 0.5) if head else 0.5
|
|
248
|
+
|
|
249
|
+
for subdir in MEMORY_TYPES:
|
|
250
|
+
dir_path = current_dir / subdir
|
|
251
|
+
if not dir_path.exists():
|
|
252
|
+
continue
|
|
253
|
+
for f in dir_path.rglob("*"):
|
|
254
|
+
if not f.is_file() or f.suffix.lower() not in (".md", ".txt"):
|
|
255
|
+
continue
|
|
256
|
+
try:
|
|
257
|
+
rel_path = str(f.relative_to(current_dir))
|
|
258
|
+
except ValueError:
|
|
259
|
+
continue
|
|
260
|
+
if _matches_exclude(rel_path, exclude):
|
|
261
|
+
continue
|
|
262
|
+
try:
|
|
263
|
+
content = f.read_text(encoding="utf-8", errors="replace")
|
|
264
|
+
except Exception:
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
mtime = f.stat().st_mtime
|
|
268
|
+
rec_score = 1.0 - (time.time() - mtime) / (86400 * 30) # normalize to ~30 days
|
|
269
|
+
rec_score = max(0, min(1, rec_score))
|
|
270
|
+
|
|
271
|
+
fm, _ = FrontmatterParser.parse(content)
|
|
272
|
+
imp_score = 0.5
|
|
273
|
+
if fm and fm.importance is not None:
|
|
274
|
+
imp_score = fm.importance
|
|
275
|
+
elif fm and fm.confidence_score is not None:
|
|
276
|
+
imp_score = fm.confidence_score
|
|
277
|
+
else:
|
|
278
|
+
imp_score = commit_imp
|
|
279
|
+
|
|
280
|
+
if rel_path in path_to_result:
|
|
281
|
+
path_to_result[rel_path]["rec_score"] = rec_score
|
|
282
|
+
path_to_result[rel_path]["imp_score"] = imp_score
|
|
283
|
+
else:
|
|
284
|
+
path_to_result[rel_path] = {
|
|
285
|
+
"result": RecallResult(
|
|
286
|
+
path=rel_path,
|
|
287
|
+
content=content[:2000] + ("..." if len(content) > 2000 else ""),
|
|
288
|
+
relevance_score=0,
|
|
289
|
+
source={
|
|
290
|
+
"indexed_at": datetime.fromtimestamp(mtime).isoformat() + "Z"
|
|
291
|
+
},
|
|
292
|
+
importance=imp_score,
|
|
293
|
+
),
|
|
294
|
+
"sim_score": 0.5,
|
|
295
|
+
"rec_score": rec_score,
|
|
296
|
+
"imp_score": imp_score,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# Compute hybrid score
|
|
300
|
+
w = self.weights
|
|
301
|
+
scored = []
|
|
302
|
+
for path, data in path_to_result.items():
|
|
303
|
+
score = (
|
|
304
|
+
w.get("similarity", 0.33) * data["sim_score"]
|
|
305
|
+
+ w.get("recency", 0.33) * data["rec_score"]
|
|
306
|
+
+ w.get("importance", 0.33) * data["imp_score"]
|
|
307
|
+
)
|
|
308
|
+
r = data["result"]
|
|
309
|
+
r.relevance_score = score
|
|
310
|
+
r.importance = data.get("imp_score")
|
|
311
|
+
scored.append(r)
|
|
312
|
+
|
|
313
|
+
scored.sort(key=lambda x: x.relevance_score, reverse=True)
|
|
314
|
+
return scored[:limit]
|
memvcs/utils/__init__.py
CHANGED
memvcs/utils/helpers.py
CHANGED
|
@@ -9,42 +9,42 @@ from typing import Optional
|
|
|
9
9
|
def find_repo_root(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
10
10
|
"""
|
|
11
11
|
Find the repository root by looking for .mem directory.
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
Args:
|
|
14
14
|
start_path: Path to start searching from (default: current directory)
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
Returns:
|
|
17
17
|
Path to repository root or None if not found
|
|
18
18
|
"""
|
|
19
19
|
if start_path is None:
|
|
20
|
-
start_path = Path(
|
|
21
|
-
|
|
20
|
+
start_path = Path(".").resolve()
|
|
21
|
+
|
|
22
22
|
current = start_path
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
while current != current.parent:
|
|
25
|
-
if (current /
|
|
25
|
+
if (current / ".mem").exists():
|
|
26
26
|
return current
|
|
27
27
|
current = current.parent
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
return None
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def format_timestamp(timestamp_str: str, format_str: str =
|
|
32
|
+
def format_timestamp(timestamp_str: str, format_str: str = "%Y-%m-%d %H:%M:%S") -> str:
|
|
33
33
|
"""
|
|
34
34
|
Format an ISO timestamp string.
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
Args:
|
|
37
37
|
timestamp_str: ISO format timestamp
|
|
38
38
|
format_str: Output format string
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
Returns:
|
|
41
41
|
Formatted timestamp string
|
|
42
42
|
"""
|
|
43
43
|
try:
|
|
44
44
|
# Handle 'Z' suffix
|
|
45
|
-
if timestamp_str.endswith(
|
|
45
|
+
if timestamp_str.endswith("Z"):
|
|
46
46
|
timestamp_str = timestamp_str[:-1]
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
dt = datetime.fromisoformat(timestamp_str)
|
|
49
49
|
return dt.strftime(format_str)
|
|
50
50
|
except (ValueError, TypeError):
|
|
@@ -54,11 +54,11 @@ def format_timestamp(timestamp_str: str, format_str: str = '%Y-%m-%d %H:%M:%S')
|
|
|
54
54
|
def shorten_hash(hash_id: str, length: int = 8) -> str:
|
|
55
55
|
"""
|
|
56
56
|
Shorten a hash for display.
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
Args:
|
|
59
59
|
hash_id: Full hash string
|
|
60
60
|
length: Length of shortened hash
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
Returns:
|
|
63
63
|
Shortened hash
|
|
64
64
|
"""
|
|
@@ -70,18 +70,18 @@ def shorten_hash(hash_id: str, length: int = 8) -> str:
|
|
|
70
70
|
def human_readable_size(size_bytes: int) -> str:
|
|
71
71
|
"""
|
|
72
72
|
Convert bytes to human readable format.
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
Args:
|
|
75
75
|
size_bytes: Size in bytes
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
Returns:
|
|
78
78
|
Human readable string (e.g., "1.5 MB")
|
|
79
79
|
"""
|
|
80
80
|
if size_bytes < 1024:
|
|
81
81
|
return f"{size_bytes} B"
|
|
82
|
-
elif size_bytes < 1024
|
|
82
|
+
elif size_bytes < 1024**2:
|
|
83
83
|
return f"{size_bytes / 1024:.1f} KB"
|
|
84
|
-
elif size_bytes < 1024
|
|
84
|
+
elif size_bytes < 1024**3:
|
|
85
85
|
return f"{size_bytes / (1024 ** 2):.1f} MB"
|
|
86
86
|
else:
|
|
87
87
|
return f"{size_bytes / (1024 ** 3):.1f} GB"
|
|
@@ -90,89 +90,89 @@ def human_readable_size(size_bytes: int) -> str:
|
|
|
90
90
|
def parse_memory_type(filepath: str) -> str:
|
|
91
91
|
"""
|
|
92
92
|
Parse memory type from file path.
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
Args:
|
|
95
95
|
filepath: Path to memory file
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
Returns:
|
|
98
98
|
Memory type ('episodic', 'semantic', 'procedural', 'unknown')
|
|
99
99
|
"""
|
|
100
100
|
path_lower = filepath.lower()
|
|
101
|
-
|
|
102
|
-
if
|
|
103
|
-
return
|
|
104
|
-
elif
|
|
105
|
-
return
|
|
106
|
-
elif
|
|
107
|
-
return
|
|
108
|
-
elif
|
|
109
|
-
return
|
|
110
|
-
elif
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
return
|
|
101
|
+
|
|
102
|
+
if "episodic" in path_lower:
|
|
103
|
+
return "episodic"
|
|
104
|
+
elif "semantic" in path_lower:
|
|
105
|
+
return "semantic"
|
|
106
|
+
elif "procedural" in path_lower or "workflow" in path_lower:
|
|
107
|
+
return "procedural"
|
|
108
|
+
elif "checkpoint" in path_lower:
|
|
109
|
+
return "checkpoint"
|
|
110
|
+
elif "summary" in path_lower:
|
|
111
|
+
return "summary"
|
|
112
|
+
|
|
113
|
+
return "unknown"
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def is_binary_content(content: bytes) -> bool:
|
|
117
117
|
"""
|
|
118
118
|
Check if content appears to be binary.
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
Args:
|
|
121
121
|
content: Content bytes to check
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
Returns:
|
|
124
124
|
True if content appears binary
|
|
125
125
|
"""
|
|
126
126
|
# Check for null bytes
|
|
127
|
-
if b
|
|
127
|
+
if b"\x00" in content:
|
|
128
128
|
return True
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
# Check for high ratio of non-printable characters
|
|
131
131
|
if len(content) == 0:
|
|
132
132
|
return False
|
|
133
|
-
|
|
134
|
-
text_chars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {
|
|
133
|
+
|
|
134
|
+
text_chars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})
|
|
135
135
|
non_text = sum(1 for byte in content if byte not in text_chars)
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
return non_text / len(content) > 0.30
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
def sanitize_filename(filename: str) -> str:
|
|
141
141
|
"""
|
|
142
142
|
Sanitize a filename for safe use.
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
Args:
|
|
145
145
|
filename: Original filename
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
Returns:
|
|
148
148
|
Sanitized filename
|
|
149
149
|
"""
|
|
150
150
|
# Remove or replace unsafe characters
|
|
151
151
|
unsafe_chars = '<>:"/\\|?*'
|
|
152
152
|
for char in unsafe_chars:
|
|
153
|
-
filename = filename.replace(char,
|
|
154
|
-
|
|
153
|
+
filename = filename.replace(char, "_")
|
|
154
|
+
|
|
155
155
|
# Remove leading/trailing whitespace and dots
|
|
156
|
-
filename = filename.strip(
|
|
157
|
-
|
|
156
|
+
filename = filename.strip(" .")
|
|
157
|
+
|
|
158
158
|
# Ensure not empty
|
|
159
159
|
if not filename:
|
|
160
|
-
filename =
|
|
161
|
-
|
|
160
|
+
filename = "unnamed"
|
|
161
|
+
|
|
162
162
|
return filename
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
def generate_session_id() -> str:
|
|
166
166
|
"""
|
|
167
167
|
Generate a unique session ID.
|
|
168
|
-
|
|
168
|
+
|
|
169
169
|
Returns:
|
|
170
170
|
Session ID string
|
|
171
171
|
"""
|
|
172
172
|
from datetime import datetime
|
|
173
173
|
import uuid
|
|
174
|
-
|
|
175
|
-
timestamp = datetime.utcnow().strftime(
|
|
174
|
+
|
|
175
|
+
timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
|
176
176
|
short_uuid = str(uuid.uuid4())[:8]
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
return f"session-{timestamp}-{short_uuid}"
|
agmem-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
agmem-0.1.1.dist-info/licenses/LICENSE,sha256=X_S6RBErW-F0IDbM3FAEoDB-zxExFnl2m8640rTXphM,1067
|
|
2
|
-
memvcs/__init__.py,sha256=Gs0A8GAivdcePvlse8yWE3t6-vutFO9tWl5dh8lun6I,193
|
|
3
|
-
memvcs/cli.py,sha256=ImQKXb423bHZ2iPr3GndHVojx2KcTuX8rXHwJV1cku0,5364
|
|
4
|
-
memvcs/commands/__init__.py,sha256=lPKiWp-ywEGk1JPK0DeiHSublDwwSD1pU_LyPT-2BhY,510
|
|
5
|
-
memvcs/commands/add.py,sha256=n0J0N_5Mk4uNMtsEt2podLqSKeenaOBRNWMUb9gsq70,8874
|
|
6
|
-
memvcs/commands/base.py,sha256=yWvIYuofRxbHXvChlSd_DL_hJMaQdbZwa2XBDWj5Bio,634
|
|
7
|
-
memvcs/commands/blame.py,sha256=NbaaL3kt9K2zRvYWX6mh-NmXNt5ZOdacSzHDmGY0_Oo,5874
|
|
8
|
-
memvcs/commands/branch.py,sha256=7ocWZVn-WJ1c9pR9Z493ytd_VE6CY6Lakgl5lIzoNeE,3418
|
|
9
|
-
memvcs/commands/checkout.py,sha256=8-fVF9HC5b0dkyO1u0wkGLUwGXrRBc8eSIDD9XBcnjI,3310
|
|
10
|
-
memvcs/commands/clean.py,sha256=SXQqnd31KwmuIFUc2tBVm8ZjZ_3bP2mhbOR9cPBdbIs,2090
|
|
11
|
-
memvcs/commands/clone.py,sha256=YYYURUiW850CHDgctlM4_ysQlPeh35BrS-UG2LSwWZ0,2872
|
|
12
|
-
memvcs/commands/commit.py,sha256=SmeU0MDIMSYQe9PHNZzOLEK1duvflqWdRMrQXkQ6zGM,6380
|
|
13
|
-
memvcs/commands/daemon.py,sha256=nTaWx19MN1IR4u293yna8fuwhHRiYstykSq_3zoUFPc,8385
|
|
14
|
-
memvcs/commands/diff.py,sha256=TxfeqQS84rG2wHRff_WxdwjsNBKRnMkkX4B-7rsVG9Q,5410
|
|
15
|
-
memvcs/commands/fsck.py,sha256=kvJYO0Kr073qZwGtvVqL1c7n7E34QuNzSc-HG3GpWZQ,6890
|
|
16
|
-
memvcs/commands/garden.py,sha256=P8rvC6xYu6q_8VfXEUvtOa2Wez3jHLGGArGeAbn_o4Y,3496
|
|
17
|
-
memvcs/commands/graph.py,sha256=caPyoo-myqmvtg_HEBUGBgQEnNFQXnCIkiNte4zQhqs,4993
|
|
18
|
-
memvcs/commands/init.py,sha256=2TfBniqqJ6PsyHSIgBmkdtpF-kU9UCsc-V4Ct4BSumM,1767
|
|
19
|
-
memvcs/commands/log.py,sha256=tY0Hrn2xkC1SWG_DT2VGbdK-W-oajrM_1r9NVLoEq5k,3141
|
|
20
|
-
memvcs/commands/mcp.py,sha256=PMfwVD6uHltN58Jh7IOiS1w7oND42tg14QKRCJNudmY,1740
|
|
21
|
-
memvcs/commands/merge.py,sha256=qUsJelBynpdv63tL9Oga3t29AnUQCMMJo_CNl3rNvaw,2631
|
|
22
|
-
memvcs/commands/pull.py,sha256=Sk0zfCloyksYbMGCSM9z91pH1gZJ-KLKZasckQhrNd4,2082
|
|
23
|
-
memvcs/commands/push.py,sha256=bCHuxrrBwRNhVDh9GVwhRICtaNLk_a_47EHix4Z66BQ,5081
|
|
24
|
-
memvcs/commands/reflog.py,sha256=AtfJ2dzhsgJVvdQlHU5LUEiAW310QI8yWeUD7GmLoco,1256
|
|
25
|
-
memvcs/commands/remote.py,sha256=sTXf-r6w4OsZgB68ffW9jUtP1xOL8p-kttxvApZNs08,1760
|
|
26
|
-
memvcs/commands/reset.py,sha256=vvHgSb79zVJizWyH-XN6u5jHK2e_gqrNI0JoIUi22es,2991
|
|
27
|
-
memvcs/commands/search.py,sha256=rg-cQfUDl6aNRFa64szpPbqzw508sL9a2u8YtHBhSRM,5324
|
|
28
|
-
memvcs/commands/serve.py,sha256=mhfsULAtU5Gxe43RVo94RRYT1ui4Zmg5Ptatj5rNIUc,1396
|
|
29
|
-
memvcs/commands/show.py,sha256=525-T_na_B3gX6lmFJ05Q4675XNyEd7FFrVl63X2X7Q,3932
|
|
30
|
-
memvcs/commands/stash.py,sha256=aLeeOLUXcFHQYzsYsi9KIg5e_8VFANHMAEvzaqTOgsg,3293
|
|
31
|
-
memvcs/commands/status.py,sha256=P02DFM2-gIf8-dkslzpnj6xNmpxhqmf8bOhHkxA3srw,3647
|
|
32
|
-
memvcs/commands/tag.py,sha256=UCTggFnzHXVMBYjX8usz58duqg4xyOHnIUdVNyoAlWQ,3409
|
|
33
|
-
memvcs/commands/test.py,sha256=xAIzN60U1xL1TieIXOQBsvTRRfZfElVqxEqqHUn72VU,4235
|
|
34
|
-
memvcs/commands/tree.py,sha256=GzA7RYH3bYgSg9QmztYpis6EZWEt6fzwBltoQjaukf4,5059
|
|
35
|
-
memvcs/core/__init__.py,sha256=dkIC-4tS0GhwV2mZIbofEe8xR8uiFwrxslGf1aXwhYg,493
|
|
36
|
-
memvcs/core/config_loader.py,sha256=ebljEMDyVJjQdlpP8if10Whg0I3DfMUBiuAl7S5NVNE,8264
|
|
37
|
-
memvcs/core/constants.py,sha256=WUjAb50BFcF0mbFi_GNteDLCxLihmViBm9Fb-JMPmbM,220
|
|
38
|
-
memvcs/core/diff.py,sha256=wZynRRAF08HyQ_SCcseC0vlmnkoaqoQfQWne3w1o__M,13390
|
|
39
|
-
memvcs/core/gardener.py,sha256=Fr_Zpo_3eZAtA_9zXVT_PpAHfhZkp2-ukLn69FV93rA,16335
|
|
40
|
-
memvcs/core/hooks.py,sha256=3PWqebAt2c6nPw7ZX3epcb5FGLJLrwxh_UMrQeFTyr0,4679
|
|
41
|
-
memvcs/core/knowledge_graph.py,sha256=Ntm4My73Ov3u9YKEgwvDonUVwewx-svCDJD4hGA0hPc,13572
|
|
42
|
-
memvcs/core/merge.py,sha256=i_X-0ye14r62dvlxRWZVkRbIvxd_MtfQSYwuZQEYaCA,17041
|
|
43
|
-
memvcs/core/objects.py,sha256=MvWhQ-L74Rl2lf0rnJE-N3qo78dWGoclJSQinTloNPg,10289
|
|
44
|
-
memvcs/core/pii_scanner.py,sha256=MOycqGTuLJaCNGkaMyMH1ju8gPxQTyLAONoIML7gHKU,11141
|
|
45
|
-
memvcs/core/refs.py,sha256=1-ELwSXWmqdivD_967zb3hw5V3gIiujyn4eRGf8e1u8,16831
|
|
46
|
-
memvcs/core/remote.py,sha256=MhQTfxpzmH0mAMb7hoQJrTOAoqX0tqZxx1Yq5Q5niS8,10117
|
|
47
|
-
memvcs/core/repository.py,sha256=fZ-UUh9EK9nmM8dk9y4TeipkhgGEp5JqDizR6QAEVe4,18119
|
|
48
|
-
memvcs/core/schema.py,sha256=oiStwqD1crbQJf86yJTMI50efd2SaDG3RIPyUxmcxEk,14322
|
|
49
|
-
memvcs/core/staging.py,sha256=EvACqotgi-LKgOdZr6-8Ud1Lkjnflam0KAzpq8eAAp0,7499
|
|
50
|
-
memvcs/core/test_runner.py,sha256=QfEPMjBebM4L4COn9p0XqMcq1Grvij5rOXByUWAcEAg,11823
|
|
51
|
-
memvcs/core/vector_store.py,sha256=Z_C1VZy3Ytv9dfEaqgE6zCb8nNabLaIsDcc1ATstDRI,10546
|
|
52
|
-
memvcs/core/storage/__init__.py,sha256=b2MLjyFgE6L6JAFoyEldxsrR16FGO65qAtxgRj5XFcM,1959
|
|
53
|
-
memvcs/core/storage/base.py,sha256=hHPXuJPR5X1bh7xYAqACHYRfzU4CmPx9trT5tgRghdI,10384
|
|
54
|
-
memvcs/core/storage/gcs.py,sha256=mjcuQHiK6goQ3opRDE6Ky2fSEA9W-MNTOShlGXhY0cM,10953
|
|
55
|
-
memvcs/core/storage/local.py,sha256=CHi3Ot0-u-pCJJOWKW3IP_UIkdz32QAkHcJl1DxatHY,6193
|
|
56
|
-
memvcs/core/storage/s3.py,sha256=rPxZ7avw5KXMSrJrsA2YpM6gVv7Qhn9IQjfoApZIyxE,14230
|
|
57
|
-
memvcs/integrations/__init__.py,sha256=hVtJoFaXt6ErAZwctcSBDZLXRHFs1CNgtltIBQiroQ0,103
|
|
58
|
-
memvcs/integrations/mcp_server.py,sha256=-si5ymayhiRTIfNJwc4EEp4kudav28x8RjzIEDN-n2c,9091
|
|
59
|
-
memvcs/integrations/web_ui/__init__.py,sha256=MQIfgDKDgPctlcTUjwkwueS_MDsDssVRmIUnpECGS0k,51
|
|
60
|
-
memvcs/integrations/web_ui/server.py,sha256=yqokrs6T9l4vAWdRHKMSFLUIkodTJFWihw5Aq_dhGPg,12782
|
|
61
|
-
memvcs/utils/__init__.py,sha256=h74jw3O37su7BFxp52IcZmVReLfd8yDcuk4VzBHbd_s,185
|
|
62
|
-
memvcs/utils/helpers.py,sha256=4k7Jdm-8vCWDASW2kST2vZs-vA9HX8UvqoCwIGsq49g,4273
|
|
63
|
-
agmem-0.1.1.dist-info/METADATA,sha256=8ovwtZcCCNfSeQA6IH5HfAY0MzQgeqxBo1BfRZIkLIU,25844
|
|
64
|
-
agmem-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
65
|
-
agmem-0.1.1.dist-info/entry_points.txt,sha256=at7eWycgjqOo1wbUMECnXUsNo3gpCkJTU71OzrGLHu0,42
|
|
66
|
-
agmem-0.1.1.dist-info/top_level.txt,sha256=HtMMsKuwLKLOdgF1GxqQztqFM54tTJctVdJuOec6B-4,7
|
|
67
|
-
agmem-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|