agmem 0.1.1__py3-none-any.whl → 0.1.2__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.2.dist-info}/METADATA +20 -3
- agmem-0.1.2.dist-info/RECORD +86 -0
- memvcs/__init__.py +1 -1
- memvcs/cli.py +35 -31
- memvcs/commands/__init__.py +9 -9
- memvcs/commands/add.py +77 -76
- 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 +4 -1
- memvcs/commands/commit.py +40 -39
- memvcs/commands/daemon.py +81 -76
- memvcs/commands/decay.py +77 -0
- memvcs/commands/diff.py +56 -57
- memvcs/commands/distill.py +74 -0
- memvcs/commands/fsck.py +55 -61
- memvcs/commands/garden.py +28 -37
- memvcs/commands/graph.py +41 -48
- memvcs/commands/init.py +16 -24
- memvcs/commands/log.py +25 -40
- memvcs/commands/merge.py +16 -28
- memvcs/commands/pack.py +129 -0
- memvcs/commands/pull.py +4 -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/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 +59 -0
- memvcs/commands/when.py +115 -0
- memvcs/core/access_index.py +167 -0
- memvcs/core/config_loader.py +3 -1
- memvcs/core/consistency.py +214 -0
- memvcs/core/decay.py +185 -0
- memvcs/core/diff.py +158 -143
- memvcs/core/distiller.py +277 -0
- memvcs/core/gardener.py +164 -132
- memvcs/core/hooks.py +48 -14
- memvcs/core/knowledge_graph.py +134 -138
- memvcs/core/merge.py +248 -171
- memvcs/core/objects.py +95 -96
- memvcs/core/pii_scanner.py +147 -146
- memvcs/core/refs.py +132 -115
- memvcs/core/repository.py +174 -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 +112 -0
- memvcs/core/test_runner.py +101 -93
- memvcs/core/vector_store.py +41 -35
- 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.2.dist-info}/WHEEL +0 -0
- {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/entry_points.txt +0 -0
- {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/top_level.txt +0 -0
memvcs/commands/fsck.py
CHANGED
|
@@ -10,44 +10,39 @@ from ..commands.base import require_repo
|
|
|
10
10
|
|
|
11
11
|
class FsckCommand:
|
|
12
12
|
"""Check and repair repository consistency."""
|
|
13
|
-
|
|
14
|
-
name =
|
|
15
|
-
help =
|
|
16
|
-
|
|
13
|
+
|
|
14
|
+
name = "fsck"
|
|
15
|
+
help = "Check and repair repository consistency (remove dangling vectors)"
|
|
16
|
+
|
|
17
17
|
@staticmethod
|
|
18
18
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
19
19
|
parser.add_argument(
|
|
20
|
-
|
|
21
|
-
action='store_true',
|
|
22
|
-
help='Show what would be done without making changes'
|
|
20
|
+
"--dry-run", action="store_true", help="Show what would be done without making changes"
|
|
23
21
|
)
|
|
22
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
|
|
24
23
|
parser.add_argument(
|
|
25
|
-
|
|
26
|
-
action=
|
|
27
|
-
help=
|
|
24
|
+
"--fix",
|
|
25
|
+
action="store_true",
|
|
26
|
+
help="Actually remove dangling entries (required to make changes)",
|
|
28
27
|
)
|
|
29
|
-
|
|
30
|
-
'--fix',
|
|
31
|
-
action='store_true',
|
|
32
|
-
help='Actually remove dangling entries (required to make changes)'
|
|
33
|
-
)
|
|
34
|
-
|
|
28
|
+
|
|
35
29
|
@staticmethod
|
|
36
30
|
def execute(args) -> int:
|
|
37
31
|
repo, code = require_repo()
|
|
38
32
|
if code != 0:
|
|
39
33
|
return code
|
|
40
|
-
|
|
34
|
+
|
|
41
35
|
print("Running file system consistency check...")
|
|
42
|
-
|
|
36
|
+
|
|
43
37
|
issues_found = 0
|
|
44
38
|
issues_fixed = 0
|
|
45
|
-
|
|
39
|
+
|
|
46
40
|
# Check vector store for dangling entries
|
|
47
41
|
try:
|
|
48
42
|
from ..core.vector_store import VectorStore
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
|
|
44
|
+
vs = VectorStore(repo.root / ".mem")
|
|
45
|
+
|
|
51
46
|
vector_issues, vector_fixed = FsckCommand._check_vectors(
|
|
52
47
|
repo, vs, args.dry_run, args.verbose, args.fix
|
|
53
48
|
)
|
|
@@ -58,64 +53,62 @@ class FsckCommand:
|
|
|
58
53
|
print("Vector store not available, skipping vector check")
|
|
59
54
|
except Exception as e:
|
|
60
55
|
print(f"Warning: Vector store check failed: {e}")
|
|
61
|
-
|
|
56
|
+
|
|
62
57
|
# Check object store integrity
|
|
63
58
|
obj_issues, obj_fixed = FsckCommand._check_objects(
|
|
64
59
|
repo, args.dry_run, args.verbose, args.fix
|
|
65
60
|
)
|
|
66
61
|
issues_found += obj_issues
|
|
67
62
|
issues_fixed += obj_fixed
|
|
68
|
-
|
|
63
|
+
|
|
69
64
|
# Check refs integrity
|
|
70
|
-
ref_issues, ref_fixed = FsckCommand._check_refs(
|
|
71
|
-
repo, args.dry_run, args.verbose, args.fix
|
|
72
|
-
)
|
|
65
|
+
ref_issues, ref_fixed = FsckCommand._check_refs(repo, args.dry_run, args.verbose, args.fix)
|
|
73
66
|
issues_found += ref_issues
|
|
74
67
|
issues_fixed += ref_fixed
|
|
75
|
-
|
|
68
|
+
|
|
76
69
|
# Print summary
|
|
77
70
|
print()
|
|
78
71
|
print("=" * 40)
|
|
79
72
|
print("FSCK Summary")
|
|
80
73
|
print("=" * 40)
|
|
81
74
|
print(f"Issues found: {issues_found}")
|
|
82
|
-
|
|
75
|
+
|
|
83
76
|
if args.fix:
|
|
84
77
|
print(f"Issues fixed: {issues_fixed}")
|
|
85
78
|
elif issues_found > 0:
|
|
86
79
|
print("\nRun with --fix to repair issues")
|
|
87
|
-
|
|
80
|
+
|
|
88
81
|
if issues_found == 0:
|
|
89
82
|
print("Repository is healthy!")
|
|
90
|
-
|
|
83
|
+
|
|
91
84
|
return 0 if issues_found == 0 else 1
|
|
92
|
-
|
|
85
|
+
|
|
93
86
|
@staticmethod
|
|
94
87
|
def _check_vectors(repo, vs, dry_run: bool, verbose: bool, fix: bool) -> tuple:
|
|
95
88
|
"""Check for dangling vector entries."""
|
|
96
89
|
print("\nChecking vector store...")
|
|
97
|
-
|
|
98
|
-
current_dir = repo.root /
|
|
90
|
+
|
|
91
|
+
current_dir = repo.root / "current"
|
|
99
92
|
entries = vs.get_all_entries()
|
|
100
|
-
|
|
93
|
+
|
|
101
94
|
dangling = []
|
|
102
|
-
|
|
95
|
+
|
|
103
96
|
for entry in entries:
|
|
104
|
-
path = entry[
|
|
97
|
+
path = entry["path"]
|
|
105
98
|
full_path = current_dir / path
|
|
106
|
-
|
|
99
|
+
|
|
107
100
|
if not full_path.exists():
|
|
108
101
|
dangling.append(entry)
|
|
109
102
|
if verbose:
|
|
110
103
|
print(f" Dangling: {path} (rowid: {entry['rowid']})")
|
|
111
|
-
|
|
104
|
+
|
|
112
105
|
if dangling:
|
|
113
106
|
print(f" Found {len(dangling)} dangling vector entries")
|
|
114
|
-
|
|
107
|
+
|
|
115
108
|
if fix and not dry_run:
|
|
116
109
|
fixed = 0
|
|
117
110
|
for entry in dangling:
|
|
118
|
-
if vs.delete_entry(entry[
|
|
111
|
+
if vs.delete_entry(entry["rowid"]):
|
|
119
112
|
fixed += 1
|
|
120
113
|
print(f" Removed {fixed} dangling entries")
|
|
121
114
|
return len(dangling), fixed
|
|
@@ -123,22 +116,22 @@ class FsckCommand:
|
|
|
123
116
|
print(" (dry-run: no changes made)")
|
|
124
117
|
else:
|
|
125
118
|
print(" Vector store is consistent")
|
|
126
|
-
|
|
119
|
+
|
|
127
120
|
return len(dangling), 0
|
|
128
|
-
|
|
121
|
+
|
|
129
122
|
@staticmethod
|
|
130
123
|
def _check_objects(repo, dry_run: bool, verbose: bool, fix: bool) -> tuple:
|
|
131
124
|
"""Check object store integrity."""
|
|
132
125
|
print("\nChecking object store...")
|
|
133
|
-
|
|
126
|
+
|
|
134
127
|
issues = 0
|
|
135
|
-
|
|
128
|
+
|
|
136
129
|
# Check if all referenced blobs exist
|
|
137
|
-
for obj_type in [
|
|
138
|
-
obj_dir = repo.root /
|
|
130
|
+
for obj_type in ["blob", "tree", "commit", "tag"]:
|
|
131
|
+
obj_dir = repo.root / ".mem" / "objects" / obj_type
|
|
139
132
|
if not obj_dir.exists():
|
|
140
133
|
continue
|
|
141
|
-
|
|
134
|
+
|
|
142
135
|
for prefix_dir in obj_dir.iterdir():
|
|
143
136
|
if not prefix_dir.is_dir():
|
|
144
137
|
continue
|
|
@@ -146,6 +139,7 @@ class FsckCommand:
|
|
|
146
139
|
try:
|
|
147
140
|
# Try to read and decompress
|
|
148
141
|
import zlib
|
|
142
|
+
|
|
149
143
|
compressed = obj_file.read_bytes()
|
|
150
144
|
zlib.decompress(compressed)
|
|
151
145
|
except Exception as e:
|
|
@@ -153,51 +147,51 @@ class FsckCommand:
|
|
|
153
147
|
if verbose:
|
|
154
148
|
hash_id = prefix_dir.name + obj_file.name
|
|
155
149
|
print(f" Corrupted {obj_type}: {hash_id[:8]}...")
|
|
156
|
-
|
|
150
|
+
|
|
157
151
|
if issues == 0:
|
|
158
152
|
print(" Object store is consistent")
|
|
159
153
|
else:
|
|
160
154
|
print(f" Found {issues} corrupted objects")
|
|
161
|
-
|
|
155
|
+
|
|
162
156
|
return issues, 0 # Object repair not implemented
|
|
163
|
-
|
|
157
|
+
|
|
164
158
|
@staticmethod
|
|
165
159
|
def _check_refs(repo, dry_run: bool, verbose: bool, fix: bool) -> tuple:
|
|
166
160
|
"""Check refs integrity."""
|
|
167
161
|
print("\nChecking refs...")
|
|
168
|
-
|
|
162
|
+
|
|
169
163
|
issues = 0
|
|
170
|
-
|
|
164
|
+
|
|
171
165
|
# Check if HEAD points to valid commit
|
|
172
166
|
head = repo.refs.get_head()
|
|
173
|
-
if head[
|
|
174
|
-
branch_commit = repo.refs.get_branch_commit(head[
|
|
167
|
+
if head["type"] == "branch":
|
|
168
|
+
branch_commit = repo.refs.get_branch_commit(head["value"])
|
|
175
169
|
if not branch_commit:
|
|
176
170
|
issues += 1
|
|
177
171
|
if verbose:
|
|
178
172
|
print(f" HEAD branch '{head['value']}' has no commit")
|
|
179
|
-
elif not repo.object_store.exists(branch_commit,
|
|
173
|
+
elif not repo.object_store.exists(branch_commit, "commit"):
|
|
180
174
|
issues += 1
|
|
181
175
|
if verbose:
|
|
182
176
|
print(f" HEAD points to missing commit: {branch_commit[:8]}")
|
|
183
|
-
elif head[
|
|
184
|
-
if not repo.object_store.exists(head[
|
|
177
|
+
elif head["type"] == "detached":
|
|
178
|
+
if not repo.object_store.exists(head["value"], "commit"):
|
|
185
179
|
issues += 1
|
|
186
180
|
if verbose:
|
|
187
181
|
print(f" Detached HEAD points to missing commit")
|
|
188
|
-
|
|
182
|
+
|
|
189
183
|
# Check all branches
|
|
190
184
|
branches = repo.refs.list_branches()
|
|
191
185
|
for branch in branches:
|
|
192
186
|
commit_hash = repo.refs.get_branch_commit(branch)
|
|
193
|
-
if commit_hash and not repo.object_store.exists(commit_hash,
|
|
187
|
+
if commit_hash and not repo.object_store.exists(commit_hash, "commit"):
|
|
194
188
|
issues += 1
|
|
195
189
|
if verbose:
|
|
196
190
|
print(f" Branch '{branch}' points to missing commit")
|
|
197
|
-
|
|
191
|
+
|
|
198
192
|
if issues == 0:
|
|
199
193
|
print(" Refs are consistent")
|
|
200
194
|
else:
|
|
201
195
|
print(f" Found {issues} ref issues")
|
|
202
|
-
|
|
196
|
+
|
|
203
197
|
return issues, 0
|
memvcs/commands/garden.py
CHANGED
|
@@ -10,96 +10,87 @@ from ..core.gardener import Gardener, GardenerConfig
|
|
|
10
10
|
|
|
11
11
|
class GardenCommand:
|
|
12
12
|
"""Run the Gardener reflection loop."""
|
|
13
|
-
|
|
14
|
-
name =
|
|
15
|
-
help =
|
|
16
|
-
|
|
13
|
+
|
|
14
|
+
name = "garden"
|
|
15
|
+
help = "Synthesize episodic memories into semantic insights"
|
|
16
|
+
|
|
17
17
|
@staticmethod
|
|
18
18
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
19
19
|
parser.add_argument(
|
|
20
|
-
|
|
21
|
-
action='store_true',
|
|
22
|
-
help='Run even if episode threshold not met'
|
|
20
|
+
"--force", action="store_true", help="Run even if episode threshold not met"
|
|
23
21
|
)
|
|
24
22
|
parser.add_argument(
|
|
25
|
-
|
|
23
|
+
"--threshold",
|
|
26
24
|
type=int,
|
|
27
25
|
default=50,
|
|
28
|
-
help=
|
|
29
|
-
)
|
|
30
|
-
parser.add_argument(
|
|
31
|
-
'--dry-run',
|
|
32
|
-
action='store_true',
|
|
33
|
-
help='Show what would be done without making changes'
|
|
26
|
+
help="Number of episodes before auto-triggering (default: 50)",
|
|
34
27
|
)
|
|
35
28
|
parser.add_argument(
|
|
36
|
-
|
|
37
|
-
action='store_true',
|
|
38
|
-
help='Do not auto-commit generated insights'
|
|
29
|
+
"--dry-run", action="store_true", help="Show what would be done without making changes"
|
|
39
30
|
)
|
|
40
31
|
parser.add_argument(
|
|
41
|
-
|
|
42
|
-
choices=['openai', 'none'],
|
|
43
|
-
default='none',
|
|
44
|
-
help='LLM provider for summarization (default: none)'
|
|
32
|
+
"--no-commit", action="store_true", help="Do not auto-commit generated insights"
|
|
45
33
|
)
|
|
46
34
|
parser.add_argument(
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
"--llm",
|
|
36
|
+
choices=["openai", "none"],
|
|
37
|
+
default="none",
|
|
38
|
+
help="LLM provider for summarization (default: none)",
|
|
49
39
|
)
|
|
50
|
-
|
|
40
|
+
parser.add_argument("--model", help="LLM model to use (e.g., gpt-3.5-turbo)")
|
|
41
|
+
|
|
51
42
|
@staticmethod
|
|
52
43
|
def execute(args) -> int:
|
|
53
44
|
repo, code = require_repo()
|
|
54
45
|
if code != 0:
|
|
55
46
|
return code
|
|
56
|
-
|
|
47
|
+
|
|
57
48
|
# Build config
|
|
58
49
|
config = GardenerConfig(
|
|
59
50
|
threshold=args.threshold,
|
|
60
51
|
auto_commit=not args.no_commit,
|
|
61
|
-
llm_provider=args.llm if args.llm !=
|
|
62
|
-
llm_model=args.model
|
|
52
|
+
llm_provider=args.llm if args.llm != "none" else None,
|
|
53
|
+
llm_model=args.model,
|
|
63
54
|
)
|
|
64
|
-
|
|
55
|
+
|
|
65
56
|
# Create gardener
|
|
66
57
|
gardener = Gardener(repo, config)
|
|
67
|
-
|
|
58
|
+
|
|
68
59
|
# Show status
|
|
69
60
|
episode_count = gardener.get_episode_count()
|
|
70
61
|
print(f"Episodic files: {episode_count}/{config.threshold}")
|
|
71
|
-
|
|
62
|
+
|
|
72
63
|
if args.dry_run:
|
|
73
64
|
if gardener.should_run() or args.force:
|
|
74
65
|
episodes = gardener.load_episodes()
|
|
75
66
|
clusters = gardener.cluster_episodes(episodes)
|
|
76
|
-
|
|
67
|
+
|
|
77
68
|
print(f"\nWould process {len(episodes)} episodes into {len(clusters)} clusters:")
|
|
78
69
|
for cluster in clusters:
|
|
79
70
|
print(f" - {cluster.topic}: {len(cluster.episodes)} episodes")
|
|
80
|
-
|
|
71
|
+
|
|
81
72
|
print("\nRun without --dry-run to execute.")
|
|
82
73
|
else:
|
|
83
74
|
print("\nThreshold not met. Use --force to run anyway.")
|
|
84
75
|
return 0
|
|
85
|
-
|
|
76
|
+
|
|
86
77
|
# Run gardener
|
|
87
78
|
if not gardener.should_run() and not args.force:
|
|
88
79
|
print("\nThreshold not met. Use --force to run anyway.")
|
|
89
80
|
return 0
|
|
90
|
-
|
|
81
|
+
|
|
91
82
|
print("\nRunning Gardener...")
|
|
92
83
|
result = gardener.run(force=args.force)
|
|
93
|
-
|
|
84
|
+
|
|
94
85
|
if result.success:
|
|
95
86
|
print(f"\nGardener completed:")
|
|
96
87
|
print(f" Clusters found: {result.clusters_found}")
|
|
97
88
|
print(f" Insights generated: {result.insights_generated}")
|
|
98
89
|
print(f" Episodes archived: {result.episodes_archived}")
|
|
99
|
-
|
|
90
|
+
|
|
100
91
|
if result.commit_hash:
|
|
101
92
|
print(f" Commit: {result.commit_hash[:8]}")
|
|
102
|
-
|
|
93
|
+
|
|
103
94
|
print(f"\n{result.message}")
|
|
104
95
|
return 0
|
|
105
96
|
else:
|
memvcs/commands/graph.py
CHANGED
|
@@ -11,109 +11,102 @@ from ..commands.base import require_repo
|
|
|
11
11
|
|
|
12
12
|
class GraphCommand:
|
|
13
13
|
"""Visualize connections between memory files."""
|
|
14
|
-
|
|
15
|
-
name =
|
|
16
|
-
help =
|
|
17
|
-
|
|
14
|
+
|
|
15
|
+
name = "graph"
|
|
16
|
+
help = "Visualize the knowledge graph of memory files"
|
|
17
|
+
|
|
18
18
|
@staticmethod
|
|
19
19
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
20
|
+
parser.add_argument("--output", "-o", help="Output file for graph data (JSON)")
|
|
20
21
|
parser.add_argument(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'--format',
|
|
26
|
-
choices=['json', 'd3', 'summary'],
|
|
27
|
-
default='summary',
|
|
28
|
-
help='Output format (default: summary)'
|
|
22
|
+
"--format",
|
|
23
|
+
choices=["json", "d3", "summary"],
|
|
24
|
+
default="summary",
|
|
25
|
+
help="Output format (default: summary)",
|
|
29
26
|
)
|
|
30
27
|
parser.add_argument(
|
|
31
|
-
|
|
32
|
-
action='store_true',
|
|
33
|
-
help='Skip similarity-based edges (faster)'
|
|
28
|
+
"--no-similarity", action="store_true", help="Skip similarity-based edges (faster)"
|
|
34
29
|
)
|
|
35
30
|
parser.add_argument(
|
|
36
|
-
|
|
31
|
+
"--threshold",
|
|
37
32
|
type=float,
|
|
38
33
|
default=0.7,
|
|
39
|
-
help=
|
|
34
|
+
help="Similarity threshold for edges (default: 0.7)",
|
|
40
35
|
)
|
|
41
36
|
parser.add_argument(
|
|
42
|
-
|
|
43
|
-
action='store_true',
|
|
44
|
-
help='Start web server to view interactive graph'
|
|
37
|
+
"--serve", action="store_true", help="Start web server to view interactive graph"
|
|
45
38
|
)
|
|
46
|
-
|
|
39
|
+
|
|
47
40
|
@staticmethod
|
|
48
41
|
def execute(args) -> int:
|
|
49
42
|
repo, code = require_repo()
|
|
50
43
|
if code != 0:
|
|
51
44
|
return code
|
|
52
|
-
|
|
45
|
+
|
|
53
46
|
# Try to get vector store for similarity
|
|
54
47
|
vector_store = None
|
|
55
48
|
if not args.no_similarity:
|
|
56
49
|
try:
|
|
57
50
|
from ..core.vector_store import VectorStore
|
|
58
|
-
|
|
51
|
+
|
|
52
|
+
vector_store = VectorStore(repo.root / ".mem")
|
|
59
53
|
except ImportError:
|
|
60
54
|
print("Note: Vector store not available, skipping similarity edges")
|
|
61
|
-
|
|
55
|
+
|
|
62
56
|
# Build graph
|
|
63
57
|
from ..core.knowledge_graph import KnowledgeGraphBuilder
|
|
64
|
-
|
|
58
|
+
|
|
65
59
|
builder = KnowledgeGraphBuilder(repo, vector_store)
|
|
66
|
-
|
|
60
|
+
|
|
67
61
|
print("Building knowledge graph...")
|
|
68
62
|
graph_data = builder.build_graph(
|
|
69
|
-
include_similarity=not args.no_similarity,
|
|
70
|
-
similarity_threshold=args.threshold
|
|
63
|
+
include_similarity=not args.no_similarity, similarity_threshold=args.threshold
|
|
71
64
|
)
|
|
72
|
-
|
|
65
|
+
|
|
73
66
|
if args.serve:
|
|
74
67
|
return GraphCommand._serve_graph(repo, graph_data)
|
|
75
|
-
|
|
76
|
-
if args.format ==
|
|
68
|
+
|
|
69
|
+
if args.format == "summary":
|
|
77
70
|
GraphCommand._print_summary(graph_data, builder)
|
|
78
|
-
|
|
79
|
-
elif args.format ==
|
|
71
|
+
|
|
72
|
+
elif args.format == "json":
|
|
80
73
|
output = graph_data.to_json()
|
|
81
74
|
if args.output:
|
|
82
75
|
Path(args.output).write_text(output)
|
|
83
76
|
print(f"Graph data written to: {args.output}")
|
|
84
77
|
else:
|
|
85
78
|
print(output)
|
|
86
|
-
|
|
87
|
-
elif args.format ==
|
|
79
|
+
|
|
80
|
+
elif args.format == "d3":
|
|
88
81
|
output = builder.export_for_d3()
|
|
89
82
|
if args.output:
|
|
90
83
|
Path(args.output).write_text(output)
|
|
91
84
|
print(f"D3 graph data written to: {args.output}")
|
|
92
85
|
else:
|
|
93
86
|
print(output)
|
|
94
|
-
|
|
87
|
+
|
|
95
88
|
return 0
|
|
96
|
-
|
|
89
|
+
|
|
97
90
|
@staticmethod
|
|
98
91
|
def _print_summary(graph_data, builder):
|
|
99
92
|
"""Print a text summary of the graph."""
|
|
100
93
|
meta = graph_data.metadata
|
|
101
|
-
|
|
94
|
+
|
|
102
95
|
print("\nKnowledge Graph Summary")
|
|
103
96
|
print("=" * 40)
|
|
104
97
|
print(f"Total files: {meta['total_nodes']}")
|
|
105
98
|
print(f"Total connections: {meta['total_edges']}")
|
|
106
|
-
|
|
99
|
+
|
|
107
100
|
print("\nBy Memory Type:")
|
|
108
|
-
for mtype, count in meta[
|
|
101
|
+
for mtype, count in meta["memory_types"].items():
|
|
109
102
|
if count > 0:
|
|
110
103
|
print(f" {mtype}: {count}")
|
|
111
|
-
|
|
104
|
+
|
|
112
105
|
print("\nBy Edge Type:")
|
|
113
|
-
for etype, count in meta[
|
|
106
|
+
for etype, count in meta["edge_types"].items():
|
|
114
107
|
if count > 0:
|
|
115
108
|
print(f" {etype}: {count}")
|
|
116
|
-
|
|
109
|
+
|
|
117
110
|
# Find isolated nodes
|
|
118
111
|
isolated = builder.find_isolated_nodes()
|
|
119
112
|
if isolated:
|
|
@@ -122,16 +115,16 @@ class GraphCommand:
|
|
|
122
115
|
print(f" - {path}")
|
|
123
116
|
if len(isolated) > 5:
|
|
124
117
|
print(f" ... and {len(isolated) - 5} more")
|
|
125
|
-
|
|
118
|
+
|
|
126
119
|
# Find potential contradictions
|
|
127
120
|
contradictions = builder.find_potential_contradictions()
|
|
128
121
|
if contradictions:
|
|
129
122
|
print(f"\nPotential contradictions: {len(contradictions)}")
|
|
130
123
|
for path1, path2, sim in contradictions[:3]:
|
|
131
124
|
print(f" - {path1} <-> {path2} (similarity: {sim:.2%})")
|
|
132
|
-
|
|
125
|
+
|
|
133
126
|
print("\nUse --format d3 --output graph.json to export for visualization")
|
|
134
|
-
|
|
127
|
+
|
|
135
128
|
@staticmethod
|
|
136
129
|
def _serve_graph(repo, graph_data):
|
|
137
130
|
"""Start web server to view interactive graph."""
|
|
@@ -142,10 +135,10 @@ class GraphCommand:
|
|
|
142
135
|
print("Error: Web server requires fastapi and uvicorn.")
|
|
143
136
|
print("Install with: pip install agmem[web]")
|
|
144
137
|
return 1
|
|
145
|
-
|
|
138
|
+
|
|
146
139
|
print("Starting graph visualization server...")
|
|
147
140
|
print("Open http://localhost:8080/graph in your browser")
|
|
148
|
-
|
|
141
|
+
|
|
149
142
|
app = create_app(repo.root)
|
|
150
143
|
uvicorn.run(app, host="127.0.0.1", port=8080)
|
|
151
144
|
return 0
|
memvcs/commands/init.py
CHANGED
|
@@ -10,49 +10,41 @@ from ..core.repository import Repository
|
|
|
10
10
|
|
|
11
11
|
class InitCommand:
|
|
12
12
|
"""Initialize a new agmem repository."""
|
|
13
|
-
|
|
14
|
-
name =
|
|
15
|
-
help =
|
|
16
|
-
|
|
13
|
+
|
|
14
|
+
name = "init"
|
|
15
|
+
help = "Initialize a new memory repository"
|
|
16
|
+
|
|
17
17
|
@staticmethod
|
|
18
18
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
19
19
|
parser.add_argument(
|
|
20
|
-
|
|
21
|
-
nargs=
|
|
22
|
-
default=
|
|
23
|
-
help=
|
|
20
|
+
"path",
|
|
21
|
+
nargs="?",
|
|
22
|
+
default=".",
|
|
23
|
+
help="Directory to initialize repository in (default: current directory)",
|
|
24
24
|
)
|
|
25
|
+
parser.add_argument("--author-name", default="Agent", help="Default author name")
|
|
25
26
|
parser.add_argument(
|
|
26
|
-
|
|
27
|
-
default='Agent',
|
|
28
|
-
help='Default author name'
|
|
27
|
+
"--author-email", default="agent@example.com", help="Default author email"
|
|
29
28
|
)
|
|
30
|
-
|
|
31
|
-
'--author-email',
|
|
32
|
-
default='agent@example.com',
|
|
33
|
-
help='Default author email'
|
|
34
|
-
)
|
|
35
|
-
|
|
29
|
+
|
|
36
30
|
@staticmethod
|
|
37
31
|
def execute(args) -> int:
|
|
38
32
|
path = Path(args.path).resolve()
|
|
39
|
-
|
|
33
|
+
|
|
40
34
|
try:
|
|
41
35
|
repo = Repository.init(
|
|
42
|
-
path=path,
|
|
43
|
-
author_name=args.author_name,
|
|
44
|
-
author_email=args.author_email
|
|
36
|
+
path=path, author_name=args.author_name, author_email=args.author_email
|
|
45
37
|
)
|
|
46
|
-
|
|
38
|
+
|
|
47
39
|
print(f"Initialized empty agmem repository in {repo.mem_dir}")
|
|
48
40
|
print(f"Author: {args.author_name} <{args.author_email}>")
|
|
49
41
|
print(f"\nNext steps:")
|
|
50
42
|
print(f" 1. Add memory files to {repo.current_dir}/")
|
|
51
43
|
print(f" 2. Run 'agmem add <file>' to stage changes")
|
|
52
44
|
print(f" 3. Run 'agmem commit -m \"message\"' to save snapshot")
|
|
53
|
-
|
|
45
|
+
|
|
54
46
|
return 0
|
|
55
|
-
|
|
47
|
+
|
|
56
48
|
except ValueError as e:
|
|
57
49
|
print(f"Error: {e}")
|
|
58
50
|
return 1
|