code-memory 1.0.10__tar.gz → 1.0.11__tar.gz

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 (39) hide show
  1. {code_memory-1.0.10 → code_memory-1.0.11}/.github/workflows/release-binaries.yml +1 -1
  2. {code_memory-1.0.10 → code_memory-1.0.11}/PKG-INFO +1 -1
  3. {code_memory-1.0.10 → code_memory-1.0.11}/parser.py +87 -28
  4. {code_memory-1.0.10 → code_memory-1.0.11}/pyproject.toml +1 -1
  5. {code_memory-1.0.10 → code_memory-1.0.11}/.github/workflows/ci.yml +0 -0
  6. {code_memory-1.0.10 → code_memory-1.0.11}/.github/workflows/publish.yml +0 -0
  7. {code_memory-1.0.10 → code_memory-1.0.11}/.gitignore +0 -0
  8. {code_memory-1.0.10 → code_memory-1.0.11}/.python-version +0 -0
  9. {code_memory-1.0.10 → code_memory-1.0.11}/CHANGELOG.md +0 -0
  10. {code_memory-1.0.10 → code_memory-1.0.11}/CONTRIBUTING.md +0 -0
  11. {code_memory-1.0.10 → code_memory-1.0.11}/LICENSE +0 -0
  12. {code_memory-1.0.10 → code_memory-1.0.11}/Makefile +0 -0
  13. {code_memory-1.0.10 → code_memory-1.0.11}/README.md +0 -0
  14. {code_memory-1.0.10 → code_memory-1.0.11}/code-memory.spec +0 -0
  15. {code_memory-1.0.10 → code_memory-1.0.11}/db.py +0 -0
  16. {code_memory-1.0.10 → code_memory-1.0.11}/doc_parser.py +0 -0
  17. {code_memory-1.0.10 → code_memory-1.0.11}/errors.py +0 -0
  18. {code_memory-1.0.10 → code_memory-1.0.11}/git_search.py +0 -0
  19. {code_memory-1.0.10 → code_memory-1.0.11}/hooks/hook-sentence_transformers.py +0 -0
  20. {code_memory-1.0.10 → code_memory-1.0.11}/hooks/hook-sqlite_vec.py +0 -0
  21. {code_memory-1.0.10 → code_memory-1.0.11}/hooks/hook-tree_sitter.py +0 -0
  22. {code_memory-1.0.10 → code_memory-1.0.11}/hooks/hook-tree_sitter_languages.py +0 -0
  23. {code_memory-1.0.10 → code_memory-1.0.11}/logging_config.py +0 -0
  24. {code_memory-1.0.10 → code_memory-1.0.11}/prompts/milestone_1.xml +0 -0
  25. {code_memory-1.0.10 → code_memory-1.0.11}/prompts/milestone_2.xml +0 -0
  26. {code_memory-1.0.10 → code_memory-1.0.11}/prompts/milestone_3.xml +0 -0
  27. {code_memory-1.0.10 → code_memory-1.0.11}/prompts/milestone_4.xml +0 -0
  28. {code_memory-1.0.10 → code_memory-1.0.11}/prompts/milestone_5.xml +0 -0
  29. {code_memory-1.0.10 → code_memory-1.0.11}/prompts/milestone_6.xml +0 -0
  30. {code_memory-1.0.10 → code_memory-1.0.11}/queries.py +0 -0
  31. {code_memory-1.0.10 → code_memory-1.0.11}/server.py +0 -0
  32. {code_memory-1.0.10 → code_memory-1.0.11}/tests/__init__.py +0 -0
  33. {code_memory-1.0.10 → code_memory-1.0.11}/tests/conftest.py +0 -0
  34. {code_memory-1.0.10 → code_memory-1.0.11}/tests/test_errors.py +0 -0
  35. {code_memory-1.0.10 → code_memory-1.0.11}/tests/test_logging.py +0 -0
  36. {code_memory-1.0.10 → code_memory-1.0.11}/tests/test_tools.py +0 -0
  37. {code_memory-1.0.10 → code_memory-1.0.11}/tests/test_validation.py +0 -0
  38. {code_memory-1.0.10 → code_memory-1.0.11}/uv.lock +0 -0
  39. {code_memory-1.0.10 → code_memory-1.0.11}/validation.py +0 -0
@@ -53,7 +53,7 @@ jobs:
53
53
 
54
54
  - name: Download embedding model (cache for offline use)
55
55
  run: |
56
- python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('jinaai/jina-code-embeddings-v3')"
56
+ python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('jinaai/jina-code-embeddings-0.5b', trust_remote_code=True)"
57
57
 
58
58
  - name: Build binary with PyInstaller (Linux/Windows)
59
59
  if: matrix.os != 'macos-latest' || matrix.target == 'macos-x86_64'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-memory
3
- Version: 1.0.10
3
+ Version: 1.0.11
4
4
  Summary: A deterministic, high-precision code intelligence MCP server
5
5
  Project-URL: Homepage, https://github.com/kapillamba4/code-memory
6
6
  Project-URL: Documentation, https://github.com/kapillamba4/code-memory#readme
@@ -47,22 +47,79 @@ def _load_gitignore_spec(root_dir: str) -> pathspec.PathSpec | None:
47
47
  return None
48
48
 
49
49
 
50
- def _should_skip_path(
51
- rel_path: str,
52
- is_dir: bool,
53
- gitignore_spec: pathspec.PathSpec | None,
54
- ) -> bool:
55
- """Check if a path should be skipped based on .gitignore patterns."""
56
- if gitignore_spec is None:
57
- return False
50
+ class GitignoreMatcher:
51
+ """Manages .gitignore matching with support for nested .gitignore files.
52
+
53
+ Git reads all .gitignore files in the directory tree, not just the root.
54
+ Each nested .gitignore applies patterns relative to its own directory.
55
+ """
56
+
57
+ def __init__(self, root_dir: str):
58
+ self.root_dir = root_dir
59
+ self._specs: dict[str, pathspec.PathSpec] = {}
58
60
 
59
- # Check both the path as-is and with trailing slash for directories
60
- if gitignore_spec.match_file(rel_path):
61
- return True
62
- if is_dir and gitignore_spec.match_file(rel_path + "/"):
63
- return True
61
+ # Load root .gitignore if it exists
62
+ root_spec = _load_gitignore_spec(root_dir)
63
+ if root_spec:
64
+ self._specs["."] = root_spec
65
+
66
+ def _load_spec_for_dir(self, abs_dir: str, rel_dir: str) -> None:
67
+ """Load .gitignore for a directory if not already loaded."""
68
+ if rel_dir in self._specs:
69
+ return
70
+
71
+ spec = _load_gitignore_spec(abs_dir)
72
+ if spec:
73
+ self._specs[rel_dir] = spec
74
+
75
+ def _get_parent_specs(self, rel_path: str) -> list[tuple[str, pathspec.PathSpec]]:
76
+ """Get all applicable gitignore specs for a given path.
77
+
78
+ Returns list of (base_dir, spec) tuples for specs that apply to this path.
79
+ """
80
+ result = []
81
+ path_parts = rel_path.replace("\\", "/").split("/")
82
+
83
+ # Check all ancestor directories that have .gitignore files
84
+ for base_dir, spec in self._specs.items():
85
+ if base_dir == ".":
86
+ # Root spec applies to everything
87
+ result.append((base_dir, spec))
88
+ else:
89
+ # Nested spec only applies if path is under that directory
90
+ base_parts = base_dir.replace("\\", "/").split("/")
91
+ if path_parts[:len(base_parts)] == base_parts:
92
+ result.append((base_dir, spec))
93
+
94
+ return result
95
+
96
+ def should_skip(self, rel_path: str, is_dir: bool) -> bool:
97
+ """Check if a path should be skipped based on all applicable .gitignore patterns."""
98
+ # Normalize path separators for matching
99
+ rel_path = rel_path.replace("\\", "/")
100
+
101
+ for base_dir, spec in self._get_parent_specs(rel_path):
102
+ # For nested gitignores, compute path relative to that gitignore's directory
103
+ if base_dir == ".":
104
+ check_path = rel_path
105
+ else:
106
+ base_prefix = base_dir.replace("\\", "/") + "/"
107
+ if rel_path.startswith(base_prefix):
108
+ check_path = rel_path[len(base_prefix):]
109
+ else:
110
+ continue
64
111
 
65
- return False
112
+ # Check both the path as-is and with trailing slash for directories
113
+ if spec.match_file(check_path):
114
+ return True
115
+ if is_dir and spec.match_file(check_path + "/"):
116
+ return True
117
+
118
+ return False
119
+
120
+ def check_dir_for_gitignore(self, abs_dir: str, rel_dir: str) -> None:
121
+ """Check if directory contains a .gitignore and load it."""
122
+ self._load_spec_for_dir(abs_dir, rel_dir)
66
123
 
67
124
  # ── File extensions we consider "source code" ─────────────────────────
68
125
  _SOURCE_EXTENSIONS = frozenset({
@@ -397,8 +454,9 @@ def index_file(filepath: str, db) -> dict:
397
454
  def index_directory(dirpath: str, db) -> list[dict]:
398
455
  """Recursively index all source files under *dirpath*.
399
456
 
400
- Skips directories in ``_SKIP_DIRS``, files matching ``.gitignore`` patterns,
401
- and unchanged files. Indexes any file with a recognised source-code extension.
457
+ Skips directories in ``_SKIP_DIRS``, files matching ``.gitignore`` patterns
458
+ (including nested .gitignore files), and unchanged files. Indexes any file
459
+ with a recognised source-code extension.
402
460
 
403
461
  Args:
404
462
  dirpath: Root directory to scan.
@@ -413,32 +471,33 @@ def index_directory(dirpath: str, db) -> list[dict]:
413
471
  dirpath = os.path.abspath(dirpath)
414
472
  total_start = time.perf_counter()
415
473
 
416
- # Load .gitignore patterns from the root directory
417
- gitignore_spec = _load_gitignore_spec(dirpath)
418
- if gitignore_spec:
419
- logger.debug("Loaded .gitignore patterns from %s", dirpath)
474
+ # Initialize gitignore matcher (supports nested .gitignore files)
475
+ gitignore = GitignoreMatcher(dirpath)
476
+ logger.debug("Initialized gitignore matcher for %s", dirpath)
420
477
 
421
478
  for root, dirs, files in os.walk(dirpath, topdown=True):
422
479
  rel_root = os.path.relpath(root, dirpath)
423
480
 
481
+ # Check for .gitignore in current directory and load it
482
+ if rel_root != ".":
483
+ gitignore.check_dir_for_gitignore(root, rel_root)
484
+
424
485
  # Prune skipped directories in-place (always-skip + gitignore)
425
486
  def _should_keep_dir(d: str) -> bool:
426
487
  if d in _SKIP_DIRS or d.endswith(".egg-info"):
427
488
  return False
428
- if gitignore_spec:
429
- rel_path = os.path.join(rel_root, d) if rel_root != "." else d
430
- if _should_skip_path(rel_path, is_dir=True, gitignore_spec=gitignore_spec):
431
- return False
489
+ rel_path = os.path.join(rel_root, d) if rel_root != "." else d
490
+ if gitignore.should_skip(rel_path, is_dir=True):
491
+ return False
432
492
  return True
433
493
 
434
494
  dirs[:] = [d for d in dirs if _should_keep_dir(d)]
435
495
 
436
496
  for fname in sorted(files):
437
497
  # Skip files matching .gitignore patterns
438
- if gitignore_spec:
439
- rel_path = os.path.join(rel_root, fname) if rel_root != "." else fname
440
- if _should_skip_path(rel_path, is_dir=False, gitignore_spec=gitignore_spec):
441
- continue
498
+ rel_path = os.path.join(rel_root, fname) if rel_root != "." else fname
499
+ if gitignore.should_skip(rel_path, is_dir=False):
500
+ continue
442
501
 
443
502
  ext = os.path.splitext(fname)[1].lower()
444
503
  # Accept files with known extensions, or files with a
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-memory"
7
- version = "1.0.10"
7
+ version = "1.0.11"
8
8
  description = "A deterministic, high-precision code intelligence MCP server"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes