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/log.py
CHANGED
|
@@ -11,39 +11,24 @@ from ..core.repository import Repository
|
|
|
11
11
|
|
|
12
12
|
class LogCommand:
|
|
13
13
|
"""Show commit history."""
|
|
14
|
-
|
|
15
|
-
name =
|
|
16
|
-
help =
|
|
17
|
-
|
|
14
|
+
|
|
15
|
+
name = "log"
|
|
16
|
+
help = "Show commit history"
|
|
17
|
+
|
|
18
18
|
@staticmethod
|
|
19
19
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
20
20
|
parser.add_argument(
|
|
21
|
-
|
|
22
|
-
type=int,
|
|
23
|
-
default=10,
|
|
24
|
-
help='Maximum number of commits to show'
|
|
25
|
-
)
|
|
26
|
-
parser.add_argument(
|
|
27
|
-
'--oneline',
|
|
28
|
-
action='store_true',
|
|
29
|
-
help='Show one commit per line'
|
|
21
|
+
"--max-count", "-n", type=int, default=10, help="Maximum number of commits to show"
|
|
30
22
|
)
|
|
23
|
+
parser.add_argument("--oneline", action="store_true", help="Show one commit per line")
|
|
31
24
|
parser.add_argument(
|
|
32
|
-
|
|
33
|
-
action='store_true',
|
|
34
|
-
help='Show ASCII graph of branch/merge history'
|
|
25
|
+
"--graph", action="store_true", help="Show ASCII graph of branch/merge history"
|
|
35
26
|
)
|
|
27
|
+
parser.add_argument("--all", action="store_true", help="Show all branches")
|
|
36
28
|
parser.add_argument(
|
|
37
|
-
|
|
38
|
-
action='store_true',
|
|
39
|
-
help='Show all branches'
|
|
29
|
+
"ref", nargs="?", help="Start from this reference (branch, tag, or commit)"
|
|
40
30
|
)
|
|
41
|
-
|
|
42
|
-
'ref',
|
|
43
|
-
nargs='?',
|
|
44
|
-
help='Start from this reference (branch, tag, or commit)'
|
|
45
|
-
)
|
|
46
|
-
|
|
31
|
+
|
|
47
32
|
@staticmethod
|
|
48
33
|
def execute(args) -> int:
|
|
49
34
|
repo, code = require_repo()
|
|
@@ -52,11 +37,11 @@ class LogCommand:
|
|
|
52
37
|
|
|
53
38
|
# Get commits
|
|
54
39
|
commits = repo.get_log(max_count=args.max_count)
|
|
55
|
-
|
|
40
|
+
|
|
56
41
|
if not commits:
|
|
57
42
|
print("No commits yet.")
|
|
58
43
|
return 0
|
|
59
|
-
|
|
44
|
+
|
|
60
45
|
if args.oneline:
|
|
61
46
|
for commit in commits:
|
|
62
47
|
print(f"{commit['short_hash']} {commit['message']}")
|
|
@@ -71,33 +56,33 @@ class LogCommand:
|
|
|
71
56
|
for i, commit in enumerate(commits):
|
|
72
57
|
if i > 0:
|
|
73
58
|
print()
|
|
74
|
-
|
|
59
|
+
|
|
75
60
|
# Commit header
|
|
76
61
|
print(f"\033[33mcommit {commit['hash']}\033[0m")
|
|
77
|
-
|
|
62
|
+
|
|
78
63
|
# Show branch info if this is HEAD
|
|
79
64
|
head = repo.refs.get_head()
|
|
80
|
-
if head[
|
|
81
|
-
head_commit = repo.refs.get_branch_commit(head[
|
|
82
|
-
if head_commit == commit[
|
|
65
|
+
if head["type"] == "branch":
|
|
66
|
+
head_commit = repo.refs.get_branch_commit(head["value"])
|
|
67
|
+
if head_commit == commit["hash"]:
|
|
83
68
|
print(f"\033[36mHEAD -> {head['value']}\033[0m")
|
|
84
|
-
|
|
69
|
+
|
|
85
70
|
# Author and date
|
|
86
71
|
print(f"Author: {commit['author']}")
|
|
87
|
-
|
|
72
|
+
|
|
88
73
|
# Format timestamp
|
|
89
74
|
try:
|
|
90
|
-
ts = commit[
|
|
91
|
-
if ts.endswith(
|
|
75
|
+
ts = commit["timestamp"]
|
|
76
|
+
if ts.endswith("Z"):
|
|
92
77
|
ts = ts[:-1]
|
|
93
78
|
dt = datetime.fromisoformat(ts)
|
|
94
|
-
date_str = dt.strftime(
|
|
79
|
+
date_str = dt.strftime("%a %b %d %H:%M:%S %Y")
|
|
95
80
|
print(f"Date: {date_str}")
|
|
96
|
-
except:
|
|
81
|
+
except Exception:
|
|
97
82
|
print(f"Date: {commit['timestamp']}")
|
|
98
|
-
|
|
83
|
+
|
|
99
84
|
# Message
|
|
100
85
|
print()
|
|
101
86
|
print(f" {commit['message']}")
|
|
102
|
-
|
|
87
|
+
|
|
103
88
|
return 0
|
memvcs/commands/merge.py
CHANGED
|
@@ -11,31 +11,19 @@ from ..core.repository import Repository
|
|
|
11
11
|
|
|
12
12
|
class MergeCommand:
|
|
13
13
|
"""Merge branches."""
|
|
14
|
-
|
|
15
|
-
name =
|
|
16
|
-
help =
|
|
17
|
-
|
|
14
|
+
|
|
15
|
+
name = "merge"
|
|
16
|
+
help = "Join two or more development histories together"
|
|
17
|
+
|
|
18
18
|
@staticmethod
|
|
19
19
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
20
|
+
parser.add_argument("branch", help="Branch to merge into current branch")
|
|
21
|
+
parser.add_argument("-m", "--message", help="Merge commit message")
|
|
20
22
|
parser.add_argument(
|
|
21
|
-
|
|
22
|
-
help='Branch to merge into current branch'
|
|
23
|
-
)
|
|
24
|
-
parser.add_argument(
|
|
25
|
-
'-m', '--message',
|
|
26
|
-
help='Merge commit message'
|
|
27
|
-
)
|
|
28
|
-
parser.add_argument(
|
|
29
|
-
'--no-commit',
|
|
30
|
-
action='store_true',
|
|
31
|
-
help='Perform merge but do not commit'
|
|
32
|
-
)
|
|
33
|
-
parser.add_argument(
|
|
34
|
-
'--abort',
|
|
35
|
-
action='store_true',
|
|
36
|
-
help='Abort the current merge'
|
|
23
|
+
"--no-commit", action="store_true", help="Perform merge but do not commit"
|
|
37
24
|
)
|
|
38
|
-
|
|
25
|
+
parser.add_argument("--abort", action="store_true", help="Abort the current merge")
|
|
26
|
+
|
|
39
27
|
@staticmethod
|
|
40
28
|
def execute(args) -> int:
|
|
41
29
|
repo, code = require_repo()
|
|
@@ -47,28 +35,28 @@ class MergeCommand:
|
|
|
47
35
|
# TODO: Implement merge abort
|
|
48
36
|
print("Merge abort not yet implemented")
|
|
49
37
|
return 0
|
|
50
|
-
|
|
38
|
+
|
|
51
39
|
# Check if we're on a branch
|
|
52
40
|
current_branch = repo.refs.get_current_branch()
|
|
53
41
|
if not current_branch:
|
|
54
42
|
print("Error: Not currently on any branch.")
|
|
55
43
|
print("Cannot merge when HEAD is detached.")
|
|
56
44
|
return 1
|
|
57
|
-
|
|
45
|
+
|
|
58
46
|
# Check if trying to merge current branch
|
|
59
47
|
if args.branch == current_branch:
|
|
60
48
|
print(f"Error: Cannot merge '{args.branch}' into itself")
|
|
61
49
|
return 1
|
|
62
|
-
|
|
50
|
+
|
|
63
51
|
# Check if branch exists
|
|
64
52
|
if not repo.refs.branch_exists(args.branch):
|
|
65
53
|
print(f"Error: Branch '{args.branch}' not found.")
|
|
66
54
|
return 1
|
|
67
|
-
|
|
55
|
+
|
|
68
56
|
# Perform merge
|
|
69
57
|
engine = MergeEngine(repo)
|
|
70
58
|
result = engine.merge(args.branch, message=args.message)
|
|
71
|
-
|
|
59
|
+
|
|
72
60
|
if result.success:
|
|
73
61
|
print(f"Merge successful: {result.message}")
|
|
74
62
|
if result.commit_hash:
|
|
@@ -76,7 +64,7 @@ class MergeCommand:
|
|
|
76
64
|
return 0
|
|
77
65
|
else:
|
|
78
66
|
print(f"Merge failed: {result.message}")
|
|
79
|
-
|
|
67
|
+
|
|
80
68
|
if result.conflicts:
|
|
81
69
|
print()
|
|
82
70
|
print("Conflicts:")
|
|
@@ -84,5 +72,5 @@ class MergeCommand:
|
|
|
84
72
|
print(f" {conflict.path}")
|
|
85
73
|
print()
|
|
86
74
|
print("Resolve conflicts and run 'agmem commit' to complete the merge.")
|
|
87
|
-
|
|
75
|
+
|
|
88
76
|
return 1
|
memvcs/commands/pack.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agmem pack - Context window budget manager.
|
|
3
|
+
|
|
4
|
+
Packs recalled memories into token budget for LLM injection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from ..commands.base import require_repo
|
|
12
|
+
from ..core.access_index import AccessIndex
|
|
13
|
+
from ..retrieval import RecallEngine
|
|
14
|
+
from ..retrieval.pack import PackEngine
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PackCommand:
|
|
18
|
+
"""Context window budget manager."""
|
|
19
|
+
|
|
20
|
+
name = "pack"
|
|
21
|
+
help = "Pack recalled memories into token budget for LLM context"
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def add_arguments(parser: argparse.ArgumentParser):
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--context",
|
|
27
|
+
"-c",
|
|
28
|
+
default="",
|
|
29
|
+
help="Current task description for recall",
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--budget",
|
|
33
|
+
"-b",
|
|
34
|
+
type=int,
|
|
35
|
+
default=4000,
|
|
36
|
+
help="Max tokens (default: 4000)",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--strategy",
|
|
40
|
+
"-s",
|
|
41
|
+
choices=["relevance", "recency", "importance", "balanced"],
|
|
42
|
+
default="relevance",
|
|
43
|
+
help="Packing strategy (default: relevance)",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--exclude",
|
|
47
|
+
"-e",
|
|
48
|
+
action="append",
|
|
49
|
+
default=[],
|
|
50
|
+
help="Paths to exclude; repeatable",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--model",
|
|
54
|
+
"-m",
|
|
55
|
+
default="gpt-4o-mini",
|
|
56
|
+
help="Model for token counting (default: gpt-4o-mini)",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--format",
|
|
60
|
+
"-f",
|
|
61
|
+
choices=["text", "json"],
|
|
62
|
+
default="text",
|
|
63
|
+
help="Output format (default: text)",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def execute(args) -> int:
|
|
68
|
+
repo, code = require_repo()
|
|
69
|
+
if code != 0:
|
|
70
|
+
return code
|
|
71
|
+
|
|
72
|
+
vector_store = None
|
|
73
|
+
try:
|
|
74
|
+
from ..core.vector_store import VectorStore
|
|
75
|
+
|
|
76
|
+
vs = VectorStore(repo.mem_dir)
|
|
77
|
+
vs._get_connection() # ensure sqlite-vec is usable; may raise
|
|
78
|
+
vector_store = vs
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
access_index = AccessIndex(repo.mem_dir)
|
|
83
|
+
recall_engine = RecallEngine(
|
|
84
|
+
repo=repo,
|
|
85
|
+
vector_store=vector_store,
|
|
86
|
+
access_index=access_index,
|
|
87
|
+
use_cache=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
strategy = "hybrid" if args.strategy == "balanced" else args.strategy
|
|
91
|
+
pack_engine = PackEngine(
|
|
92
|
+
recall_engine=recall_engine,
|
|
93
|
+
model=args.model,
|
|
94
|
+
summarization_cascade=False,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
result = pack_engine.pack(
|
|
98
|
+
context=args.context,
|
|
99
|
+
budget=args.budget,
|
|
100
|
+
strategy=strategy,
|
|
101
|
+
exclude=args.exclude,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if args.format == "json":
|
|
105
|
+
import json
|
|
106
|
+
|
|
107
|
+
print(
|
|
108
|
+
json.dumps(
|
|
109
|
+
{
|
|
110
|
+
"content": result.content,
|
|
111
|
+
"total_tokens": result.total_tokens,
|
|
112
|
+
"budget": result.budget,
|
|
113
|
+
"items_used": result.items_used,
|
|
114
|
+
"items_total": result.items_total,
|
|
115
|
+
},
|
|
116
|
+
indent=2,
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
print(result.content)
|
|
121
|
+
print(
|
|
122
|
+
f"\n# Pack stats: {result.total_tokens}/{result.budget} tokens, "
|
|
123
|
+
f"{result.items_used}/{result.items_total} items",
|
|
124
|
+
file=sys.stderr,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if vector_store and hasattr(vector_store, "close"):
|
|
128
|
+
vector_store.close()
|
|
129
|
+
return 0
|
memvcs/commands/pull.py
CHANGED
|
@@ -37,7 +37,9 @@ class PullCommand:
|
|
|
37
37
|
|
|
38
38
|
remote = Remote(repo.root, args.remote)
|
|
39
39
|
if not remote.get_remote_url():
|
|
40
|
-
print(
|
|
40
|
+
print(
|
|
41
|
+
f"Error: Remote '{args.remote}' has no URL. Set with: agmem remote add {args.remote} <url>"
|
|
42
|
+
)
|
|
41
43
|
return 1
|
|
42
44
|
|
|
43
45
|
try:
|
|
@@ -50,6 +52,7 @@ class PullCommand:
|
|
|
50
52
|
remote_hash = repo.resolve_ref(remote_ref)
|
|
51
53
|
if remote_hash:
|
|
52
54
|
from memvcs.core.merge import MergeEngine
|
|
55
|
+
|
|
53
56
|
merge_engine = MergeEngine(repo)
|
|
54
57
|
try:
|
|
55
58
|
result = merge_engine.merge(remote_ref)
|
memvcs/commands/push.py
CHANGED
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
class MemoryConflictError(Exception):
|
|
10
10
|
"""Exception raised when push fails due to conflicts."""
|
|
11
|
+
|
|
11
12
|
pass
|
|
12
13
|
|
|
13
14
|
|
|
@@ -31,7 +32,8 @@ class PushCommand:
|
|
|
31
32
|
help="Branch to push (default: current)",
|
|
32
33
|
)
|
|
33
34
|
parser.add_argument(
|
|
34
|
-
"--force",
|
|
35
|
+
"--force",
|
|
36
|
+
"-f",
|
|
35
37
|
action="store_true",
|
|
36
38
|
help="Force push (WARNING: may overwrite remote changes)",
|
|
37
39
|
)
|
|
@@ -53,7 +55,7 @@ class PushCommand:
|
|
|
53
55
|
|
|
54
56
|
remote = Remote(repo.root, args.remote)
|
|
55
57
|
remote_url = remote.get_remote_url()
|
|
56
|
-
|
|
58
|
+
|
|
57
59
|
if not remote_url:
|
|
58
60
|
print(f"Error: Remote '{args.remote}' has no URL.")
|
|
59
61
|
print(f"Set with: agmem remote add {args.remote} <url>")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agmem recall - Context-aware retrieval with pluggable strategies.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ..commands.base import require_repo
|
|
11
|
+
from ..core.access_index import AccessIndex
|
|
12
|
+
from ..retrieval import RecallEngine
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_vector_unavailable_error(exc: Exception) -> bool:
|
|
16
|
+
"""True if exception indicates vector deps are missing."""
|
|
17
|
+
msg = str(exc).lower()
|
|
18
|
+
return any(
|
|
19
|
+
key in msg
|
|
20
|
+
for key in ("sqlite-vec", "sentence-transformers", "vector search", "agmem[vector]")
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RecallCommand:
|
|
25
|
+
"""Context-aware recall with pluggable strategies."""
|
|
26
|
+
|
|
27
|
+
name = "recall"
|
|
28
|
+
help = "Recall curated memories for the current task (context-aware retrieval)"
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def add_arguments(parser: argparse.ArgumentParser):
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--context",
|
|
34
|
+
"-c",
|
|
35
|
+
default="",
|
|
36
|
+
help="Current task description (used for embedding similarity)",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--strategy",
|
|
40
|
+
"-s",
|
|
41
|
+
choices=["recency", "importance", "similarity", "hybrid"],
|
|
42
|
+
default="hybrid",
|
|
43
|
+
help="Recall strategy (default: hybrid)",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--limit",
|
|
47
|
+
"-n",
|
|
48
|
+
type=int,
|
|
49
|
+
default=10,
|
|
50
|
+
help="Max chunks to return (default: 10)",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--exclude",
|
|
54
|
+
"-e",
|
|
55
|
+
action="append",
|
|
56
|
+
default=[],
|
|
57
|
+
help="Tags/paths to exclude (e.g., experiment/*); repeatable",
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--no-cache",
|
|
61
|
+
action="store_true",
|
|
62
|
+
help="Disable recall cache",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--format",
|
|
66
|
+
"-f",
|
|
67
|
+
choices=["json", "text"],
|
|
68
|
+
default="json",
|
|
69
|
+
help="Output format (default: json)",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def execute(args) -> int:
|
|
74
|
+
repo, code = require_repo()
|
|
75
|
+
if code != 0:
|
|
76
|
+
return code
|
|
77
|
+
|
|
78
|
+
vector_store = None
|
|
79
|
+
try:
|
|
80
|
+
from ..core.vector_store import VectorStore
|
|
81
|
+
|
|
82
|
+
vector_store = VectorStore(repo.mem_dir)
|
|
83
|
+
except ImportError:
|
|
84
|
+
if args.strategy in ("similarity", "hybrid"):
|
|
85
|
+
print(
|
|
86
|
+
"Error: Strategy '{}' requires agmem[vector]. "
|
|
87
|
+
"Install with: pip install agmem[vector]".format(args.strategy),
|
|
88
|
+
file=sys.stderr,
|
|
89
|
+
)
|
|
90
|
+
return 1
|
|
91
|
+
if args.strategy == "hybrid":
|
|
92
|
+
args.strategy = "recency"
|
|
93
|
+
print("Note: Falling back to recency (vector store not available)")
|
|
94
|
+
|
|
95
|
+
access_index = AccessIndex(repo.mem_dir)
|
|
96
|
+
engine = RecallEngine(
|
|
97
|
+
repo=repo,
|
|
98
|
+
vector_store=vector_store,
|
|
99
|
+
access_index=access_index,
|
|
100
|
+
use_cache=not args.no_cache,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
results = engine.recall(
|
|
105
|
+
context=args.context,
|
|
106
|
+
limit=args.limit,
|
|
107
|
+
strategy=args.strategy,
|
|
108
|
+
exclude=args.exclude,
|
|
109
|
+
)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
if _is_vector_unavailable_error(e):
|
|
112
|
+
if args.strategy in ("similarity", "hybrid"):
|
|
113
|
+
print(
|
|
114
|
+
"Error: Vector search unavailable. Try --strategy recency or importance.",
|
|
115
|
+
file=sys.stderr,
|
|
116
|
+
)
|
|
117
|
+
return 1
|
|
118
|
+
engine = RecallEngine(
|
|
119
|
+
repo=repo,
|
|
120
|
+
vector_store=None,
|
|
121
|
+
access_index=access_index,
|
|
122
|
+
use_cache=not args.no_cache,
|
|
123
|
+
)
|
|
124
|
+
results = engine.recall(
|
|
125
|
+
context=args.context,
|
|
126
|
+
limit=args.limit,
|
|
127
|
+
strategy="recency",
|
|
128
|
+
exclude=args.exclude,
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
if args.format == "json":
|
|
134
|
+
output = [r.to_dict() for r in results]
|
|
135
|
+
print(json.dumps(output, indent=2))
|
|
136
|
+
else:
|
|
137
|
+
for r in results:
|
|
138
|
+
print(f"\n--- {r.path} (score: {r.relevance_score:.4f}) ---")
|
|
139
|
+
print(r.content[:500] + ("..." if len(r.content) > 500 else ""))
|
|
140
|
+
if r.importance is not None:
|
|
141
|
+
print(f"(importance: {r.importance})")
|
|
142
|
+
|
|
143
|
+
if vector_store and hasattr(vector_store, "close"):
|
|
144
|
+
vector_store.close()
|
|
145
|
+
return 0
|
memvcs/commands/reflog.py
CHANGED
|
@@ -11,42 +11,33 @@ from ..core.repository import Repository
|
|
|
11
11
|
|
|
12
12
|
class ReflogCommand:
|
|
13
13
|
"""Show reflog - history of HEAD changes."""
|
|
14
|
-
|
|
15
|
-
name =
|
|
16
|
-
help =
|
|
17
|
-
|
|
14
|
+
|
|
15
|
+
name = "reflog"
|
|
16
|
+
help = "Show reference log (history of HEAD changes)"
|
|
17
|
+
|
|
18
18
|
@staticmethod
|
|
19
19
|
def add_arguments(parser: argparse.ArgumentParser):
|
|
20
|
+
parser.add_argument("ref", nargs="?", default="HEAD", help="Reference to show log for")
|
|
20
21
|
parser.add_argument(
|
|
21
|
-
|
|
22
|
-
nargs='?',
|
|
23
|
-
default='HEAD',
|
|
24
|
-
help='Reference to show log for'
|
|
25
|
-
)
|
|
26
|
-
parser.add_argument(
|
|
27
|
-
'-n', '--max-count',
|
|
28
|
-
type=int,
|
|
29
|
-
default=20,
|
|
30
|
-
help='Maximum number of entries'
|
|
22
|
+
"-n", "--max-count", type=int, default=20, help="Maximum number of entries"
|
|
31
23
|
)
|
|
32
|
-
|
|
24
|
+
|
|
33
25
|
@staticmethod
|
|
34
26
|
def execute(args) -> int:
|
|
35
27
|
repo, code = require_repo()
|
|
36
28
|
if code != 0:
|
|
37
29
|
return code
|
|
38
30
|
|
|
39
|
-
|
|
40
31
|
entries = repo.refs.get_reflog(args.ref, args.max_count)
|
|
41
|
-
|
|
32
|
+
|
|
42
33
|
if not entries:
|
|
43
34
|
print("No reflog entries found.")
|
|
44
35
|
return 0
|
|
45
|
-
|
|
36
|
+
|
|
46
37
|
for e in entries:
|
|
47
|
-
h = e[
|
|
48
|
-
ts = e.get(
|
|
49
|
-
msg = e.get(
|
|
38
|
+
h = e["hash"][:8]
|
|
39
|
+
ts = e.get("timestamp", "")[:19]
|
|
40
|
+
msg = e.get("message", "")
|
|
50
41
|
print(f"{h} {ts} {msg}")
|
|
51
|
-
|
|
42
|
+
|
|
52
43
|
return 0
|
memvcs/commands/remote.py
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agmem repair - Auto-fix belief contradictions.
|
|
3
|
+
|
|
4
|
+
Repairs semantic memory contradictions using configurable strategy.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ..commands.base import require_repo
|
|
11
|
+
from ..core.consistency import ConsistencyChecker, ConsistencyResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RepairCommand:
|
|
15
|
+
"""Repair belief contradictions in semantic memories."""
|
|
16
|
+
|
|
17
|
+
name = "repair"
|
|
18
|
+
help = "Auto-fix contradictions using confidence scores"
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def add_arguments(parser: argparse.ArgumentParser):
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--strategy",
|
|
24
|
+
"-s",
|
|
25
|
+
choices=["confidence", "recency", "llm"],
|
|
26
|
+
default="confidence",
|
|
27
|
+
help="Repair strategy (default: confidence)",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--dry-run",
|
|
31
|
+
action="store_true",
|
|
32
|
+
help="Show what would be fixed without making changes",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def execute(args) -> int:
|
|
37
|
+
repo, code = require_repo()
|
|
38
|
+
if code != 0:
|
|
39
|
+
return code
|
|
40
|
+
|
|
41
|
+
checker = ConsistencyChecker(repo, llm_provider="openai")
|
|
42
|
+
result = checker.repair(strategy=args.strategy)
|
|
43
|
+
|
|
44
|
+
if result.valid:
|
|
45
|
+
print("No contradictions to repair.")
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
if args.dry_run:
|
|
49
|
+
print(f"Would repair {len(result.contradictions)} contradiction(s):")
|
|
50
|
+
for c in result.contradictions:
|
|
51
|
+
print(
|
|
52
|
+
f" - {c.triple1.source}:{c.triple1.line} vs {c.triple2.source}:{c.triple2.line}"
|
|
53
|
+
)
|
|
54
|
+
print("\nRun without --dry-run to apply repairs.")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
# Repair: keep higher-confidence triple, flag the other
|
|
58
|
+
# For now we only report - full repair would modify files
|
|
59
|
+
print(f"Found {len(result.contradictions)} contradiction(s).")
|
|
60
|
+
print("Manual repair required - edit the semantic files to resolve.")
|
|
61
|
+
for c in result.contradictions:
|
|
62
|
+
keep = c.triple1 if c.triple1.confidence >= c.triple2.confidence else c.triple2
|
|
63
|
+
drop = c.triple2 if keep is c.triple1 else c.triple1
|
|
64
|
+
print(f" Keep: {keep.source}:{keep.line} (confidence {keep.confidence})")
|
|
65
|
+
print(f" Review: {drop.source}:{drop.line} (confidence {drop.confidence})")
|
|
66
|
+
return 1
|