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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agmem
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Agentic Memory Version Control System - Git for AI agent memories
5
5
  Home-page: https://github.com/vivek-tiwari-vt/agmem
6
6
  Author: agmem Team
@@ -16,7 +16,6 @@ Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.8
20
19
  Classifier: Programming Language :: Python :: 3.9
21
20
  Classifier: Programming Language :: Python :: 3.10
22
21
  Classifier: Programming Language :: Python :: 3.11
@@ -30,7 +29,7 @@ Requires-Dist: PyYAML>=6.0.0
30
29
  Provides-Extra: dev
31
30
  Requires-Dist: pytest>=7.0.0; extra == "dev"
32
31
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
- Requires-Dist: black>=22.0.0; extra == "dev"
32
+ Requires-Dist: black==24.10.0; extra == "dev"
34
33
  Requires-Dist: flake8>=5.0.0; extra == "dev"
35
34
  Requires-Dist: mypy>=1.0.0; extra == "dev"
36
35
  Provides-Extra: mcp
@@ -53,6 +52,12 @@ Provides-Extra: daemon
53
52
  Requires-Dist: watchdog>=3.0.0; extra == "daemon"
54
53
  Provides-Extra: graph
55
54
  Requires-Dist: networkx>=3.0; extra == "graph"
55
+ Provides-Extra: pack
56
+ Requires-Dist: tiktoken>=0.5.0; extra == "pack"
57
+ Provides-Extra: distill
58
+ Requires-Dist: openai>=1.0.0; extra == "distill"
59
+ Provides-Extra: verify
60
+ Requires-Dist: openai>=1.0.0; extra == "verify"
56
61
  Provides-Extra: all
57
62
  Requires-Dist: mcp>=1.0.0; extra == "all"
58
63
  Requires-Dist: sqlite-vec>=0.1.0; extra == "all"
@@ -72,6 +77,7 @@ Dynamic: requires-python
72
77
 
73
78
  # agmem - Agentic Memory Version Control System
74
79
 
80
+ [![PyPI version](https://img.shields.io/pypi/v/agmem.svg)](https://pypi.org/project/agmem/)
75
81
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
76
82
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
77
83
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -109,6 +115,8 @@ agmem solves all of these problems with a familiar Git-like interface.
109
115
 
110
116
  ### Installation
111
117
 
118
+ **From PyPI** ([pypi.org/project/agmem](https://pypi.org/project/agmem/)):
119
+
112
120
  ```bash
113
121
  pip install agmem
114
122
  ```
@@ -639,6 +647,15 @@ Or connect via MCP and call `memory_add` from your agent.
639
647
  3. Write via `agmem add` + `agmem commit` or MCP `memory_add`
640
648
  4. Optional: Use `agmem search` for semantic retrieval
641
649
 
650
+ ## Links
651
+
652
+ - **PyPI:** [pypi.org/project/agmem](https://pypi.org/project/agmem/) — `pip install agmem`
653
+
654
+ **GitHub repo (About):** Use this description and topics in your repo’s **About** (gear → Description & topics):
655
+
656
+ - **Description:** Git for AI agent memories. Version control for episodic logs, semantic knowledge, and procedural workflows.
657
+ - **Topics:** `ai` `agent` `memory` `version-control` `git` `vcs` `llm` `python`
658
+
642
659
  ## Contributing
643
660
 
644
661
  Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
@@ -0,0 +1,86 @@
1
+ agmem-0.1.2.dist-info/licenses/LICENSE,sha256=X_S6RBErW-F0IDbM3FAEoDB-zxExFnl2m8640rTXphM,1067
2
+ memvcs/__init__.py,sha256=mXwHTSlUPWo4ERqJLGJnxmxtGQQHPSbXb4IpO61l04M,193
3
+ memvcs/cli.py,sha256=0bIdOVz23ZON4i8LoAsepiII-DpiGTGysllXFwmG0GI,5771
4
+ memvcs/commands/__init__.py,sha256=A2D6xWaO6epU7iV4QSvqvF5TspnwRyDN7NojmGatPrE,510
5
+ memvcs/commands/add.py,sha256=5Bia5nLfaREyp-ujqReDOyZu6zF5WveQk_N8XkWmqnM,8511
6
+ memvcs/commands/base.py,sha256=yWvIYuofRxbHXvChlSd_DL_hJMaQdbZwa2XBDWj5Bio,634
7
+ memvcs/commands/blame.py,sha256=2tEXyXpAebm_zQFAyw-6d3K09Lnh1GVLYGp6htlEmI0,5605
8
+ memvcs/commands/branch.py,sha256=ghsK4tnGe7XApxQSgou7V0BASBC8PL8nQs_yBt0notQ,3150
9
+ memvcs/commands/checkout.py,sha256=xaYZSbCQ-MyLWPtwA2FdH6WqGMI3oF3R2JmCufGBVFg,3182
10
+ memvcs/commands/clean.py,sha256=e0OhSQdHfFnOPTRbyKbM8IcX4yJD5n_kaBKjIeoaRBo,1973
11
+ memvcs/commands/clone.py,sha256=EAeojcUpRyCj1tVLR5zqnbkdF6Cvpza0_gyjZoCHysw,2895
12
+ memvcs/commands/commit.py,sha256=W4ulVZuEETJh1SHpscaQfNjyQMqeIE0AYZIbMbTrsq4,6801
13
+ memvcs/commands/daemon.py,sha256=B9_a7DFXMde9Fphpo7J9-osXqYCby-KfvITObJ3VqIo,8589
14
+ memvcs/commands/decay.py,sha256=QcgOTMJZxrfw_AOz94YHA3LGoNXRMDn69TxWlUrpSw4,2421
15
+ memvcs/commands/diff.py,sha256=KcgD57_fae4uvQ8G9ZbXmLpAYYIDiWiBuVcjsDtyE1U,5480
16
+ memvcs/commands/distill.py,sha256=Inl6igvjewGTT1L4nH48PIi8_oUtlFgJwq1gUTosI9E,2171
17
+ memvcs/commands/fsck.py,sha256=PQWEoqmGKhjZEEMYBf1pnORz8j-YaOLFojmQkND3m3Q,6545
18
+ memvcs/commands/garden.py,sha256=0ZbohharxBC6-n1Y2Cgpyfpwuzlq_vmWi3Yu0pQRQBA,3269
19
+ memvcs/commands/graph.py,sha256=MDi6bK2w0OrpK5VOE8XXw5gQX7BuD7VzUyqJ5Ra9Bsg,4746
20
+ memvcs/commands/init.py,sha256=TsrLFLXwkDFT0opsYJTfwu0NIxLrNiiba5SpzRtxjDI,1614
21
+ memvcs/commands/log.py,sha256=eNlLs0-PS2nF0pMAMI8izKGUiEb2m3S0RB4Zh6cUQpE,2859
22
+ memvcs/commands/mcp.py,sha256=PMfwVD6uHltN58Jh7IOiS1w7oND42tg14QKRCJNudmY,1740
23
+ memvcs/commands/merge.py,sha256=kB9bXBagZrZYz1mozVGwelMTJ3ATm9QyINrE90FhXhA,2417
24
+ memvcs/commands/pack.py,sha256=rIDjMpxJG0oxrWnB3vCGHqviCITIeIbdy3nhuHVHzM8,3629
25
+ memvcs/commands/pull.py,sha256=qeEtBEuXo-BLsOqBU146V2lUK1WrCNp1d8z6qqWhhyU,2113
26
+ memvcs/commands/push.py,sha256=0abEdHkCMfHpH_Nmlw3OaU7Hzi0-RXF-cTVHpiSPw6k,5086
27
+ memvcs/commands/recall.py,sha256=7nwC4mFYpdjKWG-Cs3cpDLr5_SgYJ6HkVSXDOkFke5A,4592
28
+ memvcs/commands/reflog.py,sha256=j8Rxw_4jZuldS1k1g3wF2EItmPxBEmogWcbccOZrGkw,1117
29
+ memvcs/commands/remote.py,sha256=4PXDZjoU4YA4IbGrn_1KbFVCSVxVB_pVdI2nIn1NSbw,1761
30
+ memvcs/commands/repair.py,sha256=OBVa30Zc9rOjUw2GoiSAC6iZrnuCZN_J8nHpCnk5BzM,2312
31
+ memvcs/commands/reset.py,sha256=3TG7qOu2k1uji-TdSyXsdZ6LV2wqc2MB8aSJ6q71IGk,2778
32
+ memvcs/commands/resurrect.py,sha256=zi4ErN1jWVCn8I5rV4g_Dlbg1YadGEU6Xhc7srzlwfg,2503
33
+ memvcs/commands/search.py,sha256=NOMbkQmVurQ-_d7bZVf9vjOH6OgVPeErqpLZiSxsvBA,5322
34
+ memvcs/commands/serve.py,sha256=n8DroVRIQVm5UO2-o-9JU17pQYF-HYOMIJKycdRcBMs,1408
35
+ memvcs/commands/show.py,sha256=5YiIfY0368UB9sP9KE8J5O_rB2opdr8zrJswnLHgMCA,5268
36
+ memvcs/commands/stash.py,sha256=CD3mRWehcmfVRPGGpndUBdTT_ku4LC_rmSKPvTEOTAo,3193
37
+ memvcs/commands/status.py,sha256=O6BgzTiW3UHjXx6OKwH8X4g0hP0IlYDgr7As5RmeujU,3447
38
+ memvcs/commands/tag.py,sha256=CaCnA3JifVrdr8DfX4g0bp-_oRvagJkQFcI4bJbW1uM,3004
39
+ memvcs/commands/test.py,sha256=HZrpGZQhu9HnGZLjiq8TXi8jfOZqP-wc3bW6mgpP2yk,3926
40
+ memvcs/commands/timeline.py,sha256=xdOr2jz-_ArSPY-GxwXBloiwhfBzIfz4MAi-JEhP8H0,3666
41
+ memvcs/commands/tree.py,sha256=vdULq4vIXA_4gNfMnHn_Y78BwE0sJoeTBOnFJR3WsZ4,4927
42
+ memvcs/commands/verify.py,sha256=aH0FN_7xRulRV2oSLYSB4BiAw_U81IkZ5sFdBRmxdZ0,1858
43
+ memvcs/commands/when.py,sha256=MMQ15PFXFCTmjIq7dr0tC0XvGAdndMvckVnnWehc60Y,3692
44
+ memvcs/core/__init__.py,sha256=dkIC-4tS0GhwV2mZIbofEe8xR8uiFwrxslGf1aXwhYg,493
45
+ memvcs/core/access_index.py,sha256=HhacnzSUASzRV2jhDHkwRFoPS3rtqh9n9yE1VV7JXpk,5596
46
+ memvcs/core/config_loader.py,sha256=j-jgLDp2TRzWN9ZEZebfWSfatevBNYs0FEb3ud1SIR8,8277
47
+ memvcs/core/consistency.py,sha256=JUUlrGB8b2DMmozoP7KFNqd6nSLzKDZaHGbYyKlK_l8,7464
48
+ memvcs/core/constants.py,sha256=WUjAb50BFcF0mbFi_GNteDLCxLihmViBm9Fb-JMPmbM,220
49
+ memvcs/core/decay.py,sha256=ROGwnqngs7eJNkbKmwyOdij607m73vpmoJqzrIDLBzk,6581
50
+ memvcs/core/diff.py,sha256=koEHTLciIUxYKVJVuvmY0GDXMgDgGZP_qg5RayhF-iE,13226
51
+ memvcs/core/distiller.py,sha256=QBoTz3xFVepwGHOXVuIr5qfln2tkNo1CtcBpKIjsnqc,10337
52
+ memvcs/core/gardener.py,sha256=bFMyHU5H5-rYZIahT8j14ZOvBs5LSVhYZiTbyw7SQl4,16080
53
+ memvcs/core/hooks.py,sha256=XF9z8J5sWjAcuOyWQ2nuvEzK0UV8s4ThrcltaBZttzw,5448
54
+ memvcs/core/knowledge_graph.py,sha256=fjDZ-68tUHA6AZcn-p0d32r8X3SyrMME1OyuRPxtI_0,12875
55
+ memvcs/core/merge.py,sha256=eBdPd8loe7G6F8C3fMNdnm_kvlKgZ4JC5z4xnboteOI,19165
56
+ memvcs/core/objects.py,sha256=zlTcNHc3ehFnLm0UoI1Ct2gw5CU4moaRLhN8hZXm3p4,9943
57
+ memvcs/core/pii_scanner.py,sha256=T6gQ1APFrSDk980fjnv4ZMF-UztbJgmUFSwGrwWixEw,10802
58
+ memvcs/core/refs.py,sha256=4Nx2ZVRa_DzfUZ4O1AwzOHEjoGAEICJKqSd9GxaiD_g,16754
59
+ memvcs/core/remote.py,sha256=MhQTfxpzmH0mAMb7hoQJrTOAoqX0tqZxx1Yq5Q5niS8,10117
60
+ memvcs/core/repository.py,sha256=fu6ZxkF3RY7yV1FHRQ2gVOpxkwJzeffna2sZixd8puI,17894
61
+ memvcs/core/schema.py,sha256=_CrEWCdArc0yDJ04GT7fyvjHqkal7gegdFSsFOjVpBc,15287
62
+ memvcs/core/staging.py,sha256=dptdGi_74lhDkcGqGVU39ZyTkb25j-Rnkz0GWi83W1k,7221
63
+ memvcs/core/temporal_index.py,sha256=OmgfznlScItVf2sML7JOj_WaGtKJZMIsP818YKUlMAw,3554
64
+ memvcs/core/test_runner.py,sha256=7-0jCvji63JRbVfy3LNQWIQ7VL5weulOoG7SY1-YJbw,11496
65
+ memvcs/core/vector_store.py,sha256=JqyiPbznxEkuD1CIna5DKm0PwrlQ4tVARJpt6W0mESE,10599
66
+ memvcs/core/storage/__init__.py,sha256=yJE7bm5G7VlFLpSVFrrTkP4eOyQlWRPfBvEP9T5PW44,1901
67
+ memvcs/core/storage/base.py,sha256=IK4To8Cb-LHv5ltlaQLdB6LE-69euFK3hNqBtMCe7-g,9956
68
+ memvcs/core/storage/gcs.py,sha256=-cWuGw1jkFh-Xig-Abmwr9HGwjW5lWQJuF2xcAR1l78,10632
69
+ memvcs/core/storage/local.py,sha256=JAik9nta6RMe4mD7aMtgdFi8M4iZCeTqiP8pPisaO6U,6028
70
+ memvcs/core/storage/s3.py,sha256=tY5rfz8FfkRRNaHOPX7Wk6yXdBBBhKV0Ju2qnBtHxeU,13814
71
+ memvcs/integrations/__init__.py,sha256=hVtJoFaXt6ErAZwctcSBDZLXRHFs1CNgtltIBQiroQ0,103
72
+ memvcs/integrations/mcp_server.py,sha256=PxBYJnbzPs6bcFH6EmH5jQqbu_9Vy5eSAA8ruWTn2Q4,9061
73
+ memvcs/integrations/web_ui/__init__.py,sha256=MQIfgDKDgPctlcTUjwkwueS_MDsDssVRmIUnpECGS0k,51
74
+ memvcs/integrations/web_ui/server.py,sha256=4roJdX0xI3o-n3jZkOMnRScW4YUwpzE2dYZABDv6Iio,12751
75
+ memvcs/retrieval/__init__.py,sha256=IzzmYbE_hmkCmcwQqIUPca4vQh_tD--cB_l00i6uGxY,465
76
+ memvcs/retrieval/base.py,sha256=aCeWpb2EbSRE81Rn4maLzqPmLSKZOUrwmSSfTEFJ33U,1336
77
+ memvcs/retrieval/pack.py,sha256=4rrGP7oduPhAM9PYnW2_eNm4WpSDnleHEja-fjBtONc,4299
78
+ memvcs/retrieval/recaller.py,sha256=8KY-XjMUz5_vcKf46zI64uk1DEM__u7wM92ShukOtsY,3819
79
+ memvcs/retrieval/strategies.py,sha256=26yxQQubQfjxWQXknfVMxuzPHf2EcZxJg_B99BEdl5c,11458
80
+ memvcs/utils/__init__.py,sha256=8psUzz4Ntv2GzbRebkeVsoyC6Ck-FIwi0_lfYdj5oho,185
81
+ memvcs/utils/helpers.py,sha256=37zg_DcQ2y99b9NSLqxFkglHe13rJXKhFDpEbQ7iLhM,4121
82
+ agmem-0.1.2.dist-info/METADATA,sha256=uIzEeVVPQ36Kdy0NQ0ygJS9pvMSglfYSTo_oNjG5TsM,26608
83
+ agmem-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
84
+ agmem-0.1.2.dist-info/entry_points.txt,sha256=at7eWycgjqOo1wbUMECnXUsNo3gpCkJTU71OzrGLHu0,42
85
+ agmem-0.1.2.dist-info/top_level.txt,sha256=HtMMsKuwLKLOdgF1GxqQztqFM54tTJctVdJuOec6B-4,7
86
+ agmem-0.1.2.dist-info/RECORD,,
memvcs/__init__.py CHANGED
@@ -4,6 +4,6 @@ agmem - Agentic Memory Version Control System
4
4
  A Git-inspired version control system for AI agent memory artifacts.
5
5
  """
6
6
 
7
- __version__ = "0.1.1"
7
+ __version__ = "0.1.3"
8
8
  __author__ = "agmem Team"
9
9
  __license__ = "MIT"
memvcs/cli.py CHANGED
@@ -49,6 +49,15 @@ from .commands.fsck import FsckCommand
49
49
  from .commands.graph import GraphCommand
50
50
  from .commands.daemon import DaemonCommand
51
51
  from .commands.garden import GardenCommand
52
+ from .commands.recall import RecallCommand
53
+ from .commands.when import WhenCommand
54
+ from .commands.timeline import TimelineCommand
55
+ from .commands.pack import PackCommand
56
+ from .commands.distill import DistillCommand
57
+ from .commands.decay import DecayCommand
58
+ from .commands.resurrect import ResurrectCommand
59
+ from .commands.verify import VerifyCommand
60
+ from .commands.repair import RepairCommand
52
61
 
53
62
 
54
63
  # List of available commands
@@ -82,14 +91,23 @@ COMMANDS = [
82
91
  GraphCommand,
83
92
  DaemonCommand,
84
93
  GardenCommand,
94
+ RecallCommand,
95
+ WhenCommand,
96
+ TimelineCommand,
97
+ PackCommand,
98
+ DistillCommand,
99
+ DecayCommand,
100
+ ResurrectCommand,
101
+ VerifyCommand,
102
+ RepairCommand,
85
103
  ]
86
104
 
87
105
 
88
106
  def create_parser() -> argparse.ArgumentParser:
89
107
  """Create the main argument parser."""
90
108
  parser = argparse.ArgumentParser(
91
- prog='agmem',
92
- description='agmem - Agentic Memory Version Control System',
109
+ prog="agmem",
110
+ description="agmem - Agentic Memory Version Control System",
93
111
  formatter_class=argparse.RawDescriptionHelpFormatter,
94
112
  epilog="""
95
113
  Examples:
@@ -110,36 +128,21 @@ Examples:
110
128
  agmem tree Show directory tree visually
111
129
 
112
130
  For more information: https://github.com/vivek-tiwari-vt/agmem
113
- """
131
+ """,
114
132
  )
115
-
116
- parser.add_argument(
117
- '--version', '-v',
118
- action='version',
119
- version='%(prog)s 0.1.0'
120
- )
121
-
122
- parser.add_argument(
123
- '--verbose',
124
- action='store_true',
125
- help='Enable verbose output'
126
- )
127
-
133
+
134
+ parser.add_argument("--version", "-v", action="version", version="%(prog)s 0.1.0")
135
+
136
+ parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
137
+
128
138
  # Create subparsers for commands
129
- subparsers = parser.add_subparsers(
130
- dest='command',
131
- help='Available commands',
132
- metavar='COMMAND'
133
- )
134
-
139
+ subparsers = parser.add_subparsers(dest="command", help="Available commands", metavar="COMMAND")
140
+
135
141
  # Add each command
136
142
  for cmd_class in COMMANDS:
137
- cmd_parser = subparsers.add_parser(
138
- cmd_class.name,
139
- help=cmd_class.help
140
- )
143
+ cmd_parser = subparsers.add_parser(cmd_class.name, help=cmd_class.help)
141
144
  cmd_class.add_arguments(cmd_parser)
142
-
145
+
143
146
  return parser
144
147
 
145
148
 
@@ -147,12 +150,12 @@ def main(args: List[str] = None) -> int:
147
150
  """Main entry point."""
148
151
  parser = create_parser()
149
152
  parsed_args = parser.parse_args(args)
150
-
153
+
151
154
  # No command specified
152
155
  if not parsed_args.command:
153
156
  parser.print_help()
154
157
  return 0
155
-
158
+
156
159
  # Find and execute the command
157
160
  for cmd_class in COMMANDS:
158
161
  if cmd_class.name == parsed_args.command:
@@ -164,15 +167,16 @@ def main(args: List[str] = None) -> int:
164
167
  except Exception as e:
165
168
  if parsed_args.verbose:
166
169
  import traceback
170
+
167
171
  traceback.print_exc()
168
172
  else:
169
173
  print(f"Error: {e}")
170
174
  return 1
171
-
175
+
172
176
  # Unknown command
173
177
  print(f"Unknown command: {parsed_args.command}")
174
178
  return 1
175
179
 
176
180
 
177
- if __name__ == '__main__':
181
+ if __name__ == "__main__":
178
182
  sys.exit(main())
@@ -11,13 +11,13 @@ from .merge import MergeCommand
11
11
  from .diff import DiffCommand
12
12
 
13
13
  __all__ = [
14
- 'InitCommand',
15
- 'AddCommand',
16
- 'CommitCommand',
17
- 'StatusCommand',
18
- 'LogCommand',
19
- 'BranchCommand',
20
- 'CheckoutCommand',
21
- 'MergeCommand',
22
- 'DiffCommand',
14
+ "InitCommand",
15
+ "AddCommand",
16
+ "CommitCommand",
17
+ "StatusCommand",
18
+ "LogCommand",
19
+ "BranchCommand",
20
+ "CheckoutCommand",
21
+ "MergeCommand",
22
+ "DiffCommand",
23
23
  ]
memvcs/commands/add.py CHANGED
@@ -10,84 +10,82 @@ from ..core.repository import Repository
10
10
 
11
11
 
12
12
  # Default allowed file extensions for memory files
13
- DEFAULT_ALLOWED_EXTENSIONS = {'.md', '.txt', '.json', '.yaml', '.yml'}
13
+ DEFAULT_ALLOWED_EXTENSIONS = {".md", ".txt", ".json", ".yaml", ".yml"}
14
14
 
15
15
  # Binary file signatures (magic bytes) to detect binary files
16
16
  BINARY_SIGNATURES = [
17
- b'\x89PNG', # PNG
18
- b'\xff\xd8\xff', # JPEG
19
- b'GIF8', # GIF
20
- b'%PDF', # PDF
21
- b'PK\x03\x04', # ZIP
22
- b'\x1f\x8b', # GZIP
23
- b'BM', # BMP
24
- b'\x00\x00\x01\x00', # ICO
25
- b'RIFF', # WAV, AVI, etc.
17
+ b"\x89PNG", # PNG
18
+ b"\xff\xd8\xff", # JPEG
19
+ b"GIF8", # GIF
20
+ b"%PDF", # PDF
21
+ b"PK\x03\x04", # ZIP
22
+ b"\x1f\x8b", # GZIP
23
+ b"BM", # BMP
24
+ b"\x00\x00\x01\x00", # ICO
25
+ b"RIFF", # WAV, AVI, etc.
26
26
  ]
27
27
 
28
28
 
29
29
  class AddCommand:
30
30
  """Add files to the staging area."""
31
-
32
- name = 'add'
33
- help = 'Add memory files to staging area'
34
-
31
+
32
+ name = "add"
33
+ help = "Add memory files to staging area"
34
+
35
35
  @staticmethod
36
36
  def add_arguments(parser: argparse.ArgumentParser):
37
+ parser.add_argument("paths", nargs="+", help="Files or directories to stage")
37
38
  parser.add_argument(
38
- 'paths',
39
- nargs='+',
40
- help='Files or directories to stage'
39
+ "--all",
40
+ "-A",
41
+ action="store_true",
42
+ help="Stage all changes (including modifications and deletions)",
41
43
  )
42
44
  parser.add_argument(
43
- '--all', '-A',
44
- action='store_true',
45
- help='Stage all changes (including modifications and deletions)'
45
+ "--force",
46
+ "-f",
47
+ action="store_true",
48
+ help="Force add even if file type is not recommended",
46
49
  )
47
50
  parser.add_argument(
48
- '--force', '-f',
49
- action='store_true',
50
- help='Force add even if file type is not recommended'
51
+ "--allow-binary",
52
+ action="store_true",
53
+ help="Allow staging binary files (not recommended)",
51
54
  )
52
- parser.add_argument(
53
- '--allow-binary',
54
- action='store_true',
55
- help='Allow staging binary files (not recommended)'
56
- )
57
-
55
+
58
56
  @staticmethod
59
57
  def _is_binary_file(filepath: Path) -> bool:
60
58
  """Check if a file is binary by looking at magic bytes."""
61
59
  try:
62
- with open(filepath, 'rb') as f:
60
+ with open(filepath, "rb") as f:
63
61
  header = f.read(16)
64
-
62
+
65
63
  for signature in BINARY_SIGNATURES:
66
64
  if header.startswith(signature):
67
65
  return True
68
-
66
+
69
67
  # Also check for null bytes (common in binary files)
70
- if b'\x00' in header:
68
+ if b"\x00" in header:
71
69
  return True
72
-
70
+
73
71
  return False
74
72
  except Exception:
75
73
  return False
76
-
74
+
77
75
  @staticmethod
78
76
  def _is_allowed_extension(filepath: Path, config: dict) -> bool:
79
77
  """Check if file extension is in allowed list."""
80
- allowed = config.get('allowed_extensions', list(DEFAULT_ALLOWED_EXTENSIONS))
78
+ allowed = config.get("allowed_extensions", list(DEFAULT_ALLOWED_EXTENSIONS))
81
79
  allowed_set = set(allowed)
82
-
80
+
83
81
  ext = filepath.suffix.lower()
84
82
  return ext in allowed_set or not ext # Allow files without extension
85
-
83
+
86
84
  @staticmethod
87
85
  def _validate_file(filepath: Path, config: dict, force: bool, allow_binary: bool) -> tuple:
88
86
  """
89
87
  Validate a file for staging.
90
-
88
+
91
89
  Returns:
92
90
  Tuple of (is_valid, warning_message)
93
91
  """
@@ -96,13 +94,16 @@ class AddCommand:
96
94
  if allow_binary:
97
95
  return True, f"Warning: {filepath} appears to be binary"
98
96
  else:
99
- return False, f"Rejected: {filepath} is a binary file. Use --allow-binary to override."
100
-
97
+ return (
98
+ False,
99
+ f"Rejected: {filepath} is a binary file. Use --allow-binary to override.",
100
+ )
101
+
101
102
  # Check extension
102
103
  if not AddCommand._is_allowed_extension(filepath, config):
103
- ext = filepath.suffix or '(no extension)'
104
- allowed = config.get('allowed_extensions', list(DEFAULT_ALLOWED_EXTENSIONS))
105
-
104
+ ext = filepath.suffix or "(no extension)"
105
+ allowed = config.get("allowed_extensions", list(DEFAULT_ALLOWED_EXTENSIONS))
106
+
106
107
  if force:
107
108
  return True, f"Warning: {filepath} has extension '{ext}' which may not be optimal"
108
109
  else:
@@ -111,9 +112,9 @@ class AddCommand:
111
112
  f" Recommended: {', '.join(sorted(allowed))}\n"
112
113
  f" Use --force to override."
113
114
  )
114
-
115
+
115
116
  return True, None
116
-
117
+
117
118
  @staticmethod
118
119
  def execute(args) -> int:
119
120
  repo, code = require_repo()
@@ -123,19 +124,19 @@ class AddCommand:
123
124
  staged_count = 0
124
125
  rejected_count = 0
125
126
  config = repo.get_config()
126
-
127
+
127
128
  for path_str in args.paths:
128
129
  path = Path(path_str)
129
-
130
+
130
131
  # Handle '.' to stage all
131
- if path_str == '.':
132
+ if path_str == ".":
132
133
  staged, rejected = AddCommand._stage_directory_with_validation(
133
134
  repo, None, config, args.force, args.allow_binary
134
135
  )
135
136
  staged_count += staged
136
137
  rejected_count += rejected
137
138
  continue
138
-
139
+
139
140
  # Resolve path relative to current/
140
141
  if path.is_absolute():
141
142
  try:
@@ -156,41 +157,41 @@ class AddCommand:
156
157
  else:
157
158
  print(f"Error: Path not found: {path}")
158
159
  continue
159
-
160
+
160
161
  full_path = repo.current_dir / rel_path
161
-
162
+
162
163
  if not full_path.exists():
163
164
  print(f"Error: Path not found: {path}")
164
165
  continue
165
-
166
+
166
167
  if full_path.is_file():
167
168
  # Validate file
168
169
  is_valid, message = AddCommand._validate_file(
169
170
  full_path, config, args.force, args.allow_binary
170
171
  )
171
-
172
+
172
173
  if not is_valid:
173
174
  print(message)
174
175
  rejected_count += 1
175
176
  continue
176
-
177
+
177
178
  if message: # Warning
178
179
  print(message)
179
-
180
+
180
181
  try:
181
182
  blob_hash = repo.stage_file(str(rel_path))
182
183
  print(f" staged: {rel_path}")
183
184
  staged_count += 1
184
185
  except Exception as e:
185
186
  print(f"Error staging {rel_path}: {e}")
186
-
187
+
187
188
  elif full_path.is_dir():
188
189
  staged, rejected = AddCommand._stage_directory_with_validation(
189
190
  repo, str(rel_path), config, args.force, args.allow_binary
190
191
  )
191
192
  staged_count += staged
192
193
  rejected_count += rejected
193
-
194
+
194
195
  if staged_count > 0 or rejected_count > 0:
195
196
  print(f"\nStaged {staged_count} file(s)")
196
197
  if rejected_count > 0:
@@ -199,42 +200,42 @@ class AddCommand:
199
200
  print("Run 'agmem commit -m \"message\"' to save snapshot")
200
201
  else:
201
202
  print("No files staged")
202
-
203
+
203
204
  return 0
204
-
205
+
205
206
  @staticmethod
206
- def _stage_directory_with_validation(repo, subdir: str, config: dict, force: bool, allow_binary: bool) -> tuple:
207
+ def _stage_directory_with_validation(
208
+ repo, subdir: str, config: dict, force: bool, allow_binary: bool
209
+ ) -> tuple:
207
210
  """
208
211
  Stage a directory with file validation.
209
-
212
+
210
213
  Returns:
211
214
  Tuple of (staged_count, rejected_count)
212
215
  """
213
216
  staged_count = 0
214
217
  rejected_count = 0
215
-
218
+
216
219
  if subdir:
217
220
  dir_path = repo.current_dir / subdir
218
221
  else:
219
222
  dir_path = repo.current_dir
220
-
223
+
221
224
  if not dir_path.exists():
222
225
  return 0, 0
223
-
224
- for file_path in dir_path.rglob('*'):
226
+
227
+ for file_path in dir_path.rglob("*"):
225
228
  if not file_path.is_file():
226
229
  continue
227
-
230
+
228
231
  # Skip hidden files and .mem directory
229
232
  rel_to_current = file_path.relative_to(repo.current_dir)
230
- if any(part.startswith('.') for part in rel_to_current.parts):
233
+ if any(part.startswith(".") for part in rel_to_current.parts):
231
234
  continue
232
-
235
+
233
236
  # Validate file
234
- is_valid, message = AddCommand._validate_file(
235
- file_path, config, force, allow_binary
236
- )
237
-
237
+ is_valid, message = AddCommand._validate_file(file_path, config, force, allow_binary)
238
+
238
239
  if not is_valid:
239
240
  if not force:
240
241
  # Only print first few rejections to avoid spam
@@ -244,15 +245,15 @@ class AddCommand:
244
245
  print(" ... (more files rejected)")
245
246
  rejected_count += 1
246
247
  continue
247
-
248
+
248
249
  if message: # Warning
249
250
  print(message)
250
-
251
+
251
252
  try:
252
253
  repo.stage_file(str(rel_to_current))
253
254
  print(f" staged: {rel_to_current}")
254
255
  staged_count += 1
255
256
  except Exception as e:
256
257
  print(f"Error staging {rel_to_current}: {e}")
257
-
258
+
258
259
  return staged_count, rejected_count