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/blame.py CHANGED
@@ -12,53 +12,46 @@ from ..core.objects import Commit, Tree, Blob
12
12
 
13
13
  class BlameCommand:
14
14
  """Show author and commit for each line of a file, or trace semantic facts."""
15
-
16
- name = 'blame'
17
- help = 'Show who changed each line of a memory file, or trace semantic facts'
18
-
15
+
16
+ name = "blame"
17
+ help = "Show who changed each line of a memory file, or trace semantic facts"
18
+
19
19
  @staticmethod
20
20
  def add_arguments(parser: argparse.ArgumentParser):
21
+ parser.add_argument("file", nargs="?", help="File to blame (path relative to current/)")
21
22
  parser.add_argument(
22
- 'file',
23
- nargs='?',
24
- help='File to blame (path relative to current/)'
25
- )
26
- parser.add_argument(
27
- 'ref',
28
- nargs='?',
29
- default='HEAD',
30
- help='Commit to blame at (default: HEAD)'
23
+ "ref", nargs="?", default="HEAD", help="Commit to blame at (default: HEAD)"
31
24
  )
32
25
  parser.add_argument(
33
- '--query', '-q',
34
- help='Semantic query to trace (e.g., "Why does agent think X?")'
26
+ "--query", "-q", help='Semantic query to trace (e.g., "Why does agent think X?")'
35
27
  )
36
28
  parser.add_argument(
37
- '--limit', '-n',
29
+ "--limit",
30
+ "-n",
38
31
  type=int,
39
32
  default=5,
40
- help='Number of results to show for semantic blame (default: 5)'
33
+ help="Number of results to show for semantic blame (default: 5)",
41
34
  )
42
-
35
+
43
36
  @staticmethod
44
37
  def execute(args) -> int:
45
38
  repo, code = require_repo()
46
39
  if code != 0:
47
40
  return code
48
-
41
+
49
42
  # Semantic blame mode
50
43
  if args.query:
51
44
  return BlameCommand._semantic_blame(repo, args.query, args.limit)
52
-
45
+
53
46
  # File blame mode
54
47
  if not args.file:
55
48
  print("Error: Either --query or a file path is required.")
56
49
  print("Usage: agmem blame <file> [ref]")
57
- print(" agmem blame --query \"Why does agent think X?\"")
50
+ print(' agmem blame --query "Why does agent think X?"')
58
51
  return 1
59
-
52
+
60
53
  return BlameCommand._file_blame(repo, args.file, args.ref)
61
-
54
+
62
55
  @staticmethod
63
56
  def _file_blame(repo, filepath: str, ref: str) -> int:
64
57
  """Traditional file-based blame."""
@@ -66,45 +59,45 @@ class BlameCommand:
66
59
  if not commit_hash:
67
60
  print(f"Error: Unknown revision: {ref}")
68
61
  return 1
69
-
62
+
70
63
  # Get file content at commit
71
64
  tree = repo.get_commit_tree(commit_hash)
72
65
  if not tree:
73
66
  print("Error: Could not load tree.")
74
67
  return 1
75
-
68
+
76
69
  # Find file in tree (support path like semantic/user-prefs.md)
77
70
  blob_hash = None
78
71
  for entry in tree.entries:
79
- path = entry.path + '/' + entry.name if entry.path else entry.name
72
+ path = entry.path + "/" + entry.name if entry.path else entry.name
80
73
  if path == filepath:
81
74
  blob_hash = entry.hash
82
75
  break
83
-
76
+
84
77
  if not blob_hash:
85
78
  print(f"Error: File not found in {ref}: {filepath}")
86
79
  return 1
87
-
80
+
88
81
  blob = Blob.load(repo.object_store, blob_hash)
89
82
  if not blob:
90
83
  print("Error: Could not load file content.")
91
84
  return 1
92
-
93
- lines = blob.content.decode('utf-8', errors='replace').splitlines()
85
+
86
+ lines = blob.content.decode("utf-8", errors="replace").splitlines()
94
87
  commit = Commit.load(repo.object_store, commit_hash)
95
- author_short = commit.author.split('<')[0].strip()[:20] if commit else 'unknown'
88
+ author_short = commit.author.split("<")[0].strip()[:20] if commit else "unknown"
96
89
  hash_short = commit_hash[:8]
97
-
90
+
98
91
  for i, line in enumerate(lines, 1):
99
92
  print(f"{hash_short} ({author_short:20} {i:4}) {line}")
100
-
93
+
101
94
  return 0
102
-
95
+
103
96
  @staticmethod
104
97
  def _semantic_blame(repo, query: str, limit: int) -> int:
105
98
  """
106
99
  Semantic blame - trace which commit introduced a fact.
107
-
100
+
108
101
  Searches the vector store and shows provenance for matching chunks.
109
102
  """
110
103
  try:
@@ -113,34 +106,34 @@ class BlameCommand:
113
106
  print("Error: Vector search requires sqlite-vec.")
114
107
  print("Install with: pip install agmem[vector]")
115
108
  return 1
116
-
109
+
117
110
  try:
118
- vs = VectorStore(repo.root / '.mem')
111
+ vs = VectorStore(repo.root / ".mem")
119
112
  results = vs.search_with_provenance(query, limit=limit)
120
113
  except Exception as e:
121
114
  print(f"Error: Vector search failed: {e}")
122
115
  print("Try running 'agmem search --rebuild' to rebuild the index.")
123
116
  return 1
124
-
117
+
125
118
  if not results:
126
119
  print("No matching facts found in memory.")
127
120
  print("Try rebuilding the index with 'agmem search --rebuild'")
128
121
  return 0
129
-
130
- print(f"Semantic blame for: \"{query}\"")
122
+
123
+ print(f'Semantic blame for: "{query}"')
131
124
  print("=" * 60)
132
-
125
+
133
126
  for i, result in enumerate(results, 1):
134
- path = result['path']
135
- content = result['content']
136
- similarity = result['similarity']
137
- commit_hash = result['commit_hash']
138
- author = result['author']
139
- indexed_at = result['indexed_at']
140
-
127
+ path = result["path"]
128
+ content = result["content"]
129
+ similarity = result["similarity"]
130
+ commit_hash = result["commit_hash"]
131
+ author = result["author"]
132
+ indexed_at = result["indexed_at"]
133
+
141
134
  print(f"\n[{i}] {path}")
142
135
  print(f" Similarity: {similarity:.2%}")
143
-
136
+
144
137
  if commit_hash:
145
138
  # Try to get commit details
146
139
  commit = Commit.load(repo.object_store, commit_hash)
@@ -157,13 +150,13 @@ class BlameCommand:
157
150
  print(" Commit: (not tracked)")
158
151
  if indexed_at:
159
152
  print(f" Indexed: {indexed_at}")
160
-
153
+
161
154
  # Show content preview
162
155
  print(f"\n Content preview:")
163
- for line in content.split('\n')[:5]:
156
+ for line in content.split("\n")[:5]:
164
157
  print(f" {line[:70]}")
165
- if len(content.split('\n')) > 5:
158
+ if len(content.split("\n")) > 5:
166
159
  print(" ...")
167
-
160
+
168
161
  print()
169
162
  return 0
memvcs/commands/branch.py CHANGED
@@ -10,43 +10,23 @@ from ..core.repository import Repository
10
10
 
11
11
  class BranchCommand:
12
12
  """Manage branches."""
13
-
14
- name = 'branch'
15
- help = 'List, create, or delete branches'
16
-
13
+
14
+ name = "branch"
15
+ help = "List, create, or delete branches"
16
+
17
17
  @staticmethod
18
18
  def add_arguments(parser: argparse.ArgumentParser):
19
+ parser.add_argument("name", nargs="?", help="Branch name to create or delete")
20
+ parser.add_argument("--delete", "-d", action="store_true", help="Delete a branch")
19
21
  parser.add_argument(
20
- 'name',
21
- nargs='?',
22
- help='Branch name to create or delete'
23
- )
24
- parser.add_argument(
25
- '--delete', '-d',
26
- action='store_true',
27
- help='Delete a branch'
22
+ "--force", "-D", action="store_true", help="Force delete a branch (even if not merged)"
28
23
  )
24
+ parser.add_argument("--list", "-l", action="store_true", help="List all branches")
29
25
  parser.add_argument(
30
- '--force', '-D',
31
- action='store_true',
32
- help='Force delete a branch (even if not merged)'
26
+ "--all", "-a", action="store_true", help="List all branches including remote"
33
27
  )
34
- parser.add_argument(
35
- '--list', '-l',
36
- action='store_true',
37
- help='List all branches'
38
- )
39
- parser.add_argument(
40
- '--all', '-a',
41
- action='store_true',
42
- help='List all branches including remote'
43
- )
44
- parser.add_argument(
45
- 'start_point',
46
- nargs='?',
47
- help='Commit to start the new branch from'
48
- )
49
-
28
+ parser.add_argument("start_point", nargs="?", help="Commit to start the new branch from")
29
+
50
30
  @staticmethod
51
31
  def execute(args) -> int:
52
32
  repo, code = require_repo()
@@ -60,13 +40,13 @@ class BranchCommand:
60
40
  if not args.name:
61
41
  print("Error: Branch name required for deletion")
62
42
  return 1
63
-
43
+
64
44
  current = repo.refs.get_current_branch()
65
45
  if args.name == current:
66
46
  print(f"Error: Cannot delete current branch '{args.name}'")
67
47
  print("Switch to another branch first.")
68
48
  return 1
69
-
49
+
70
50
  if repo.refs.delete_branch(args.name):
71
51
  print(f"Deleted branch {args.name}")
72
52
  return 0
@@ -10,27 +10,18 @@ from ..core.repository import Repository
10
10
 
11
11
  class CheckoutCommand:
12
12
  """Switch branches or restore working tree files."""
13
-
14
- name = 'checkout'
15
- help = 'Switch branches or restore working tree files'
16
-
13
+
14
+ name = "checkout"
15
+ help = "Switch branches or restore working tree files"
16
+
17
17
  @staticmethod
18
18
  def add_arguments(parser: argparse.ArgumentParser):
19
+ parser.add_argument("ref", help="Branch, tag, or commit to checkout")
20
+ parser.add_argument("-b", action="store_true", help="Create and checkout a new branch")
19
21
  parser.add_argument(
20
- 'ref',
21
- help='Branch, tag, or commit to checkout'
22
+ "--force", "-f", action="store_true", help="Force checkout (discard local changes)"
22
23
  )
23
- parser.add_argument(
24
- '-b',
25
- action='store_true',
26
- help='Create and checkout a new branch'
27
- )
28
- parser.add_argument(
29
- '--force', '-f',
30
- action='store_true',
31
- help='Force checkout (discard local changes)'
32
- )
33
-
24
+
34
25
  @staticmethod
35
26
  def execute(args) -> int:
36
27
  repo, code = require_repo()
@@ -40,24 +31,24 @@ class CheckoutCommand:
40
31
  # Create and checkout new branch
41
32
  if args.b:
42
33
  branch_name = args.ref
43
-
34
+
44
35
  # Check if branch already exists
45
36
  if repo.refs.branch_exists(branch_name):
46
37
  print(f"Error: A branch named '{branch_name}' already exists.")
47
38
  return 1
48
-
39
+
49
40
  # Get current HEAD commit
50
41
  head = repo.refs.get_head()
51
- if head['type'] == 'branch':
52
- current_commit = repo.refs.get_branch_commit(head['value'])
42
+ if head["type"] == "branch":
43
+ current_commit = repo.refs.get_branch_commit(head["value"])
53
44
  else:
54
- current_commit = head['value']
55
-
45
+ current_commit = head["value"]
46
+
56
47
  # Create branch
57
48
  if not repo.refs.create_branch(branch_name, current_commit):
58
49
  print(f"Error: Could not create branch '{branch_name}'")
59
50
  return 1
60
-
51
+
61
52
  # Switch to new branch
62
53
  try:
63
54
  repo.refs.set_head_branch(branch_name)
@@ -66,16 +57,16 @@ class CheckoutCommand:
66
57
  except Exception as e:
67
58
  print(f"Error switching to branch: {e}")
68
59
  return 1
69
-
60
+
70
61
  # Regular checkout
71
62
  ref = args.ref
72
-
63
+
73
64
  # Check if it's a branch
74
65
  is_branch = repo.refs.branch_exists(ref)
75
-
66
+
76
67
  try:
77
68
  commit_hash = repo.checkout(ref, force=args.force)
78
-
69
+
79
70
  if is_branch:
80
71
  print(f"Switched to branch '{ref}'")
81
72
  else:
@@ -83,16 +74,20 @@ class CheckoutCommand:
83
74
  if repo.refs.tag_exists(ref):
84
75
  print(f"Note: checking out '{ref}'.")
85
76
  print()
86
- print("You are in 'detached HEAD' state. You can look around, make experimental")
87
- print("changes and commit them, and you can discard any commits you make in this")
77
+ print(
78
+ "You are in 'detached HEAD' state. You can look around, make experimental"
79
+ )
80
+ print(
81
+ "changes and commit them, and you can discard any commits you make in this"
82
+ )
88
83
  print("state without impacting any branches by switching back to a branch.")
89
84
  else:
90
85
  print(f"Note: checking out '{commit_hash[:8]}'.")
91
86
  print()
92
87
  print("You are in 'detached HEAD' state.")
93
-
88
+
94
89
  return 0
95
-
90
+
96
91
  except ValueError as e:
97
92
  print(f"Error: {e}")
98
93
  return 1
memvcs/commands/clean.py CHANGED
@@ -12,52 +12,46 @@ from ..core.repository import Repository
12
12
 
13
13
  class CleanCommand:
14
14
  """Remove untracked files from working directory."""
15
-
16
- name = 'clean'
17
- help = 'Remove untracked files from working directory'
18
-
15
+
16
+ name = "clean"
17
+ help = "Remove untracked files from working directory"
18
+
19
19
  @staticmethod
20
20
  def add_arguments(parser: argparse.ArgumentParser):
21
21
  parser.add_argument(
22
- '-n', '--dry-run',
23
- action='store_true',
24
- help='Show what would be removed without removing'
25
- )
26
- parser.add_argument(
27
- '-f', '--force',
28
- action='store_true',
29
- help='Required to actually remove files'
22
+ "-n",
23
+ "--dry-run",
24
+ action="store_true",
25
+ help="Show what would be removed without removing",
30
26
  )
31
27
  parser.add_argument(
32
- '-d',
33
- action='store_true',
34
- help='Remove untracked directories too'
28
+ "-f", "--force", action="store_true", help="Required to actually remove files"
35
29
  )
36
-
30
+ parser.add_argument("-d", action="store_true", help="Remove untracked directories too")
31
+
37
32
  @staticmethod
38
33
  def execute(args) -> int:
39
34
  repo, code = require_repo()
40
35
  if code != 0:
41
36
  return code
42
37
 
43
-
44
38
  status = repo.get_status()
45
- untracked = status.get('untracked', [])
46
-
39
+ untracked = status.get("untracked", [])
40
+
47
41
  if not untracked:
48
42
  print("Nothing to clean.")
49
43
  return 0
50
-
44
+
51
45
  if args.dry_run:
52
46
  print("Would remove:")
53
47
  for p in untracked:
54
48
  print(f" {p}")
55
49
  return 0
56
-
50
+
57
51
  if not args.force:
58
52
  print("Use -f to force removal of untracked files.")
59
53
  return 1
60
-
54
+
61
55
  removed = 0
62
56
  for rel_path in untracked:
63
57
  full_path = repo.current_dir / rel_path
@@ -68,9 +62,10 @@ class CleanCommand:
68
62
  print(f"Removed {rel_path}")
69
63
  elif args.d and full_path.is_dir():
70
64
  import shutil
65
+
71
66
  shutil.rmtree(full_path)
72
67
  removed += 1
73
68
  print(f"Removed {rel_path}/")
74
-
69
+
75
70
  print(f"Removed {removed} file(s)")
76
71
  return 0
memvcs/commands/clone.py CHANGED
@@ -80,11 +80,14 @@ class CloneCommand:
80
80
 
81
81
  # Set remote origin to source
82
82
  import json
83
+
83
84
  config_file = target / ".mem" / "config.json"
84
85
  config = json.loads(config_file.read_text()) if config_file.exists() else {}
85
86
  if "remotes" not in config:
86
87
  config["remotes"] = {}
87
- config["remotes"]["origin"] = {"url": url if url.startswith("file://") else f"file://{remote_path}"}
88
+ config["remotes"]["origin"] = {
89
+ "url": url if url.startswith("file://") else f"file://{remote_path}"
90
+ }
88
91
  config_file.write_text(json.dumps(config, indent=2))
89
92
 
90
93
  print(f"Cloned into {target}")
memvcs/commands/commit.py CHANGED
@@ -7,88 +7,82 @@ from datetime import datetime
7
7
 
8
8
  from ..commands.base import require_repo
9
9
  from ..core.schema import SchemaValidator
10
- from ..core.hooks import run_pre_commit_hooks
10
+ from ..core.hooks import run_pre_commit_hooks, compute_suggested_importance
11
11
 
12
12
 
13
13
  class CommitCommand:
14
14
  """Create a commit from staged changes."""
15
-
16
- name = 'commit'
17
- help = 'Save staged changes as a memory snapshot'
18
-
15
+
16
+ name = "commit"
17
+ help = "Save staged changes as a memory snapshot"
18
+
19
19
  @staticmethod
20
20
  def add_arguments(parser: argparse.ArgumentParser):
21
21
  parser.add_argument(
22
- '-m', '--message',
23
- required=True,
24
- help='Commit message describing the changes'
25
- )
26
- parser.add_argument(
27
- '--author',
28
- help='Override default author'
22
+ "-m", "--message", required=True, help="Commit message describing the changes"
29
23
  )
24
+ parser.add_argument("--author", help="Override default author")
30
25
  parser.add_argument(
31
- '--no-verify',
32
- action='store_true',
33
- help='Skip pre-commit hooks and schema validation'
26
+ "--no-verify", action="store_true", help="Skip pre-commit hooks and schema validation"
34
27
  )
28
+ parser.add_argument("--strict", action="store_true", help="Treat schema warnings as errors")
35
29
  parser.add_argument(
36
- '--strict',
37
- action='store_true',
38
- help='Treat schema warnings as errors'
30
+ "--run-tests", action="store_true", help="Run memory tests before committing"
39
31
  )
40
32
  parser.add_argument(
41
- '--run-tests',
42
- action='store_true',
43
- help='Run memory tests before committing'
33
+ "--importance",
34
+ type=float,
35
+ metavar="SCORE",
36
+ help="Importance score 0.0-1.0 for recall/decay weighting",
44
37
  )
45
-
38
+
46
39
  @staticmethod
47
40
  def _get_blob_hash(file_info) -> str:
48
41
  """Get blob hash from StagedFile or dict (for hooks that pass either)."""
49
- if hasattr(file_info, 'blob_hash'):
42
+ if hasattr(file_info, "blob_hash"):
50
43
  return file_info.blob_hash
51
44
  if isinstance(file_info, dict):
52
- return file_info.get('blob_hash') or file_info.get('hash') or ''
53
- return ''
45
+ return file_info.get("blob_hash") or file_info.get("hash") or ""
46
+ return ""
54
47
 
55
48
  @staticmethod
56
49
  def _validate_staged_files(repo, staged: dict, strict: bool) -> tuple:
57
50
  """
58
51
  Validate staged files for schema compliance.
59
-
52
+
60
53
  Returns:
61
54
  Tuple of (success, validation_results)
62
55
  """
63
56
  validation_results = {}
64
57
  has_errors = False
65
-
58
+
66
59
  for filepath, file_info in staged.items():
67
60
  blob_hash = CommitCommand._get_blob_hash(file_info)
68
61
  if not blob_hash:
69
62
  continue
70
-
63
+
71
64
  # Read content from object store
72
65
  from ..core.objects import Blob
66
+
73
67
  blob = Blob.load(repo.object_store, blob_hash)
74
68
  if not blob:
75
69
  continue
76
-
70
+
77
71
  try:
78
- content = blob.content.decode('utf-8')
72
+ content = blob.content.decode("utf-8")
79
73
  except UnicodeDecodeError:
80
74
  # Skip binary files
81
75
  continue
82
-
76
+
83
77
  # Validate the file
84
78
  result = SchemaValidator.validate(content, filepath, strict=strict)
85
79
  validation_results[filepath] = result
86
-
80
+
87
81
  if not result.valid:
88
82
  has_errors = True
89
-
83
+
90
84
  return not has_errors, validation_results
91
-
85
+
92
86
  @staticmethod
93
87
  def _print_validation_results(results: dict) -> None:
94
88
  """Print validation results in a readable format."""
@@ -126,6 +120,7 @@ class CommitCommand:
126
120
  """Run memory tests if available. Returns 1 on failure, 0 on success or skip."""
127
121
  try:
128
122
  from ..core.test_runner import TestRunner
123
+
129
124
  test_runner = TestRunner(repo)
130
125
  test_result = test_runner.run_all()
131
126
  if not test_result.passed:
@@ -156,13 +151,19 @@ class CommitCommand:
156
151
  if args.run_tests:
157
152
  if CommitCommand._run_memory_tests(repo) != 0:
158
153
  return 1
159
- metadata = {
160
- 'files_changed': len(staged),
161
- 'timestamp': datetime.utcnow().isoformat() + 'Z'
162
- }
154
+ metadata = {"files_changed": len(staged), "timestamp": datetime.utcnow().isoformat() + "Z"}
155
+ # Importance scoring: explicit --importance or auto from heuristics
156
+ if args.importance is not None:
157
+ if not (0.0 <= args.importance <= 1.0):
158
+ print("Error: --importance must be between 0.0 and 1.0")
159
+ return 1
160
+ metadata["importance"] = args.importance
161
+ else:
162
+ suggested = compute_suggested_importance(repo, staged, args.message, metadata)
163
+ metadata["importance"] = suggested
163
164
  if args.author:
164
165
  config = repo.get_config()
165
- config['author']['name'] = args.author
166
+ config["author"]["name"] = args.author
166
167
  repo.set_config(config)
167
168
  try:
168
169
  commit_hash = repo.commit(args.message, metadata)