ragtime-cli 0.2.13__py3-none-any.whl → 0.2.14__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.13
3
+ Version: 0.2.14
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
@@ -1,9 +1,9 @@
1
- ragtime_cli-0.2.13.dist-info/licenses/LICENSE,sha256=9A0wJs2PRDciGRH4F8JUJ-aMKYQyq_gVu2ixrXs-l5A,1070
1
+ ragtime_cli-0.2.14.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=jVkl3MuttkqphMq3T_jfg49Lf_fdTHVDoQ5YDchygk4,76659
3
+ src/cli.py,sha256=RO9LIvZrJ8AG7RaTpR3FsYP2NsShGKJjjiomgfH7xYc,76926
4
4
  src/config.py,sha256=tQ6gPLr4ksn2bJPIUjtELFr-k01Eg4g-LDo3GNE6P0Q,4600
5
- src/db.py,sha256=ueSThFXkhI5MFwXICkNW3zqCawGDi3kqFQnbm4st_Ew,8186
6
- src/mcp_server.py,sha256=NBZaqVwYfuXxKEZp9alis04IE2CZoofkoJzRTs6qExw,21559
5
+ src/db.py,sha256=KcDaaqqNMDnodD8zIWC-_y3OE-kQ0Iib1YQ1ChwtOH8,11590
6
+ src/mcp_server.py,sha256=n7T5gtgySilLDLhtaYnbHxBhr6Ys70F4ZpIu3lLOvHM,21973
7
7
  src/memory.py,sha256=UiHyudKbseMMY-sdcaDSfVBMGj6sFXXw1GxBsZ7nuBc,18450
8
8
  src/commands/audit.md,sha256=Xkucm-gfBIMalK9wf7NBbyejpsqBTUAGGlb7GxMtMPY,5137
9
9
  src/commands/create-pr.md,sha256=u6-jVkDP_6bJQp6ImK039eY9F6B9E2KlAVlvLY-WV6Q,9483
@@ -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.13.dist-info/METADATA,sha256=vHivVa4-y0GQ01--zYlzHpUInn59RQsPw8J1ItYAUI0,11269
22
- ragtime_cli-0.2.13.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
- ragtime_cli-0.2.13.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
24
- ragtime_cli-0.2.13.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
25
- ragtime_cli-0.2.13.dist-info/RECORD,,
21
+ ragtime_cli-0.2.14.dist-info/METADATA,sha256=n_VCE2tgWEFA7fIDEzZjYm8vy9kZPeAKgx9cu2Esbug,11269
22
+ ragtime_cli-0.2.14.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
+ ragtime_cli-0.2.14.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
24
+ ragtime_cli-0.2.14.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
25
+ ragtime_cli-0.2.14.dist-info/RECORD,,
src/cli.py CHANGED
@@ -470,17 +470,21 @@ def index(path: Path, index_type: str, clear: bool):
470
470
  @click.option("--type", "type_filter", type=click.Choice(["all", "docs", "code"]), default="all")
471
471
  @click.option("--namespace", "-n", help="Filter by namespace")
472
472
  @click.option("--require", "-r", "require_terms", multiple=True,
473
- help="Terms that MUST appear in results (repeatable)")
473
+ help="Additional terms that MUST appear (usually auto-detected)")
474
+ @click.option("--raw", is_flag=True, help="Disable auto-detection of qualifiers")
474
475
  @click.option("--include-archive", is_flag=True, help="Also search archived branches")
475
476
  @click.option("--limit", "-l", default=5, help="Max results")
476
477
  @click.option("--verbose", "-v", is_flag=True, help="Show full content")
477
478
  def search(query: str, path: Path, type_filter: str, namespace: str,
478
- require_terms: tuple, include_archive: bool, limit: int, verbose: bool):
479
+ require_terms: tuple, raw: bool, include_archive: bool, limit: int, verbose: bool):
479
480
  """
480
- Hybrid search: semantic similarity + keyword filtering.
481
+ Smart search: auto-detects qualifiers like 'mobile', 'auth', 'dart'.
481
482
 
482
- Use --require/-r to ensure specific terms appear in results.
483
- Example: ragtime search "error handling" -r mobile -r dart
483
+ \b
484
+ Examples:
485
+ ragtime search "error handling in mobile" # auto-requires 'mobile'
486
+ ragtime search "auth flow" # auto-requires 'auth'
487
+ ragtime search "useAsyncState" --raw # literal search, no extraction
484
488
  """
485
489
  path = Path(path).resolve()
486
490
  db = get_db(path)
@@ -493,6 +497,7 @@ def search(query: str, path: Path, type_filter: str, namespace: str,
493
497
  type_filter=type_arg,
494
498
  namespace=namespace,
495
499
  require_terms=list(require_terms) if require_terms else None,
500
+ auto_extract=not raw,
496
501
  )
497
502
 
498
503
  if not results:
src/db.py CHANGED
@@ -4,12 +4,74 @@ ChromaDB wrapper for ragtime.
4
4
  Handles storage and retrieval of indexed documents and code.
5
5
  """
6
6
 
7
+ import re
7
8
  from pathlib import Path
8
9
  from typing import Any
9
10
  import chromadb
10
11
  from chromadb.config import Settings
11
12
 
12
13
 
14
+ def extract_query_hints(query: str, known_components: list[str] | None = None) -> tuple[str, list[str]]:
15
+ """
16
+ Extract component/scope hints from a query for hybrid search.
17
+
18
+ Detects patterns like "X in mobile", "mobile X", "X for auth" and extracts
19
+ the qualifier to use as require_terms. This prevents qualifiers from being
20
+ diluted in semantic search.
21
+
22
+ Args:
23
+ query: The natural language search query
24
+ known_components: Optional list of known component names to detect
25
+
26
+ Returns:
27
+ (cleaned_query, extracted_terms) - query with hints removed, terms to require
28
+ """
29
+ # Default known components/scopes (common patterns)
30
+ default_components = [
31
+ # Platforms
32
+ "mobile", "web", "desktop", "ios", "android", "flutter", "react", "vue",
33
+ # Languages
34
+ "dart", "python", "typescript", "javascript", "ts", "js", "py",
35
+ # Common components
36
+ "auth", "authentication", "api", "database", "db", "ui", "frontend", "backend",
37
+ "server", "client", "admin", "user", "payment", "billing", "notification",
38
+ "email", "cache", "queue", "worker", "scheduler", "logging", "metrics",
39
+ ]
40
+
41
+ components = set(c.lower() for c in (known_components or default_components))
42
+ extracted = []
43
+ cleaned = query
44
+
45
+ # Pattern 1: "X in/for/on {component}" - extract component
46
+ patterns = [
47
+ r'\b(?:in|for|on|from|using|with)\s+(?:the\s+)?(\w+)\s*(?:app|code|module|service|codebase)?(?:\s|$)',
48
+ r'\b(\w+)\s+(?:app|code|module|service|codebase)\b',
49
+ ]
50
+
51
+ for pattern in patterns:
52
+ for match in re.finditer(pattern, query, re.IGNORECASE):
53
+ word = match.group(1).lower()
54
+ if word in components:
55
+ extracted.append(word)
56
+ # Remove the matched phrase from query
57
+ cleaned = cleaned[:match.start()] + " " + cleaned[match.end():]
58
+
59
+ # Pattern 2: Check if any known component appears as standalone word
60
+ words = re.findall(r'\b\w+\b', query.lower())
61
+ for word in words:
62
+ if word in components and word not in extracted:
63
+ # Only extract if it looks like a qualifier (not the main subject)
64
+ # Heuristic: if query has other meaningful words, it's likely a qualifier
65
+ other_words = [w for w in words if w != word and len(w) > 3]
66
+ if len(other_words) >= 2:
67
+ extracted.append(word)
68
+
69
+ # Clean up extra whitespace
70
+ cleaned = re.sub(r'\s+', ' ', cleaned).strip()
71
+
72
+ return cleaned, list(set(extracted))
73
+
74
+
13
75
  class RagtimeDB:
14
76
  """Vector database for ragtime indexes."""
15
77
 
@@ -85,6 +147,7 @@ class RagtimeDB:
85
147
  type_filter: str | None = None,
86
148
  namespace: str | None = None,
87
149
  require_terms: list[str] | None = None,
150
+ auto_extract: bool = True,
88
151
  **filters,
89
152
  ) -> list[dict]:
90
153
  """
@@ -98,11 +161,26 @@ class RagtimeDB:
98
161
  require_terms: List of terms that MUST appear in results (case-insensitive).
99
162
  Use for scoped queries like "error handling in mobile" with
100
163
  require_terms=["mobile"] to ensure "mobile" isn't ignored.
164
+ auto_extract: If True (default), automatically detect component qualifiers
165
+ in the query and add them to require_terms. Set to False
166
+ for raw/literal search.
101
167
  **filters: Additional metadata filters (None values are ignored)
102
168
 
103
169
  Returns:
104
170
  List of dicts with 'content', 'metadata', 'distance'
105
171
  """
172
+ # Auto-extract component hints from query if enabled
173
+ search_query = query
174
+ all_require_terms = list(require_terms) if require_terms else []
175
+
176
+ if auto_extract:
177
+ cleaned_query, extracted = extract_query_hints(query)
178
+ if extracted:
179
+ # Use cleaned query for embedding (removes noise)
180
+ search_query = cleaned_query
181
+ # Add extracted terms to require_terms
182
+ all_require_terms.extend(extracted)
183
+ all_require_terms = list(set(all_require_terms)) # dedupe
106
184
  # Build list of filter conditions, excluding None values
107
185
  conditions = []
108
186
 
@@ -126,10 +204,10 @@ class RagtimeDB:
126
204
  where = {"$and": conditions}
127
205
 
128
206
  # When using require_terms, fetch more results since we'll filter some out
129
- fetch_limit = limit * 5 if require_terms else limit
207
+ fetch_limit = limit * 5 if all_require_terms else limit
130
208
 
131
209
  results = self.collection.query(
132
- query_texts=[query],
210
+ query_texts=[search_query],
133
211
  n_results=fetch_limit,
134
212
  where=where,
135
213
  )
@@ -139,13 +217,13 @@ class RagtimeDB:
139
217
  if results["documents"] and results["documents"][0]:
140
218
  for i, doc in enumerate(results["documents"][0]):
141
219
  # Hybrid filtering: ensure required terms appear
142
- if require_terms:
220
+ if all_require_terms:
143
221
  doc_lower = doc.lower()
144
222
  # Also check file path in metadata for code/file matches
145
223
  file_path = (results["metadatas"][0][i].get("file", "") or "").lower()
146
224
  combined_text = f"{doc_lower} {file_path}"
147
225
 
148
- if not all(term.lower() in combined_text for term in require_terms):
226
+ if not all(term.lower() in combined_text for term in all_require_terms):
149
227
  continue
150
228
 
151
229
  output.append({
src/mcp_server.py CHANGED
@@ -132,13 +132,13 @@ class RagtimeMCPServer:
132
132
  },
133
133
  {
134
134
  "name": "search",
135
- "description": "Hybrid search over indexed code and docs (semantic + keyword). 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.",
135
+ "description": "Smart hybrid search over indexed code and docs. Auto-detects qualifiers like 'mobile', 'auth', 'dart' in your query and ensures they appear in results. 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.",
136
136
  "inputSchema": {
137
137
  "type": "object",
138
138
  "properties": {
139
139
  "query": {
140
140
  "type": "string",
141
- "description": "Natural language search query"
141
+ "description": "Natural language search query. Qualifiers like 'in mobile', 'for auth', 'dart' are auto-detected and used for filtering."
142
142
  },
143
143
  "namespace": {
144
144
  "type": "string",
@@ -155,7 +155,12 @@ class RagtimeMCPServer:
155
155
  "require_terms": {
156
156
  "type": "array",
157
157
  "items": {"type": "string"},
158
- "description": "Terms that MUST appear in results (case-insensitive). Use for scoped queries like 'error handling in mobile' with require_terms=['mobile'] to ensure the qualifier isn't lost in semantic search."
158
+ "description": "Additional terms that MUST appear in results. Usually not needed since qualifiers are auto-detected from the query."
159
+ },
160
+ "auto_extract": {
161
+ "type": "boolean",
162
+ "default": True,
163
+ "description": "Auto-detect component qualifiers from query (default: true). Set to false for literal/raw search."
159
164
  },
160
165
  "limit": {
161
166
  "type": "integer",
@@ -338,7 +343,7 @@ class RagtimeMCPServer:
338
343
  }
339
344
 
340
345
  def _search(self, args: dict) -> dict:
341
- """Search indexed content with hybrid semantic + keyword matching."""
346
+ """Search indexed content with smart query understanding."""
342
347
  results = self.db.search(
343
348
  query=args["query"],
344
349
  limit=args.get("limit", 10),
@@ -346,6 +351,7 @@ class RagtimeMCPServer:
346
351
  type_filter=args.get("type"),
347
352
  component=args.get("component"),
348
353
  require_terms=args.get("require_terms"),
354
+ auto_extract=args.get("auto_extract", True),
349
355
  )
350
356
 
351
357
  return {
@@ -493,7 +499,7 @@ class RagtimeMCPServer:
493
499
  "protocolVersion": "2024-11-05",
494
500
  "serverInfo": {
495
501
  "name": "ragtime",
496
- "version": "0.2.13",
502
+ "version": "0.2.14",
497
503
  },
498
504
  "capabilities": {
499
505
  "tools": {},