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.
Files changed (80) hide show
  1. {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/METADATA +20 -3
  2. agmem-0.1.2.dist-info/RECORD +86 -0
  3. memvcs/__init__.py +1 -1
  4. memvcs/cli.py +35 -31
  5. memvcs/commands/__init__.py +9 -9
  6. memvcs/commands/add.py +77 -76
  7. memvcs/commands/blame.py +46 -53
  8. memvcs/commands/branch.py +13 -33
  9. memvcs/commands/checkout.py +27 -32
  10. memvcs/commands/clean.py +18 -23
  11. memvcs/commands/clone.py +4 -1
  12. memvcs/commands/commit.py +40 -39
  13. memvcs/commands/daemon.py +81 -76
  14. memvcs/commands/decay.py +77 -0
  15. memvcs/commands/diff.py +56 -57
  16. memvcs/commands/distill.py +74 -0
  17. memvcs/commands/fsck.py +55 -61
  18. memvcs/commands/garden.py +28 -37
  19. memvcs/commands/graph.py +41 -48
  20. memvcs/commands/init.py +16 -24
  21. memvcs/commands/log.py +25 -40
  22. memvcs/commands/merge.py +16 -28
  23. memvcs/commands/pack.py +129 -0
  24. memvcs/commands/pull.py +4 -1
  25. memvcs/commands/push.py +4 -2
  26. memvcs/commands/recall.py +145 -0
  27. memvcs/commands/reflog.py +13 -22
  28. memvcs/commands/remote.py +1 -0
  29. memvcs/commands/repair.py +66 -0
  30. memvcs/commands/reset.py +23 -33
  31. memvcs/commands/resurrect.py +82 -0
  32. memvcs/commands/search.py +3 -4
  33. memvcs/commands/serve.py +2 -1
  34. memvcs/commands/show.py +66 -36
  35. memvcs/commands/stash.py +34 -34
  36. memvcs/commands/status.py +27 -35
  37. memvcs/commands/tag.py +23 -47
  38. memvcs/commands/test.py +30 -44
  39. memvcs/commands/timeline.py +111 -0
  40. memvcs/commands/tree.py +26 -27
  41. memvcs/commands/verify.py +59 -0
  42. memvcs/commands/when.py +115 -0
  43. memvcs/core/access_index.py +167 -0
  44. memvcs/core/config_loader.py +3 -1
  45. memvcs/core/consistency.py +214 -0
  46. memvcs/core/decay.py +185 -0
  47. memvcs/core/diff.py +158 -143
  48. memvcs/core/distiller.py +277 -0
  49. memvcs/core/gardener.py +164 -132
  50. memvcs/core/hooks.py +48 -14
  51. memvcs/core/knowledge_graph.py +134 -138
  52. memvcs/core/merge.py +248 -171
  53. memvcs/core/objects.py +95 -96
  54. memvcs/core/pii_scanner.py +147 -146
  55. memvcs/core/refs.py +132 -115
  56. memvcs/core/repository.py +174 -164
  57. memvcs/core/schema.py +155 -113
  58. memvcs/core/staging.py +60 -65
  59. memvcs/core/storage/__init__.py +20 -18
  60. memvcs/core/storage/base.py +74 -70
  61. memvcs/core/storage/gcs.py +70 -68
  62. memvcs/core/storage/local.py +42 -40
  63. memvcs/core/storage/s3.py +105 -110
  64. memvcs/core/temporal_index.py +112 -0
  65. memvcs/core/test_runner.py +101 -93
  66. memvcs/core/vector_store.py +41 -35
  67. memvcs/integrations/mcp_server.py +1 -3
  68. memvcs/integrations/web_ui/server.py +25 -26
  69. memvcs/retrieval/__init__.py +22 -0
  70. memvcs/retrieval/base.py +54 -0
  71. memvcs/retrieval/pack.py +128 -0
  72. memvcs/retrieval/recaller.py +105 -0
  73. memvcs/retrieval/strategies.py +314 -0
  74. memvcs/utils/__init__.py +3 -3
  75. memvcs/utils/helpers.py +52 -52
  76. agmem-0.1.1.dist-info/RECORD +0 -67
  77. {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/WHEEL +0 -0
  78. {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/entry_points.txt +0 -0
  79. {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/licenses/LICENSE +0 -0
  80. {agmem-0.1.1.dist-info → agmem-0.1.2.dist-info}/top_level.txt +0 -0
memvcs/commands/reset.py CHANGED
@@ -11,34 +11,25 @@ from ..core.repository import Repository
11
11
 
12
12
  class ResetCommand:
13
13
  """Reset current HEAD to the specified state."""
14
-
15
- name = 'reset'
16
- help = 'Reset current HEAD to the specified state'
17
-
14
+
15
+ name = "reset"
16
+ help = "Reset current HEAD to the specified state"
17
+
18
18
  @staticmethod
19
19
  def add_arguments(parser: argparse.ArgumentParser):
20
20
  parser.add_argument(
21
- 'commit',
22
- nargs='?',
23
- default='HEAD',
24
- help='Commit to reset to (default: HEAD)'
21
+ "commit", nargs="?", default="HEAD", help="Commit to reset to (default: HEAD)"
25
22
  )
26
23
  parser.add_argument(
27
- '--soft',
28
- action='store_true',
29
- help='Reset HEAD but keep staged changes'
24
+ "--soft", action="store_true", help="Reset HEAD but keep staged changes"
30
25
  )
31
26
  parser.add_argument(
32
- '--mixed',
33
- action='store_true',
34
- help='Reset HEAD and unstaged changes (default)'
27
+ "--mixed", action="store_true", help="Reset HEAD and unstaged changes (default)"
35
28
  )
36
29
  parser.add_argument(
37
- '--hard',
38
- action='store_true',
39
- help='Reset HEAD, index, and working tree'
30
+ "--hard", action="store_true", help="Reset HEAD, index, and working tree"
40
31
  )
41
-
32
+
42
33
  @staticmethod
43
34
  def execute(args) -> int:
44
35
  # Find repository
@@ -46,53 +37,52 @@ class ResetCommand:
46
37
  if code != 0:
47
38
  return code
48
39
 
49
-
50
40
  # Determine mode
51
41
  if args.soft:
52
- mode = 'soft'
42
+ mode = "soft"
53
43
  elif args.hard:
54
- mode = 'hard'
44
+ mode = "hard"
55
45
  else:
56
- mode = 'mixed'
57
-
46
+ mode = "mixed"
47
+
58
48
  # Resolve commit
59
49
  commit_hash = repo.resolve_ref(args.commit)
60
50
  if not commit_hash:
61
51
  print(f"Error: Unknown revision: {args.commit}")
62
52
  return 1
63
-
53
+
64
54
  # Get current branch
65
55
  current_branch = repo.refs.get_current_branch()
66
-
56
+
67
57
  try:
68
- if mode == 'soft':
58
+ if mode == "soft":
69
59
  # Just move HEAD
70
60
  if current_branch:
71
61
  repo.refs.set_branch_commit(current_branch, commit_hash)
72
62
  else:
73
63
  repo.refs.set_head_detached(commit_hash)
74
64
  print(f"HEAD is now at {commit_hash[:8]}")
75
-
76
- elif mode == 'mixed':
65
+
66
+ elif mode == "mixed":
77
67
  # Move HEAD and clear staging
78
68
  if current_branch:
79
69
  repo.refs.set_branch_commit(current_branch, commit_hash)
80
70
  else:
81
71
  repo.refs.set_head_detached(commit_hash)
82
-
72
+
83
73
  # Keep staged files but mark them as unstaged
84
74
  # (In a full implementation, we'd restore the tree state)
85
75
  print(f"HEAD is now at {commit_hash[:8]}")
86
76
  print("Staged changes have been unstaged.")
87
-
88
- elif mode == 'hard':
77
+
78
+ elif mode == "hard":
89
79
  # Move HEAD, clear staging, and restore working tree
90
80
  repo.checkout(commit_hash, force=True)
91
81
  print(f"HEAD is now at {commit_hash[:8]}")
92
82
  print("Working tree has been reset.")
93
-
83
+
94
84
  return 0
95
-
85
+
96
86
  except Exception as e:
97
87
  print(f"Error during reset: {e}")
98
88
  return 1
@@ -0,0 +1,82 @@
1
+ """
2
+ agmem resurrect - Restore archived (decayed) memories.
3
+ """
4
+
5
+ import argparse
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ from ..commands.base import require_repo
10
+
11
+
12
+ class ResurrectCommand:
13
+ """Restore memories from .mem/forgetting/."""
14
+
15
+ name = "resurrect"
16
+ help = "Restore archived (decayed) memories from forgetting/"
17
+
18
+ @staticmethod
19
+ def add_arguments(parser: argparse.ArgumentParser):
20
+ parser.add_argument(
21
+ "path",
22
+ nargs="?",
23
+ help="Path or pattern to restore (e.g., semantic/user-prefs.md)",
24
+ )
25
+ parser.add_argument(
26
+ "--list",
27
+ "-l",
28
+ action="store_true",
29
+ help="List archived memories",
30
+ )
31
+
32
+ @staticmethod
33
+ def execute(args) -> int:
34
+ repo, code = require_repo()
35
+ if code != 0:
36
+ return code
37
+
38
+ forgetting_dir = repo.mem_dir / "forgetting"
39
+ if not forgetting_dir.exists():
40
+ print("No forgotten memories found.")
41
+ return 0
42
+
43
+ if args.list:
44
+ for sub in sorted(forgetting_dir.iterdir()):
45
+ if sub.is_dir():
46
+ print(f"\n{sub.name}:")
47
+ for f in sorted(sub.glob("*")):
48
+ if f.is_file():
49
+ print(f" - {f.name}")
50
+ return 0
51
+
52
+ if not args.path:
53
+ print("Error: Path to restore is required.")
54
+ print("Usage: agmem resurrect <path>")
55
+ print(" agmem resurrect --list")
56
+ return 1
57
+
58
+ # Find archived file
59
+ pattern = args.path.replace("/", "_")
60
+ found = []
61
+ for sub in forgetting_dir.iterdir():
62
+ if sub.is_dir():
63
+ for f in sub.glob("*"):
64
+ if f.is_file() and (pattern in f.name or f.name == args.path):
65
+ found.append(f)
66
+
67
+ if not found:
68
+ print(f"No archived memory matching '{args.path}' found.")
69
+ print("Use 'agmem resurrect --list' to see available archives.")
70
+ return 1
71
+
72
+ for archived in found:
73
+ # Restore to current/
74
+ orig_path = (
75
+ archived.name.replace("_", "/", 1) if "_" in archived.name else archived.name
76
+ )
77
+ dest = repo.current_dir / orig_path
78
+ dest.parent.mkdir(parents=True, exist_ok=True)
79
+ shutil.copy2(str(archived), str(dest))
80
+ print(f"Restored {archived.name} -> {orig_path}")
81
+
82
+ return 0
memvcs/commands/search.py CHANGED
@@ -10,9 +10,7 @@ from pathlib import Path
10
10
  def _is_vector_unavailable_error(exc: Exception) -> bool:
11
11
  """True if the exception indicates vector deps are missing (fall back to text search)."""
12
12
  msg = str(exc).lower()
13
- return any(
14
- key in msg for key in ("sqlite-vec", "sentence-transformers", "vector search")
15
- )
13
+ return any(key in msg for key in ("sqlite-vec", "sentence-transformers", "vector search"))
16
14
 
17
15
 
18
16
  def _first_line_containing(content: str, query: str, max_len: int = 200) -> str:
@@ -38,7 +36,8 @@ class SearchCommand:
38
36
  help="Search query for semantic search",
39
37
  )
40
38
  parser.add_argument(
41
- "--limit", "-n",
39
+ "--limit",
40
+ "-n",
42
41
  type=int,
43
42
  default=10,
44
43
  help="Maximum results to return (default: 10)",
memvcs/commands/serve.py CHANGED
@@ -16,7 +16,8 @@ class ServeCommand:
16
16
  @staticmethod
17
17
  def add_arguments(parser: argparse.ArgumentParser):
18
18
  parser.add_argument(
19
- "--port", "-p",
19
+ "--port",
20
+ "-p",
20
21
  type=int,
21
22
  default=8765,
22
23
  help="Port to bind (default: 8765)",
memvcs/commands/show.py CHANGED
@@ -4,6 +4,7 @@ agmem show - Show various types of objects.
4
4
 
5
5
  import argparse
6
6
  import json
7
+ import sys
7
8
  from pathlib import Path
8
9
 
9
10
  from ..commands.base import require_repo
@@ -13,28 +14,27 @@ from ..core.repository import Repository
13
14
 
14
15
  class ShowCommand:
15
16
  """Show various types of objects."""
16
-
17
- name = 'show'
18
- help = 'Show various types of objects'
19
-
17
+
18
+ name = "show"
19
+ help = "Show various types of objects"
20
+
20
21
  @staticmethod
21
22
  def add_arguments(parser: argparse.ArgumentParser):
23
+ parser.add_argument("object", help="Object to show (commit, tree, blob, branch)")
22
24
  parser.add_argument(
23
- 'object',
24
- help='Object to show (commit, tree, blob, branch)'
25
+ "--type",
26
+ "-t",
27
+ choices=["commit", "tree", "blob", "auto"],
28
+ default="auto",
29
+ help="Type of object to show",
25
30
  )
31
+ parser.add_argument("--raw", action="store_true", help="Show raw object content")
26
32
  parser.add_argument(
27
- '--type', '-t',
28
- choices=['commit', 'tree', 'blob', 'auto'],
29
- default='auto',
30
- help='Type of object to show'
33
+ "--at",
34
+ metavar="TIMESTAMP",
35
+ help="View file at specific time (ISO 8601, e.g., 2025-12-01T14:00:00)",
31
36
  )
32
- parser.add_argument(
33
- '--raw',
34
- action='store_true',
35
- help='Show raw object content'
36
- )
37
-
37
+
38
38
  @staticmethod
39
39
  def execute(args) -> int:
40
40
  # Find repository
@@ -42,11 +42,41 @@ class ShowCommand:
42
42
  if code != 0:
43
43
  return code
44
44
 
45
-
46
45
  obj_ref = args.object
47
46
  obj_hash = None
48
47
  obj_type = args.type
49
-
48
+
49
+ # --at: show file at timestamp
50
+ if args.at:
51
+ commit_hash = repo.resolve_ref(args.at)
52
+ if not commit_hash:
53
+ print(f"Error: No commit found at {args.at}", file=sys.stderr)
54
+ return 1
55
+ # Treat object as file path under current/
56
+ file_path = obj_ref.replace("current/", "").lstrip("/")
57
+ tree = repo.get_commit_tree(commit_hash)
58
+ if not tree:
59
+ print(
60
+ f"Error: Tree not found for commit at {args.at}", file=__import__("sys").stderr
61
+ )
62
+ return 1
63
+ # Find blob for path (tree has flat entries with path)
64
+ blob_hash = None
65
+ for entry in tree.entries:
66
+ full_path = (
67
+ (entry.path + "/" + entry.name).lstrip("/") if entry.path else entry.name
68
+ )
69
+ if full_path == file_path:
70
+ blob_hash = entry.hash
71
+ break
72
+ if not blob_hash:
73
+ print(f"Error: File {file_path} not found at {args.at}", file=sys.stderr)
74
+ return 1
75
+ blob = Blob.load(repo.object_store, blob_hash)
76
+ if blob:
77
+ print(blob.content.decode("utf-8", errors="replace"))
78
+ return 0
79
+
50
80
  # Try to resolve as reference
51
81
  resolved = repo.resolve_ref(obj_ref)
52
82
  if resolved:
@@ -54,34 +84,34 @@ class ShowCommand:
54
84
  else:
55
85
  # Assume it's a hash
56
86
  obj_hash = obj_ref
57
-
87
+
58
88
  # Try to determine type if auto
59
- if obj_type == 'auto':
89
+ if obj_type == "auto":
60
90
  # Try commit first
61
91
  commit = Commit.load(repo.object_store, obj_hash)
62
92
  if commit:
63
- obj_type = 'commit'
93
+ obj_type = "commit"
64
94
  else:
65
95
  # Try tree
66
96
  tree = Tree.load(repo.object_store, obj_hash)
67
97
  if tree:
68
- obj_type = 'tree'
98
+ obj_type = "tree"
69
99
  else:
70
100
  # Try blob
71
101
  blob = Blob.load(repo.object_store, obj_hash)
72
102
  if blob:
73
- obj_type = 'blob'
103
+ obj_type = "blob"
74
104
  else:
75
105
  print(f"Error: Object not found: {obj_ref}")
76
106
  return 1
77
-
107
+
78
108
  # Display based on type
79
- if obj_type == 'commit':
109
+ if obj_type == "commit":
80
110
  commit = Commit.load(repo.object_store, obj_hash)
81
111
  if not commit:
82
112
  print(f"Error: Commit not found: {obj_ref}")
83
113
  return 1
84
-
114
+
85
115
  print(f"commit {obj_hash}")
86
116
  print(f"Author: {commit.author}")
87
117
  print(f"Date: {commit.timestamp}")
@@ -91,35 +121,35 @@ class ShowCommand:
91
121
  print(f"tree {commit.tree}")
92
122
  if commit.parents:
93
123
  print(f"parent {' '.join(commit.parents)}")
94
-
95
- elif obj_type == 'tree':
124
+
125
+ elif obj_type == "tree":
96
126
  tree = Tree.load(repo.object_store, obj_hash)
97
127
  if not tree:
98
128
  print(f"Error: Tree not found: {obj_ref}")
99
129
  return 1
100
-
130
+
101
131
  print(f"tree {obj_hash}")
102
132
  print()
103
133
  for entry in sorted(tree.entries, key=lambda e: e.name):
104
- path = entry.path + '/' + entry.name if entry.path else entry.name
134
+ path = entry.path + "/" + entry.name if entry.path else entry.name
105
135
  print(f"{entry.mode} {entry.type} {entry.hash[:8]}\t{path}")
106
-
107
- elif obj_type == 'blob':
136
+
137
+ elif obj_type == "blob":
108
138
  blob = Blob.load(repo.object_store, obj_hash)
109
139
  if not blob:
110
140
  print(f"Error: Blob not found: {obj_ref}")
111
141
  return 1
112
-
142
+
113
143
  if args.raw:
114
- print(blob.content.decode('utf-8', errors='replace'))
144
+ print(blob.content.decode("utf-8", errors="replace"))
115
145
  else:
116
146
  print(f"blob {obj_hash}")
117
147
  print(f"Size: {len(blob.content)} bytes")
118
148
  print()
119
- content = blob.content.decode('utf-8', errors='replace')
149
+ content = blob.content.decode("utf-8", errors="replace")
120
150
  if len(content) > 1000:
121
151
  print(content[:1000] + "\n... (truncated)")
122
152
  else:
123
153
  print(content)
124
-
154
+
125
155
  return 0
memvcs/commands/stash.py CHANGED
@@ -11,52 +11,51 @@ from ..core.repository import Repository
11
11
 
12
12
  class StashCommand:
13
13
  """Stash working directory changes."""
14
-
15
- name = 'stash'
16
- help = 'Stash changes for later (save work in progress)'
17
-
14
+
15
+ name = "stash"
16
+ help = "Stash changes for later (save work in progress)"
17
+
18
18
  @staticmethod
19
19
  def add_arguments(parser: argparse.ArgumentParser):
20
- subparsers = parser.add_subparsers(dest='stash_action', help='Stash action')
21
-
20
+ subparsers = parser.add_subparsers(dest="stash_action", help="Stash action")
21
+
22
22
  # Default: list (when no subcommand given)
23
- parser.set_defaults(stash_action='list')
24
-
23
+ parser.set_defaults(stash_action="list")
24
+
25
25
  # stash (push)
26
- push_p = subparsers.add_parser('push', help='Stash current changes')
27
- push_p.add_argument('-m', '--message', default='', help='Stash message')
28
-
26
+ push_p = subparsers.add_parser("push", help="Stash current changes")
27
+ push_p.add_argument("-m", "--message", default="", help="Stash message")
28
+
29
29
  # stash pop
30
- subparsers.add_parser('pop', help='Apply and remove most recent stash')
31
-
30
+ subparsers.add_parser("pop", help="Apply and remove most recent stash")
31
+
32
32
  # stash list (default)
33
- subparsers.add_parser('list', help='List stashes')
34
-
33
+ subparsers.add_parser("list", help="List stashes")
34
+
35
35
  # stash apply
36
- apply_p = subparsers.add_parser('apply', help='Apply stash without removing')
37
- apply_p.add_argument('stash_ref', nargs='?', default='stash@{0}', help='Stash reference')
38
-
36
+ apply_p = subparsers.add_parser("apply", help="Apply stash without removing")
37
+ apply_p.add_argument("stash_ref", nargs="?", default="stash@{0}", help="Stash reference")
38
+
39
39
  @staticmethod
40
40
  def execute(args) -> int:
41
41
  repo, code = require_repo()
42
42
  if code != 0:
43
43
  return code
44
44
 
45
-
46
- action = getattr(args, 'stash_action', None)
45
+ action = getattr(args, "stash_action", None)
47
46
  if action is None:
48
- action = 'list'
49
-
50
- if action == 'push':
51
- stash_hash = repo.stash_create(getattr(args, 'message', '') or '')
47
+ action = "list"
48
+
49
+ if action == "push":
50
+ stash_hash = repo.stash_create(getattr(args, "message", "") or "")
52
51
  if stash_hash:
53
52
  print(f"Stashed changes (stash@{0})")
54
53
  return 0
55
54
  else:
56
55
  print("No local changes to stash.")
57
56
  return 0
58
-
59
- elif action == 'pop':
57
+
58
+ elif action == "pop":
60
59
  stash_hash = repo.stash_pop(0)
61
60
  if stash_hash:
62
61
  print(f"Restored stashed changes")
@@ -64,25 +63,26 @@ class StashCommand:
64
63
  else:
65
64
  print("No stash entries found.")
66
65
  return 1
67
-
68
- elif action == 'list':
66
+
67
+ elif action == "list":
69
68
  stashes = repo.refs.stash_list()
70
69
  if not stashes:
71
70
  print("No stash entries found.")
72
71
  return 0
73
72
  for i, s in enumerate(stashes):
74
- msg = s.get('message', 'WIP')
75
- h = s.get('hash', '')[:8]
73
+ msg = s.get("message", "WIP")
74
+ h = s.get("hash", "")[:8]
76
75
  print(f"stash@{{{i}}}: {h} {msg}")
77
76
  return 0
78
-
79
- elif action == 'apply':
80
- ref = getattr(args, 'stash_ref', 'stash@{0}')
77
+
78
+ elif action == "apply":
79
+ ref = getattr(args, "stash_ref", "stash@{0}")
81
80
  commit_hash = repo.resolve_ref(ref)
82
81
  if not commit_hash:
83
82
  print(f"Error: Stash not found: {ref}")
84
83
  return 1
85
84
  from ..core.objects import Tree, Blob
85
+
86
86
  tree = repo.get_commit_tree(commit_hash)
87
87
  if tree:
88
88
  for entry in tree.entries:
@@ -93,5 +93,5 @@ class StashCommand:
93
93
  fp.write_bytes(blob.content)
94
94
  print("Applied stash (changes in working directory)")
95
95
  return 0
96
-
96
+
97
97
  return 1
memvcs/commands/status.py CHANGED
@@ -10,23 +10,15 @@ from ..core.repository import Repository
10
10
 
11
11
  class StatusCommand:
12
12
  """Show the working tree status."""
13
-
14
- name = 'status'
15
- help = 'Show the working tree status'
16
-
13
+
14
+ name = "status"
15
+ help = "Show the working tree status"
16
+
17
17
  @staticmethod
18
18
  def add_arguments(parser: argparse.ArgumentParser):
19
- parser.add_argument(
20
- '--short', '-s',
21
- action='store_true',
22
- help='Show short format'
23
- )
24
- parser.add_argument(
25
- '--branch', '-b',
26
- action='store_true',
27
- help='Show branch information'
28
- )
29
-
19
+ parser.add_argument("--short", "-s", action="store_true", help="Show short format")
20
+ parser.add_argument("--branch", "-b", action="store_true", help="Show branch information")
21
+
30
22
  @staticmethod
31
23
  def execute(args) -> int:
32
24
  repo, code = require_repo()
@@ -34,35 +26,35 @@ class StatusCommand:
34
26
  return code
35
27
 
36
28
  status = repo.get_status()
37
-
29
+
38
30
  # Show branch info
39
- branch = status.get('branch')
40
- head = status.get('head', {})
41
-
31
+ branch = status.get("branch")
32
+ head = status.get("head", {})
33
+
42
34
  if args.short:
43
35
  # Short format: XY filename
44
- for f in status.get('staged', []):
36
+ for f in status.get("staged", []):
45
37
  print(f"A {f}")
46
- for f in status.get('modified', []):
38
+ for f in status.get("modified", []):
47
39
  print(f" M {f}")
48
- for f in status.get('deleted', []):
40
+ for f in status.get("deleted", []):
49
41
  print(f" D {f}")
50
- for f in status.get('untracked', []):
42
+ for f in status.get("untracked", []):
51
43
  print(f"?? {f}")
52
44
  else:
53
45
  # Long format
54
46
  if branch:
55
47
  print(f"On branch {branch}")
56
- elif head.get('type') == 'commit':
48
+ elif head.get("type") == "commit":
57
49
  print(f"HEAD detached at {head['value'][:8]}")
58
-
50
+
59
51
  # Check for commits
60
52
  head_commit = repo.get_head_commit()
61
53
  if not head_commit:
62
54
  print("\nNo commits yet")
63
-
55
+
64
56
  # Staged changes
65
- staged = status.get('staged', [])
57
+ staged = status.get("staged", [])
66
58
  if staged:
67
59
  print(f"\nChanges to be committed:")
68
60
  print(f' (use "agmem reset HEAD <file>..." to unstage)')
@@ -70,9 +62,9 @@ class StatusCommand:
70
62
  for f in staged:
71
63
  print(f" new file: {f}")
72
64
  print()
73
-
65
+
74
66
  # Modified but not staged
75
- modified = status.get('modified', [])
67
+ modified = status.get("modified", [])
76
68
  if modified:
77
69
  print(f"Changes not staged for commit:")
78
70
  print(f' (use "agmem add <file>..." to update what will be committed)')
@@ -80,9 +72,9 @@ class StatusCommand:
80
72
  for f in modified:
81
73
  print(f" modified: {f}")
82
74
  print()
83
-
75
+
84
76
  # Deleted but not staged
85
- deleted = status.get('deleted', [])
77
+ deleted = status.get("deleted", [])
86
78
  if deleted:
87
79
  print(f"Deleted files:")
88
80
  print(f' (use "agmem add <file>..." to stage deletion)')
@@ -90,9 +82,9 @@ class StatusCommand:
90
82
  for f in deleted:
91
83
  print(f" deleted: {f}")
92
84
  print()
93
-
85
+
94
86
  # Untracked files
95
- untracked = status.get('untracked', [])
87
+ untracked = status.get("untracked", [])
96
88
  if untracked:
97
89
  print(f"Untracked files:")
98
90
  print(f' (use "agmem add <file>..." to include in what will be committed)')
@@ -100,7 +92,7 @@ class StatusCommand:
100
92
  for f in untracked:
101
93
  print(f" {f}")
102
94
  print()
103
-
95
+
104
96
  # Summary
105
97
  total_changes = len(staged) + len(modified) + len(deleted) + len(untracked)
106
98
  if total_changes == 0:
@@ -108,5 +100,5 @@ class StatusCommand:
108
100
  print("nothing to commit, working tree clean")
109
101
  else:
110
102
  print('nothing to commit (create/copy files and use "agmem add" to track)')
111
-
103
+
112
104
  return 0