ragtime-cli 0.2.9__py3-none-any.whl → 0.2.10__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.

Potentially problematic release.


This version of ragtime-cli might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragtime-cli
3
- Version: 0.2.9
3
+ Version: 0.2.10
4
4
  Summary: Local-first memory and RAG system for Claude Code - semantic search over code, docs, and team knowledge
5
5
  Author-email: Bret Martineau <bretwardjames@gmail.com>
6
6
  License-Expression: MIT
@@ -100,13 +100,16 @@ ragtime forget <memory-id>
100
100
  # Index everything (docs + code)
101
101
  ragtime index
102
102
 
103
+ # Incremental index (only changed files - fast!)
104
+ ragtime index # ~8 seconds vs ~5 minutes for unchanged codebases
105
+
103
106
  # Index only docs
104
107
  ragtime index --type docs
105
108
 
106
109
  # Index only code (functions, classes, composables)
107
110
  ragtime index --type code
108
111
 
109
- # Re-index with clear (removes old entries)
112
+ # Full re-index (removes old entries, recomputes all embeddings)
110
113
  ragtime index --clear
111
114
 
112
115
  # Semantic search across all content
@@ -244,6 +247,18 @@ conventions:
244
247
  also_search_memories: true
245
248
  ```
246
249
 
250
+ ## How Search Works
251
+
252
+ Search returns **summaries with locations**, not full code:
253
+
254
+ 1. **What you get**: Function signatures, docstrings, class definitions
255
+ 2. **What you don't get**: Full implementations
256
+ 3. **What to do**: Use the file path + line number to read the full code
257
+
258
+ This is intentional - embeddings work better on focused summaries than large code blocks. The search tells you *what exists and where*, then you read the file for details.
259
+
260
+ For Claude/MCP usage: The search tool description instructs Claude to read returned file paths for full implementations before making code changes.
261
+
247
262
  ## Code Indexing
248
263
 
249
264
  The code indexer extracts meaningful symbols from your codebase:
@@ -251,7 +266,7 @@ The code indexer extracts meaningful symbols from your codebase:
251
266
  | Language | What Gets Indexed |
252
267
  |----------|-------------------|
253
268
  | Python | Classes, methods, functions (with docstrings) |
254
- | TypeScript/JS | Exported functions, classes, interfaces, types, constants |
269
+ | TypeScript/JS | Functions, classes, interfaces, types (exported and non-exported) |
255
270
  | Vue | Components, composable usage (useXxx calls) |
256
271
  | Dart | Classes, functions, mixins, extensions |
257
272
 
@@ -1,10 +1,10 @@
1
- ragtime_cli-0.2.9.dist-info/licenses/LICENSE,sha256=9A0wJs2PRDciGRH4F8JUJ-aMKYQyq_gVu2ixrXs-l5A,1070
1
+ ragtime_cli-0.2.10.dist-info/licenses/LICENSE,sha256=9A0wJs2PRDciGRH4F8JUJ-aMKYQyq_gVu2ixrXs-l5A,1070
2
2
  src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  src/cli.py,sha256=3Cn4D1W4Wok0mJB1fiBdynnCaDo4FWJo8Zay3Tp_ycQ,73978
4
4
  src/config.py,sha256=qyn5ADjPwvQlhwSJHwzat1v449b7MKEzIHr37Q5qhCc,4497
5
- src/db.py,sha256=Lm3zrj-6KBw4YlSCW17fu5GlaVqrK6AFw06xaW8E-Yw,6648
6
- src/mcp_server.py,sha256=ZU1wJQbquHVLl-lypkefZ1hc3o6s2v_3suIeiL5TgN8,21035
7
- src/memory.py,sha256=byzjAu2YaL9u4vNm6MLmaxeOWrg6RqRn5eJlIwxkEzA,12990
5
+ src/db.py,sha256=dxdGNhM-0Ke8yjcp69LTPS4L5vpGue-_2HwBPNvD2bg,7101
6
+ src/mcp_server.py,sha256=l834kmwumb9egzz5Sscfm8DdpPJLcqK7qX6a3sZ3EVk,21036
7
+ src/memory.py,sha256=qd-29w2rfkDzr1qIzxRgi9cr7skxjw8nSKhj7qjt1v4,14506
8
8
  src/commands/audit.md,sha256=Xkucm-gfBIMalK9wf7NBbyejpsqBTUAGGlb7GxMtMPY,5137
9
9
  src/commands/create-pr.md,sha256=u6-jVkDP_6bJQp6ImK039eY9F6B9E2KlAVlvLY-WV6Q,9483
10
10
  src/commands/generate-docs.md,sha256=9W2Yy-PDyC3p5k39uEb31z5YAHkSKsQLg6gV3tLgSnQ,7015
@@ -18,8 +18,8 @@ src/commands/start.md,sha256=qoqhkMgET74DBx8YPIT1-wqCiVBUDxlmevigsCinHSY,6506
18
18
  src/indexers/__init__.py,sha256=MYoCPZUpHakMX1s2vWnc9shjWfx_X1_0JzUhpKhnKUQ,454
19
19
  src/indexers/code.py,sha256=G2TbiKbWj0e7DV5KsU8-Ggw6ziDb4zTuZ4Bu3ryV4g8,18059
20
20
  src/indexers/docs.py,sha256=nyewQ4Ug4SCuhne4TuLDlUDzz9GH2STInddj81ocz50,3555
21
- ragtime_cli-0.2.9.dist-info/METADATA,sha256=5UyYXf9fk98ekvuIjPGeyCpr8h1rE2BAd8Aa1cuQ2ZQ,9875
22
- ragtime_cli-0.2.9.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
- ragtime_cli-0.2.9.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
24
- ragtime_cli-0.2.9.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
25
- ragtime_cli-0.2.9.dist-info/RECORD,,
21
+ ragtime_cli-0.2.10.dist-info/METADATA,sha256=UeWRU7V51UjtrulR1sNF1CsH2gMA3fjn9FMtMRjOqrw,10619
22
+ ragtime_cli-0.2.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
+ ragtime_cli-0.2.10.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
24
+ ragtime_cli-0.2.10.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
25
+ ragtime_cli-0.2.10.dist-info/RECORD,,
src/db.py CHANGED
@@ -94,26 +94,37 @@ class RagtimeDB:
94
94
  limit: Max results to return
95
95
  type_filter: "code" or "docs" (None = both)
96
96
  namespace: Filter by namespace (for docs)
97
- **filters: Additional metadata filters
97
+ **filters: Additional metadata filters (None values are ignored)
98
98
 
99
99
  Returns:
100
100
  List of dicts with 'content', 'metadata', 'distance'
101
101
  """
102
- where = {}
102
+ # Build list of filter conditions, excluding None values
103
+ conditions = []
103
104
 
104
105
  if type_filter:
105
- where["type"] = type_filter
106
+ conditions.append({"type": type_filter})
106
107
 
107
108
  if namespace:
108
- where["namespace"] = namespace
109
+ conditions.append({"namespace": namespace})
109
110
 
111
+ # Add any additional filters, but skip None values
110
112
  for key, value in filters.items():
111
- where[key] = value
113
+ if value is not None:
114
+ conditions.append({key: value})
115
+
116
+ # ChromaDB requires $and for multiple conditions
117
+ if len(conditions) == 0:
118
+ where = None
119
+ elif len(conditions) == 1:
120
+ where = conditions[0]
121
+ else:
122
+ where = {"$and": conditions}
112
123
 
113
124
  results = self.collection.query(
114
125
  query_texts=[query],
115
126
  n_results=limit,
116
- where=where if where else None,
127
+ where=where,
117
128
  )
118
129
 
119
130
  # Flatten results into list of dicts
src/mcp_server.py CHANGED
@@ -487,7 +487,7 @@ class RagtimeMCPServer:
487
487
  "protocolVersion": "2024-11-05",
488
488
  "serverInfo": {
489
489
  "name": "ragtime",
490
- "version": "0.2.9",
490
+ "version": "0.2.10",
491
491
  },
492
492
  "capabilities": {
493
493
  "tools": {},
src/memory.py CHANGED
@@ -32,6 +32,8 @@ class Memory:
32
32
  epic: Optional[str] = None
33
33
  branch: Optional[str] = None
34
34
  supersedes: Optional[str] = None
35
+ # Internal: actual file path when loaded from disk (not serialized)
36
+ _file_path: Optional[str] = field(default=None, repr=False)
35
37
 
36
38
  def to_frontmatter(self) -> dict:
37
39
  """Convert to YAML frontmatter dict."""
@@ -71,7 +73,8 @@ class Memory:
71
73
  def to_metadata(self) -> dict:
72
74
  """Convert to metadata dict for ChromaDB."""
73
75
  meta = self.to_frontmatter()
74
- meta["file"] = self.get_relative_path()
76
+ # Use actual file path if loaded from disk, otherwise generate it
77
+ meta["file"] = self._file_path if self._file_path else self.get_relative_path()
75
78
  return meta
76
79
 
77
80
  def get_relative_path(self) -> str:
@@ -107,8 +110,14 @@ class Memory:
107
110
  return slug[:40] # Limit length
108
111
 
109
112
  @classmethod
110
- def from_file(cls, path: Path) -> "Memory":
111
- """Parse a memory from a markdown file with YAML frontmatter."""
113
+ def from_file(cls, path: Path, relative_to: Optional[Path] = None) -> "Memory":
114
+ """
115
+ Parse a memory from a markdown file with YAML frontmatter.
116
+
117
+ Args:
118
+ path: Full path to the markdown file
119
+ relative_to: Base directory to compute relative path from (for indexing)
120
+ """
112
121
  text = path.read_text()
113
122
 
114
123
  if not text.startswith("---"):
@@ -122,6 +131,14 @@ class Memory:
122
131
  frontmatter = yaml.safe_load(parts[1])
123
132
  content = parts[2].strip()
124
133
 
134
+ # Compute relative file path for indexing
135
+ file_path = None
136
+ if relative_to:
137
+ try:
138
+ file_path = str(path.relative_to(relative_to))
139
+ except ValueError:
140
+ pass # path not relative to base, will regenerate
141
+
125
142
  return cls(
126
143
  id=frontmatter.get("id", str(uuid.uuid4())[:8]),
127
144
  content=content,
@@ -138,6 +155,7 @@ class Memory:
138
155
  epic=frontmatter.get("epic"),
139
156
  branch=frontmatter.get("branch"),
140
157
  supersedes=frontmatter.get("supersedes"),
158
+ _file_path=file_path,
141
159
  )
142
160
 
143
161
 
@@ -204,7 +222,8 @@ class MemoryStore:
204
222
  file_path = self.memory_dir / file_rel_path
205
223
 
206
224
  if file_path.exists():
207
- return Memory.from_file(file_path)
225
+ # Pass relative_to so the memory preserves its actual file path
226
+ return Memory.from_file(file_path, relative_to=self.memory_dir)
208
227
 
209
228
  return None
210
229
 
@@ -283,29 +302,41 @@ class MemoryStore:
283
302
  limit: int = 100,
284
303
  ) -> list[Memory]:
285
304
  """List memories with optional filters."""
286
- where = {}
305
+ # Build filter conditions
306
+ conditions = []
307
+ namespace_prefix = None
287
308
 
288
309
  if namespace:
289
310
  if namespace.endswith("*"):
290
- # Prefix match - ChromaDB doesn't support this directly
291
- # We'll filter in Python
292
- pass
311
+ # Prefix match - filter in Python after fetching
312
+ namespace_prefix = namespace[:-1]
293
313
  else:
294
- where["namespace"] = namespace
314
+ conditions.append({"namespace": namespace})
295
315
 
296
316
  if type_filter:
297
- where["type"] = type_filter
317
+ conditions.append({"type": type_filter})
298
318
 
299
319
  if status:
300
- where["status"] = status
320
+ conditions.append({"status": status})
301
321
 
302
322
  if component:
303
- where["component"] = component
323
+ conditions.append({"component": component})
324
+
325
+ # Build where clause with $and if multiple conditions
326
+ if len(conditions) == 0:
327
+ where = None
328
+ elif len(conditions) == 1:
329
+ where = conditions[0]
330
+ else:
331
+ where = {"$and": conditions}
332
+
333
+ # When using prefix match, fetch more results since we'll filter some out
334
+ fetch_limit = limit * 5 if namespace_prefix else limit
304
335
 
305
336
  # Get from ChromaDB
306
337
  results = self.db.collection.get(
307
- where=where if where else None,
308
- limit=limit,
338
+ where=where,
339
+ limit=fetch_limit,
309
340
  )
310
341
 
311
342
  memories = []
@@ -314,9 +345,8 @@ class MemoryStore:
314
345
  content = results["documents"][i] if results["documents"] else ""
315
346
 
316
347
  # Handle namespace prefix filtering
317
- if namespace and namespace.endswith("*"):
318
- prefix = namespace[:-1]
319
- if not metadata.get("namespace", "").startswith(prefix):
348
+ if namespace_prefix:
349
+ if not metadata.get("namespace", "").startswith(namespace_prefix):
320
350
  continue
321
351
 
322
352
  memories.append(Memory(
@@ -332,6 +362,10 @@ class MemoryStore:
332
362
  author=metadata.get("author"),
333
363
  ))
334
364
 
365
+ # Stop once we have enough
366
+ if len(memories) >= limit:
367
+ break
368
+
335
369
  return memories
336
370
 
337
371
  def store_document(self, file_path: Path, namespace: str, doc_type: str = "handoff") -> Memory:
@@ -367,7 +401,8 @@ class MemoryStore:
367
401
  count = 0
368
402
  for md_file in self.memory_dir.rglob("*.md"):
369
403
  try:
370
- memory = Memory.from_file(md_file)
404
+ # Pass memory_dir so the actual file path is stored, not regenerated
405
+ memory = Memory.from_file(md_file, relative_to=self.memory_dir)
371
406
  self.db.upsert(
372
407
  ids=[memory.id],
373
408
  documents=[memory.content],