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.
Files changed (100) hide show
  1. {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/METADATA +157 -16
  2. agmem-0.1.3.dist-info/RECORD +105 -0
  3. memvcs/__init__.py +1 -1
  4. memvcs/cli.py +45 -31
  5. memvcs/commands/__init__.py +9 -9
  6. memvcs/commands/add.py +83 -76
  7. memvcs/commands/audit.py +59 -0
  8. memvcs/commands/blame.py +46 -53
  9. memvcs/commands/branch.py +13 -33
  10. memvcs/commands/checkout.py +27 -32
  11. memvcs/commands/clean.py +18 -23
  12. memvcs/commands/clone.py +11 -1
  13. memvcs/commands/commit.py +40 -39
  14. memvcs/commands/daemon.py +109 -76
  15. memvcs/commands/decay.py +77 -0
  16. memvcs/commands/diff.py +56 -57
  17. memvcs/commands/distill.py +90 -0
  18. memvcs/commands/federated.py +53 -0
  19. memvcs/commands/fsck.py +86 -61
  20. memvcs/commands/garden.py +40 -35
  21. memvcs/commands/gc.py +51 -0
  22. memvcs/commands/graph.py +41 -48
  23. memvcs/commands/init.py +16 -24
  24. memvcs/commands/log.py +25 -40
  25. memvcs/commands/merge.py +69 -27
  26. memvcs/commands/pack.py +129 -0
  27. memvcs/commands/prove.py +66 -0
  28. memvcs/commands/pull.py +31 -1
  29. memvcs/commands/push.py +4 -2
  30. memvcs/commands/recall.py +145 -0
  31. memvcs/commands/reflog.py +13 -22
  32. memvcs/commands/remote.py +1 -0
  33. memvcs/commands/repair.py +66 -0
  34. memvcs/commands/reset.py +23 -33
  35. memvcs/commands/resolve.py +130 -0
  36. memvcs/commands/resurrect.py +82 -0
  37. memvcs/commands/search.py +3 -4
  38. memvcs/commands/serve.py +2 -1
  39. memvcs/commands/show.py +66 -36
  40. memvcs/commands/stash.py +34 -34
  41. memvcs/commands/status.py +27 -35
  42. memvcs/commands/tag.py +23 -47
  43. memvcs/commands/test.py +30 -44
  44. memvcs/commands/timeline.py +111 -0
  45. memvcs/commands/tree.py +26 -27
  46. memvcs/commands/verify.py +110 -0
  47. memvcs/commands/when.py +115 -0
  48. memvcs/core/access_index.py +167 -0
  49. memvcs/core/audit.py +124 -0
  50. memvcs/core/config_loader.py +3 -1
  51. memvcs/core/consistency.py +214 -0
  52. memvcs/core/crypto_verify.py +280 -0
  53. memvcs/core/decay.py +185 -0
  54. memvcs/core/diff.py +158 -143
  55. memvcs/core/distiller.py +277 -0
  56. memvcs/core/encryption.py +169 -0
  57. memvcs/core/federated.py +86 -0
  58. memvcs/core/gardener.py +176 -145
  59. memvcs/core/hooks.py +48 -14
  60. memvcs/core/ipfs_remote.py +39 -0
  61. memvcs/core/knowledge_graph.py +135 -138
  62. memvcs/core/llm/__init__.py +10 -0
  63. memvcs/core/llm/anthropic_provider.py +50 -0
  64. memvcs/core/llm/base.py +27 -0
  65. memvcs/core/llm/factory.py +30 -0
  66. memvcs/core/llm/openai_provider.py +36 -0
  67. memvcs/core/merge.py +260 -170
  68. memvcs/core/objects.py +110 -101
  69. memvcs/core/pack.py +92 -0
  70. memvcs/core/pii_scanner.py +147 -146
  71. memvcs/core/privacy_budget.py +63 -0
  72. memvcs/core/refs.py +132 -115
  73. memvcs/core/remote.py +38 -0
  74. memvcs/core/repository.py +254 -164
  75. memvcs/core/schema.py +155 -113
  76. memvcs/core/staging.py +60 -65
  77. memvcs/core/storage/__init__.py +20 -18
  78. memvcs/core/storage/base.py +74 -70
  79. memvcs/core/storage/gcs.py +70 -68
  80. memvcs/core/storage/local.py +42 -40
  81. memvcs/core/storage/s3.py +105 -110
  82. memvcs/core/temporal_index.py +121 -0
  83. memvcs/core/test_runner.py +101 -93
  84. memvcs/core/trust.py +103 -0
  85. memvcs/core/vector_store.py +56 -36
  86. memvcs/core/zk_proofs.py +26 -0
  87. memvcs/integrations/mcp_server.py +1 -3
  88. memvcs/integrations/web_ui/server.py +25 -26
  89. memvcs/retrieval/__init__.py +22 -0
  90. memvcs/retrieval/base.py +54 -0
  91. memvcs/retrieval/pack.py +128 -0
  92. memvcs/retrieval/recaller.py +105 -0
  93. memvcs/retrieval/strategies.py +314 -0
  94. memvcs/utils/__init__.py +3 -3
  95. memvcs/utils/helpers.py +52 -52
  96. agmem-0.1.1.dist-info/RECORD +0 -67
  97. {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/WHEEL +0 -0
  98. {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/entry_points.txt +0 -0
  99. {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/licenses/LICENSE +0 -0
  100. {agmem-0.1.1.dist-info → agmem-0.1.3.dist-info}/top_level.txt +0 -0
memvcs/core/diff.py CHANGED
@@ -14,6 +14,7 @@ from .objects import ObjectStore, Commit, Tree, Blob
14
14
 
15
15
  class DiffType(Enum):
16
16
  """Types of differences."""
17
+
17
18
  ADDED = "added"
18
19
  DELETED = "deleted"
19
20
  MODIFIED = "modified"
@@ -23,6 +24,7 @@ class DiffType(Enum):
23
24
  @dataclass
24
25
  class FileDiff:
25
26
  """Difference for a single file."""
27
+
26
28
  path: str
27
29
  diff_type: DiffType
28
30
  old_hash: Optional[str]
@@ -35,6 +37,7 @@ class FileDiff:
35
37
  @dataclass
36
38
  class TreeDiff:
37
39
  """Difference between two trees."""
40
+
38
41
  files: List[FileDiff]
39
42
  added_count: int
40
43
  deleted_count: int
@@ -43,67 +46,67 @@ class TreeDiff:
43
46
 
44
47
  class DiffEngine:
45
48
  """Engine for computing differences."""
46
-
49
+
47
50
  def __init__(self, object_store: ObjectStore):
48
51
  self.object_store = object_store
49
-
52
+
50
53
  def get_tree_files(self, tree_hash: str) -> Dict[str, str]:
51
54
  """Get all files in a tree with their blob hashes."""
52
55
  files = {}
53
56
  tree = Tree.load(self.object_store, tree_hash)
54
-
57
+
55
58
  if tree:
56
59
  for entry in tree.entries:
57
- path = entry.path + '/' + entry.name if entry.path else entry.name
60
+ path = entry.path + "/" + entry.name if entry.path else entry.name
58
61
  files[path] = entry.hash
59
-
62
+
60
63
  return files
61
-
64
+
62
65
  def get_blob_content(self, hash_id: Optional[str]) -> Optional[str]:
63
66
  """Get blob content as string."""
64
67
  if not hash_id:
65
68
  return None
66
-
69
+
67
70
  blob = Blob.load(self.object_store, hash_id)
68
71
  if blob:
69
- return blob.content.decode('utf-8', errors='replace')
72
+ return blob.content.decode("utf-8", errors="replace")
70
73
  return None
71
-
72
- def compute_line_diff(self, old_content: Optional[str], new_content: Optional[str]) -> List[str]:
74
+
75
+ def compute_line_diff(
76
+ self, old_content: Optional[str], new_content: Optional[str]
77
+ ) -> List[str]:
73
78
  """
74
79
  Compute line-by-line diff between two contents.
75
-
80
+
76
81
  Returns:
77
82
  List of diff lines with +/- prefixes
78
83
  """
79
- old_lines = (old_content or '').splitlines(keepends=True)
80
- new_lines = (new_content or '').splitlines(keepends=True)
81
-
84
+ old_lines = (old_content or "").splitlines(keepends=True)
85
+ new_lines = (new_content or "").splitlines(keepends=True)
86
+
82
87
  # Simple diff algorithm (LCS-based would be better)
83
88
  diff_lines = []
84
-
89
+
85
90
  # Handle empty cases
86
- if not old_lines or old_lines == ['']:
91
+ if not old_lines or old_lines == [""]:
87
92
  for line in new_lines:
88
- diff_lines.append(f'+ {line.rstrip()}')
93
+ diff_lines.append(f"+ {line.rstrip()}")
89
94
  return diff_lines
90
-
91
- if not new_lines or new_lines == ['']:
95
+
96
+ if not new_lines or new_lines == [""]:
92
97
  for line in old_lines:
93
- diff_lines.append(f'- {line.rstrip()}')
98
+ diff_lines.append(f"- {line.rstrip()}")
94
99
  return diff_lines
95
-
100
+
96
101
  # Use unified diff style
97
- max_lines = max(len(old_lines), len(new_lines))
98
-
99
102
  i, j = 0, 0
100
103
  while i < len(old_lines) or j < len(new_lines):
101
104
  if i < len(old_lines) and j < len(new_lines):
102
105
  old_line = old_lines[i].rstrip()
103
106
  new_line = new_lines[j].rstrip()
104
-
107
+
105
108
  if old_line == new_line:
106
- diff_lines.append(f' {old_line}')
109
+ diff_lines.append(f" {old_line}")
107
110
  i += 1
108
111
  j += 1
109
112
  else:
@@ -112,153 +115,158 @@ class DiffEngine:
112
115
  for k in range(j + 1, min(j + 5, len(new_lines))):
113
116
  if new_lines[k].rstrip() == old_line:
114
117
  # Lines were added
115
- for l in range(j, k):
116
- diff_lines.append(f'+ {new_lines[l].rstrip()}')
118
+ for idx in range(j, k):
119
+ diff_lines.append(f"+ {new_lines[idx].rstrip()}")
117
120
  j = k
118
121
  found = True
119
122
  break
120
-
123
+
121
124
  if not found:
122
125
  # Line was removed
123
- diff_lines.append(f'- {old_line}')
126
+ diff_lines.append(f"- {old_line}")
124
127
  i += 1
125
128
  elif i < len(old_lines):
126
- diff_lines.append(f'- {old_lines[i].rstrip()}')
129
+ diff_lines.append(f"- {old_lines[i].rstrip()}")
127
130
  i += 1
128
131
  else:
129
- diff_lines.append(f'+ {new_lines[j].rstrip()}')
132
+ diff_lines.append(f"+ {new_lines[j].rstrip()}")
130
133
  j += 1
131
-
134
+
132
135
  return diff_lines
133
-
136
+
134
137
  def diff_trees(self, old_tree_hash: Optional[str], new_tree_hash: Optional[str]) -> TreeDiff:
135
138
  """
136
139
  Compute diff between two trees.
137
-
140
+
138
141
  Args:
139
142
  old_tree_hash: Hash of old tree (None for empty)
140
143
  new_tree_hash: Hash of new tree (None for empty)
141
-
144
+
142
145
  Returns:
143
146
  TreeDiff with file differences
144
147
  """
145
148
  old_files = self.get_tree_files(old_tree_hash) if old_tree_hash else {}
146
149
  new_files = self.get_tree_files(new_tree_hash) if new_tree_hash else {}
147
-
150
+
148
151
  all_paths = set(old_files.keys()) | set(new_files.keys())
149
-
152
+
150
153
  file_diffs = []
151
154
  added = 0
152
155
  deleted = 0
153
156
  modified = 0
154
-
157
+
155
158
  for path in sorted(all_paths):
156
159
  old_hash = old_files.get(path)
157
160
  new_hash = new_files.get(path)
158
-
161
+
159
162
  if not old_hash and new_hash:
160
163
  # Added
161
164
  new_content = self.get_blob_content(new_hash)
162
165
  diff_lines = self.compute_line_diff(None, new_content)
163
-
164
- file_diffs.append(FileDiff(
165
- path=path,
166
- diff_type=DiffType.ADDED,
167
- old_hash=None,
168
- new_hash=new_hash,
169
- old_content=None,
170
- new_content=new_content,
171
- diff_lines=diff_lines
172
- ))
166
+
167
+ file_diffs.append(
168
+ FileDiff(
169
+ path=path,
170
+ diff_type=DiffType.ADDED,
171
+ old_hash=None,
172
+ new_hash=new_hash,
173
+ old_content=None,
174
+ new_content=new_content,
175
+ diff_lines=diff_lines,
176
+ )
177
+ )
173
178
  added += 1
174
-
179
+
175
180
  elif old_hash and not new_hash:
176
181
  # Deleted
177
182
  old_content = self.get_blob_content(old_hash)
178
183
  diff_lines = self.compute_line_diff(old_content, None)
179
-
180
- file_diffs.append(FileDiff(
181
- path=path,
182
- diff_type=DiffType.DELETED,
183
- old_hash=old_hash,
184
- new_hash=None,
185
- old_content=old_content,
186
- new_content=None,
187
- diff_lines=diff_lines
188
- ))
184
+
185
+ file_diffs.append(
186
+ FileDiff(
187
+ path=path,
188
+ diff_type=DiffType.DELETED,
189
+ old_hash=old_hash,
190
+ new_hash=None,
191
+ old_content=old_content,
192
+ new_content=None,
193
+ diff_lines=diff_lines,
194
+ )
195
+ )
189
196
  deleted += 1
190
-
197
+
191
198
  elif old_hash != new_hash:
192
199
  # Modified
193
200
  old_content = self.get_blob_content(old_hash)
194
201
  new_content = self.get_blob_content(new_hash)
195
202
  diff_lines = self.compute_line_diff(old_content, new_content)
196
-
197
- file_diffs.append(FileDiff(
198
- path=path,
199
- diff_type=DiffType.MODIFIED,
200
- old_hash=old_hash,
201
- new_hash=new_hash,
202
- old_content=old_content,
203
- new_content=new_content,
204
- diff_lines=diff_lines
205
- ))
203
+
204
+ file_diffs.append(
205
+ FileDiff(
206
+ path=path,
207
+ diff_type=DiffType.MODIFIED,
208
+ old_hash=old_hash,
209
+ new_hash=new_hash,
210
+ old_content=old_content,
211
+ new_content=new_content,
212
+ diff_lines=diff_lines,
213
+ )
214
+ )
206
215
  modified += 1
207
-
216
+
208
217
  return TreeDiff(
209
- files=file_diffs,
210
- added_count=added,
211
- deleted_count=deleted,
212
- modified_count=modified
218
+ files=file_diffs, added_count=added, deleted_count=deleted, modified_count=modified
213
219
  )
214
-
215
- def diff_commits(self, old_commit_hash: Optional[str], new_commit_hash: Optional[str]) -> TreeDiff:
220
+
221
+ def diff_commits(
222
+ self, old_commit_hash: Optional[str], new_commit_hash: Optional[str]
223
+ ) -> TreeDiff:
216
224
  """
217
225
  Compute diff between two commits.
218
-
226
+
219
227
  Args:
220
228
  old_commit_hash: Hash of old commit (None for empty)
221
229
  new_commit_hash: Hash of new commit (None for empty)
222
-
230
+
223
231
  Returns:
224
232
  TreeDiff with file differences
225
233
  """
226
234
  old_tree_hash = None
227
235
  new_tree_hash = None
228
-
236
+
229
237
  if old_commit_hash:
230
238
  old_commit = Commit.load(self.object_store, old_commit_hash)
231
239
  if old_commit:
232
240
  old_tree_hash = old_commit.tree
233
-
241
+
234
242
  if new_commit_hash:
235
243
  new_commit = Commit.load(self.object_store, new_commit_hash)
236
244
  if new_commit:
237
245
  new_tree_hash = new_commit.tree
238
-
246
+
239
247
  return self.diff_trees(old_tree_hash, new_tree_hash)
240
-
241
- def format_diff(self, tree_diff: TreeDiff, old_ref: str = 'a', new_ref: str = 'b') -> str:
248
+
249
+ def format_diff(self, tree_diff: TreeDiff, old_ref: str = "a", new_ref: str = "b") -> str:
242
250
  """
243
251
  Format tree diff as unified diff string.
244
-
252
+
245
253
  Args:
246
254
  tree_diff: TreeDiff to format
247
255
  old_ref: Label for old version
248
256
  new_ref: Label for new version
249
-
257
+
250
258
  Returns:
251
259
  Formatted diff string
252
260
  """
253
261
  lines = []
254
-
262
+
255
263
  for file_diff in tree_diff.files:
256
264
  if file_diff.diff_type == DiffType.UNCHANGED:
257
265
  continue
258
-
266
+
259
267
  # File header
260
268
  lines.append(f"diff --agmem {old_ref}/{file_diff.path} {new_ref}/{file_diff.path}")
261
-
269
+
262
270
  if file_diff.diff_type == DiffType.ADDED:
263
271
  lines.append(f"new file mode 100644")
264
272
  lines.append(f"index 0000000..{file_diff.new_hash[:7]}")
@@ -273,29 +281,29 @@ class DiffEngine:
273
281
  lines.append(f"index {file_diff.old_hash[:7]}..{file_diff.new_hash[:7]}")
274
282
  lines.append(f"--- {old_ref}/{file_diff.path}")
275
283
  lines.append(f"+++ {new_ref}/{file_diff.path}")
276
-
284
+
277
285
  # Diff content
278
286
  lines.append("@@ -1 +1 @@")
279
287
  for diff_line in file_diff.diff_lines:
280
288
  lines.append(diff_line)
281
-
289
+
282
290
  lines.append("") # Empty line between files
283
-
291
+
284
292
  # Summary
285
293
  lines.append(f"# {tree_diff.added_count} file(s) added")
286
294
  lines.append(f"# {tree_diff.deleted_count} file(s) deleted")
287
295
  lines.append(f"# {tree_diff.modified_count} file(s) modified")
288
-
289
- return '\n'.join(lines)
290
-
296
+
297
+ return "\n".join(lines)
298
+
291
299
  def diff_working_dir(self, commit_hash: str, working_files: Dict[str, bytes]) -> TreeDiff:
292
300
  """
293
301
  Compute diff between a commit and working directory.
294
-
302
+
295
303
  Args:
296
304
  commit_hash: Commit to compare against
297
305
  working_files: Dict mapping paths to file contents
298
-
306
+
299
307
  Returns:
300
308
  TreeDiff with differences
301
309
  """
@@ -303,78 +311,85 @@ class DiffEngine:
303
311
  commit = Commit.load(self.object_store, commit_hash)
304
312
  if not commit:
305
313
  return TreeDiff(files=[], added_count=0, deleted_count=0, modified_count=0)
306
-
314
+
307
315
  commit_files = self.get_tree_files(commit.tree)
308
-
316
+
309
317
  file_diffs = []
310
318
  added = 0
311
319
  deleted = 0
312
320
  modified = 0
313
-
321
+
314
322
  all_paths = set(commit_files.keys()) | set(working_files.keys())
315
-
323
+
316
324
  for path in sorted(all_paths):
317
325
  commit_hash_id = commit_files.get(path)
318
326
  working_content = working_files.get(path)
319
-
327
+
320
328
  # Compute working file hash
321
329
  working_hash = None
322
330
  if working_content is not None:
323
331
  blob = Blob(content=working_content)
324
332
  working_hash = blob.store(self.object_store)
325
-
333
+
326
334
  if not commit_hash_id and working_hash:
327
335
  # Added
328
- new_content = working_content.decode('utf-8', errors='replace') if working_content else None
336
+ new_content = (
337
+ working_content.decode("utf-8", errors="replace") if working_content else None
338
+ )
329
339
  diff_lines = self.compute_line_diff(None, new_content)
330
-
331
- file_diffs.append(FileDiff(
332
- path=path,
333
- diff_type=DiffType.ADDED,
334
- old_hash=None,
335
- new_hash=working_hash,
336
- old_content=None,
337
- new_content=new_content,
338
- diff_lines=diff_lines
339
- ))
340
+
341
+ file_diffs.append(
342
+ FileDiff(
343
+ path=path,
344
+ diff_type=DiffType.ADDED,
345
+ old_hash=None,
346
+ new_hash=working_hash,
347
+ old_content=None,
348
+ new_content=new_content,
349
+ diff_lines=diff_lines,
350
+ )
351
+ )
340
352
  added += 1
341
-
353
+
342
354
  elif commit_hash_id and not working_hash:
343
355
  # Deleted
344
356
  old_content = self.get_blob_content(commit_hash_id)
345
357
  diff_lines = self.compute_line_diff(old_content, None)
346
-
347
- file_diffs.append(FileDiff(
348
- path=path,
349
- diff_type=DiffType.DELETED,
350
- old_hash=commit_hash_id,
351
- new_hash=None,
352
- old_content=old_content,
353
- new_content=None,
354
- diff_lines=diff_lines
355
- ))
358
+
359
+ file_diffs.append(
360
+ FileDiff(
361
+ path=path,
362
+ diff_type=DiffType.DELETED,
363
+ old_hash=commit_hash_id,
364
+ new_hash=None,
365
+ old_content=old_content,
366
+ new_content=None,
367
+ diff_lines=diff_lines,
368
+ )
369
+ )
356
370
  deleted += 1
357
-
371
+
358
372
  elif commit_hash_id != working_hash:
359
373
  # Modified
360
374
  old_content = self.get_blob_content(commit_hash_id)
361
- new_content = working_content.decode('utf-8', errors='replace') if working_content else None
375
+ new_content = (
376
+ working_content.decode("utf-8", errors="replace") if working_content else None
377
+ )
362
378
  diff_lines = self.compute_line_diff(old_content, new_content)
363
-
364
- file_diffs.append(FileDiff(
365
- path=path,
366
- diff_type=DiffType.MODIFIED,
367
- old_hash=commit_hash_id,
368
- new_hash=working_hash,
369
- old_content=old_content,
370
- new_content=new_content,
371
- diff_lines=diff_lines
372
- ))
379
+
380
+ file_diffs.append(
381
+ FileDiff(
382
+ path=path,
383
+ diff_type=DiffType.MODIFIED,
384
+ old_hash=commit_hash_id,
385
+ new_hash=working_hash,
386
+ old_content=old_content,
387
+ new_content=new_content,
388
+ diff_lines=diff_lines,
389
+ )
390
+ )
373
391
  modified += 1
374
-
392
+
375
393
  return TreeDiff(
376
- files=file_diffs,
377
- added_count=added,
378
- deleted_count=deleted,
379
- modified_count=modified
394
+ files=file_diffs, added_count=added, deleted_count=deleted, modified_count=modified
380
395
  )