ragtime-cli 0.2.8__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.
- {ragtime_cli-0.2.8.dist-info → ragtime_cli-0.2.10.dist-info}/METADATA +18 -3
- {ragtime_cli-0.2.8.dist-info → ragtime_cli-0.2.10.dist-info}/RECORD +10 -10
- src/cli.py +2 -2
- src/db.py +17 -6
- src/mcp_server.py +2 -2
- src/memory.py +53 -18
- {ragtime_cli-0.2.8.dist-info → ragtime_cli-0.2.10.dist-info}/WHEEL +0 -0
- {ragtime_cli-0.2.8.dist-info → ragtime_cli-0.2.10.dist-info}/entry_points.txt +0 -0
- {ragtime_cli-0.2.8.dist-info → ragtime_cli-0.2.10.dist-info}/licenses/LICENSE +0 -0
- {ragtime_cli-0.2.8.dist-info → ragtime_cli-0.2.10.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ragtime-cli
|
|
3
|
-
Version: 0.2.
|
|
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
|
-
#
|
|
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 |
|
|
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.
|
|
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
|
-
src/cli.py,sha256=
|
|
3
|
+
src/cli.py,sha256=3Cn4D1W4Wok0mJB1fiBdynnCaDo4FWJo8Zay3Tp_ycQ,73978
|
|
4
4
|
src/config.py,sha256=qyn5ADjPwvQlhwSJHwzat1v449b7MKEzIHr37Q5qhCc,4497
|
|
5
|
-
src/db.py,sha256=
|
|
6
|
-
src/mcp_server.py,sha256=
|
|
7
|
-
src/memory.py,sha256=
|
|
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.
|
|
22
|
-
ragtime_cli-0.2.
|
|
23
|
-
ragtime_cli-0.2.
|
|
24
|
-
ragtime_cli-0.2.
|
|
25
|
-
ragtime_cli-0.2.
|
|
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/cli.py
CHANGED
|
@@ -169,7 +169,7 @@ def get_remote_branches_with_ragtime(path: Path) -> list[str]:
|
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
@click.group()
|
|
172
|
-
@click.version_option(version="0.2.
|
|
172
|
+
@click.version_option(version="0.2.9")
|
|
173
173
|
def main():
|
|
174
174
|
"""Ragtime - semantic search over code and documentation."""
|
|
175
175
|
pass
|
|
@@ -2137,7 +2137,7 @@ def update(check: bool):
|
|
|
2137
2137
|
from urllib.request import urlopen
|
|
2138
2138
|
from urllib.error import URLError
|
|
2139
2139
|
|
|
2140
|
-
current = "0.2.
|
|
2140
|
+
current = "0.2.9"
|
|
2141
2141
|
|
|
2142
2142
|
click.echo(f"Current version: {current}")
|
|
2143
2143
|
click.echo("Checking PyPI for updates...")
|
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
|
-
|
|
102
|
+
# Build list of filter conditions, excluding None values
|
|
103
|
+
conditions = []
|
|
103
104
|
|
|
104
105
|
if type_filter:
|
|
105
|
-
|
|
106
|
+
conditions.append({"type": type_filter})
|
|
106
107
|
|
|
107
108
|
if namespace:
|
|
108
|
-
|
|
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
|
-
|
|
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
|
|
127
|
+
where=where,
|
|
117
128
|
)
|
|
118
129
|
|
|
119
130
|
# Flatten results into list of dicts
|
src/mcp_server.py
CHANGED
|
@@ -132,7 +132,7 @@ class RagtimeMCPServer:
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
"name": "search",
|
|
135
|
-
"description": "Semantic search over indexed
|
|
135
|
+
"description": "Semantic search over indexed code and docs. Returns function signatures, class definitions, and doc summaries with file paths and line numbers. IMPORTANT: Results are summaries only - use the Read tool on returned file paths to see full implementations before making code changes or decisions.",
|
|
136
136
|
"inputSchema": {
|
|
137
137
|
"type": "object",
|
|
138
138
|
"properties": {
|
|
@@ -487,7 +487,7 @@ class RagtimeMCPServer:
|
|
|
487
487
|
"protocolVersion": "2024-11-05",
|
|
488
488
|
"serverInfo": {
|
|
489
489
|
"name": "ragtime",
|
|
490
|
-
"version": "0.2.
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
305
|
+
# Build filter conditions
|
|
306
|
+
conditions = []
|
|
307
|
+
namespace_prefix = None
|
|
287
308
|
|
|
288
309
|
if namespace:
|
|
289
310
|
if namespace.endswith("*"):
|
|
290
|
-
# Prefix match -
|
|
291
|
-
|
|
292
|
-
pass
|
|
311
|
+
# Prefix match - filter in Python after fetching
|
|
312
|
+
namespace_prefix = namespace[:-1]
|
|
293
313
|
else:
|
|
294
|
-
|
|
314
|
+
conditions.append({"namespace": namespace})
|
|
295
315
|
|
|
296
316
|
if type_filter:
|
|
297
|
-
|
|
317
|
+
conditions.append({"type": type_filter})
|
|
298
318
|
|
|
299
319
|
if status:
|
|
300
|
-
|
|
320
|
+
conditions.append({"status": status})
|
|
301
321
|
|
|
302
322
|
if component:
|
|
303
|
-
|
|
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
|
|
308
|
-
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
|
|
318
|
-
|
|
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
|
-
|
|
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],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|