ragtime-cli 0.2.4__tar.gz → 0.2.6__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.
- {ragtime_cli-0.2.4/ragtime_cli.egg-info → ragtime_cli-0.2.6}/PKG-INFO +51 -6
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/README.md +50 -5
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/pyproject.toml +1 -1
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6/ragtime_cli.egg-info}/PKG-INFO +51 -6
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/ragtime_cli.egg-info/SOURCES.txt +1 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/cli.py +95 -19
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/config.py +1 -1
- ragtime_cli-0.2.6/src/indexers/__init__.py +11 -0
- ragtime_cli-0.2.6/src/indexers/code.py +473 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/mcp_server.py +1 -1
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/memory.py +6 -1
- ragtime_cli-0.2.4/src/indexers/__init__.py +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/LICENSE +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/ragtime_cli.egg-info/dependency_links.txt +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/ragtime_cli.egg-info/entry_points.txt +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/ragtime_cli.egg-info/requires.txt +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/ragtime_cli.egg-info/top_level.txt +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/setup.cfg +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/__init__.py +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/audit.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/create-pr.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/generate-docs.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/handoff.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/import-docs.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/pr-graduate.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/recall.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/remember.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/save.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/commands/start.md +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/db.py +0 -0
- {ragtime_cli-0.2.4 → ragtime_cli-0.2.6}/src/indexers/docs.py +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.6
|
|
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
|
|
@@ -35,7 +35,8 @@ Local-first memory and RAG system for Claude Code. Semantic search over code, do
|
|
|
35
35
|
## Features
|
|
36
36
|
|
|
37
37
|
- **Memory Storage**: Store structured knowledge with namespaces, types, and metadata
|
|
38
|
-
- **Semantic Search**: Query memories and
|
|
38
|
+
- **Semantic Search**: Query memories, docs, and code with natural language
|
|
39
|
+
- **Code Indexing**: Index functions, classes, and composables from Python, TypeScript, Vue, and Dart
|
|
39
40
|
- **Cross-Branch Sync**: Share context with teammates before PRs merge
|
|
40
41
|
- **Convention Checking**: Verify code follows team standards before PRs
|
|
41
42
|
- **Doc Generation**: Generate documentation from code (stubs or AI-powered)
|
|
@@ -96,11 +97,26 @@ ragtime forget <memory-id>
|
|
|
96
97
|
### Search & Indexing
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
|
-
# Index docs
|
|
100
|
+
# Index everything (docs + code)
|
|
101
|
+
ragtime index
|
|
102
|
+
|
|
103
|
+
# Index only docs
|
|
100
104
|
ragtime index --type docs
|
|
101
105
|
|
|
102
|
-
#
|
|
103
|
-
ragtime
|
|
106
|
+
# Index only code (functions, classes, composables)
|
|
107
|
+
ragtime index --type code
|
|
108
|
+
|
|
109
|
+
# Re-index with clear (removes old entries)
|
|
110
|
+
ragtime index --clear
|
|
111
|
+
|
|
112
|
+
# Semantic search across all content
|
|
113
|
+
ragtime search "how does auth work" --limit 10
|
|
114
|
+
|
|
115
|
+
# Search only code
|
|
116
|
+
ragtime search "useAsyncState" --type code
|
|
117
|
+
|
|
118
|
+
# Search only docs
|
|
119
|
+
ragtime search "authentication" --type docs --namespace app
|
|
104
120
|
|
|
105
121
|
# Reindex memory files
|
|
106
122
|
ragtime reindex
|
|
@@ -220,13 +236,42 @@ docs:
|
|
|
220
236
|
|
|
221
237
|
code:
|
|
222
238
|
paths: ["."]
|
|
223
|
-
languages: ["python", "typescript", "dart"]
|
|
239
|
+
languages: ["python", "typescript", "javascript", "vue", "dart"]
|
|
240
|
+
exclude: ["**/node_modules/**", "**/build/**", "**/dist/**"]
|
|
224
241
|
|
|
225
242
|
conventions:
|
|
226
243
|
files: [".ragtime/CONVENTIONS.md"]
|
|
227
244
|
also_search_memories: true
|
|
228
245
|
```
|
|
229
246
|
|
|
247
|
+
## Code Indexing
|
|
248
|
+
|
|
249
|
+
The code indexer extracts meaningful symbols from your codebase:
|
|
250
|
+
|
|
251
|
+
| Language | What Gets Indexed |
|
|
252
|
+
|----------|-------------------|
|
|
253
|
+
| Python | Classes, methods, functions (with docstrings) |
|
|
254
|
+
| TypeScript/JS | Exported functions, classes, interfaces, types, constants |
|
|
255
|
+
| Vue | Components, composable usage (useXxx calls) |
|
|
256
|
+
| Dart | Classes, functions, mixins, extensions |
|
|
257
|
+
|
|
258
|
+
Each symbol is indexed with:
|
|
259
|
+
- **content**: The code snippet with signature and docstring
|
|
260
|
+
- **file**: Full path to the source file
|
|
261
|
+
- **line**: Line number for quick navigation
|
|
262
|
+
- **symbol_name**: Searchable name (e.g., `useAsyncState`, `JWTManager.validate`)
|
|
263
|
+
- **symbol_type**: `function`, `class`, `method`, `interface`, `composable`, etc.
|
|
264
|
+
|
|
265
|
+
Example search results:
|
|
266
|
+
```
|
|
267
|
+
ragtime search "useAsyncState" --type code
|
|
268
|
+
|
|
269
|
+
[1] /apps/web/components/agency/payers.vue
|
|
270
|
+
Type: code | Symbol: payers:useAsyncState
|
|
271
|
+
Score: 0.892
|
|
272
|
+
Uses composable: useAsyncState...
|
|
273
|
+
```
|
|
274
|
+
|
|
230
275
|
## Memory Format
|
|
231
276
|
|
|
232
277
|
Memories are markdown files with YAML frontmatter:
|
|
@@ -5,7 +5,8 @@ Local-first memory and RAG system for Claude Code. Semantic search over code, do
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Memory Storage**: Store structured knowledge with namespaces, types, and metadata
|
|
8
|
-
- **Semantic Search**: Query memories and
|
|
8
|
+
- **Semantic Search**: Query memories, docs, and code with natural language
|
|
9
|
+
- **Code Indexing**: Index functions, classes, and composables from Python, TypeScript, Vue, and Dart
|
|
9
10
|
- **Cross-Branch Sync**: Share context with teammates before PRs merge
|
|
10
11
|
- **Convention Checking**: Verify code follows team standards before PRs
|
|
11
12
|
- **Doc Generation**: Generate documentation from code (stubs or AI-powered)
|
|
@@ -66,11 +67,26 @@ ragtime forget <memory-id>
|
|
|
66
67
|
### Search & Indexing
|
|
67
68
|
|
|
68
69
|
```bash
|
|
69
|
-
# Index docs
|
|
70
|
+
# Index everything (docs + code)
|
|
71
|
+
ragtime index
|
|
72
|
+
|
|
73
|
+
# Index only docs
|
|
70
74
|
ragtime index --type docs
|
|
71
75
|
|
|
72
|
-
#
|
|
73
|
-
ragtime
|
|
76
|
+
# Index only code (functions, classes, composables)
|
|
77
|
+
ragtime index --type code
|
|
78
|
+
|
|
79
|
+
# Re-index with clear (removes old entries)
|
|
80
|
+
ragtime index --clear
|
|
81
|
+
|
|
82
|
+
# Semantic search across all content
|
|
83
|
+
ragtime search "how does auth work" --limit 10
|
|
84
|
+
|
|
85
|
+
# Search only code
|
|
86
|
+
ragtime search "useAsyncState" --type code
|
|
87
|
+
|
|
88
|
+
# Search only docs
|
|
89
|
+
ragtime search "authentication" --type docs --namespace app
|
|
74
90
|
|
|
75
91
|
# Reindex memory files
|
|
76
92
|
ragtime reindex
|
|
@@ -190,13 +206,42 @@ docs:
|
|
|
190
206
|
|
|
191
207
|
code:
|
|
192
208
|
paths: ["."]
|
|
193
|
-
languages: ["python", "typescript", "dart"]
|
|
209
|
+
languages: ["python", "typescript", "javascript", "vue", "dart"]
|
|
210
|
+
exclude: ["**/node_modules/**", "**/build/**", "**/dist/**"]
|
|
194
211
|
|
|
195
212
|
conventions:
|
|
196
213
|
files: [".ragtime/CONVENTIONS.md"]
|
|
197
214
|
also_search_memories: true
|
|
198
215
|
```
|
|
199
216
|
|
|
217
|
+
## Code Indexing
|
|
218
|
+
|
|
219
|
+
The code indexer extracts meaningful symbols from your codebase:
|
|
220
|
+
|
|
221
|
+
| Language | What Gets Indexed |
|
|
222
|
+
|----------|-------------------|
|
|
223
|
+
| Python | Classes, methods, functions (with docstrings) |
|
|
224
|
+
| TypeScript/JS | Exported functions, classes, interfaces, types, constants |
|
|
225
|
+
| Vue | Components, composable usage (useXxx calls) |
|
|
226
|
+
| Dart | Classes, functions, mixins, extensions |
|
|
227
|
+
|
|
228
|
+
Each symbol is indexed with:
|
|
229
|
+
- **content**: The code snippet with signature and docstring
|
|
230
|
+
- **file**: Full path to the source file
|
|
231
|
+
- **line**: Line number for quick navigation
|
|
232
|
+
- **symbol_name**: Searchable name (e.g., `useAsyncState`, `JWTManager.validate`)
|
|
233
|
+
- **symbol_type**: `function`, `class`, `method`, `interface`, `composable`, etc.
|
|
234
|
+
|
|
235
|
+
Example search results:
|
|
236
|
+
```
|
|
237
|
+
ragtime search "useAsyncState" --type code
|
|
238
|
+
|
|
239
|
+
[1] /apps/web/components/agency/payers.vue
|
|
240
|
+
Type: code | Symbol: payers:useAsyncState
|
|
241
|
+
Score: 0.892
|
|
242
|
+
Uses composable: useAsyncState...
|
|
243
|
+
```
|
|
244
|
+
|
|
200
245
|
## Memory Format
|
|
201
246
|
|
|
202
247
|
Memories are markdown files with YAML frontmatter:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ragtime-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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
|
|
@@ -35,7 +35,8 @@ Local-first memory and RAG system for Claude Code. Semantic search over code, do
|
|
|
35
35
|
## Features
|
|
36
36
|
|
|
37
37
|
- **Memory Storage**: Store structured knowledge with namespaces, types, and metadata
|
|
38
|
-
- **Semantic Search**: Query memories and
|
|
38
|
+
- **Semantic Search**: Query memories, docs, and code with natural language
|
|
39
|
+
- **Code Indexing**: Index functions, classes, and composables from Python, TypeScript, Vue, and Dart
|
|
39
40
|
- **Cross-Branch Sync**: Share context with teammates before PRs merge
|
|
40
41
|
- **Convention Checking**: Verify code follows team standards before PRs
|
|
41
42
|
- **Doc Generation**: Generate documentation from code (stubs or AI-powered)
|
|
@@ -96,11 +97,26 @@ ragtime forget <memory-id>
|
|
|
96
97
|
### Search & Indexing
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
|
-
# Index docs
|
|
100
|
+
# Index everything (docs + code)
|
|
101
|
+
ragtime index
|
|
102
|
+
|
|
103
|
+
# Index only docs
|
|
100
104
|
ragtime index --type docs
|
|
101
105
|
|
|
102
|
-
#
|
|
103
|
-
ragtime
|
|
106
|
+
# Index only code (functions, classes, composables)
|
|
107
|
+
ragtime index --type code
|
|
108
|
+
|
|
109
|
+
# Re-index with clear (removes old entries)
|
|
110
|
+
ragtime index --clear
|
|
111
|
+
|
|
112
|
+
# Semantic search across all content
|
|
113
|
+
ragtime search "how does auth work" --limit 10
|
|
114
|
+
|
|
115
|
+
# Search only code
|
|
116
|
+
ragtime search "useAsyncState" --type code
|
|
117
|
+
|
|
118
|
+
# Search only docs
|
|
119
|
+
ragtime search "authentication" --type docs --namespace app
|
|
104
120
|
|
|
105
121
|
# Reindex memory files
|
|
106
122
|
ragtime reindex
|
|
@@ -220,13 +236,42 @@ docs:
|
|
|
220
236
|
|
|
221
237
|
code:
|
|
222
238
|
paths: ["."]
|
|
223
|
-
languages: ["python", "typescript", "dart"]
|
|
239
|
+
languages: ["python", "typescript", "javascript", "vue", "dart"]
|
|
240
|
+
exclude: ["**/node_modules/**", "**/build/**", "**/dist/**"]
|
|
224
241
|
|
|
225
242
|
conventions:
|
|
226
243
|
files: [".ragtime/CONVENTIONS.md"]
|
|
227
244
|
also_search_memories: true
|
|
228
245
|
```
|
|
229
246
|
|
|
247
|
+
## Code Indexing
|
|
248
|
+
|
|
249
|
+
The code indexer extracts meaningful symbols from your codebase:
|
|
250
|
+
|
|
251
|
+
| Language | What Gets Indexed |
|
|
252
|
+
|----------|-------------------|
|
|
253
|
+
| Python | Classes, methods, functions (with docstrings) |
|
|
254
|
+
| TypeScript/JS | Exported functions, classes, interfaces, types, constants |
|
|
255
|
+
| Vue | Components, composable usage (useXxx calls) |
|
|
256
|
+
| Dart | Classes, functions, mixins, extensions |
|
|
257
|
+
|
|
258
|
+
Each symbol is indexed with:
|
|
259
|
+
- **content**: The code snippet with signature and docstring
|
|
260
|
+
- **file**: Full path to the source file
|
|
261
|
+
- **line**: Line number for quick navigation
|
|
262
|
+
- **symbol_name**: Searchable name (e.g., `useAsyncState`, `JWTManager.validate`)
|
|
263
|
+
- **symbol_type**: `function`, `class`, `method`, `interface`, `composable`, etc.
|
|
264
|
+
|
|
265
|
+
Example search results:
|
|
266
|
+
```
|
|
267
|
+
ragtime search "useAsyncState" --type code
|
|
268
|
+
|
|
269
|
+
[1] /apps/web/components/agency/payers.vue
|
|
270
|
+
Type: code | Symbol: payers:useAsyncState
|
|
271
|
+
Score: 0.892
|
|
272
|
+
Uses composable: useAsyncState...
|
|
273
|
+
```
|
|
274
|
+
|
|
230
275
|
## Memory Format
|
|
231
276
|
|
|
232
277
|
Memories are markdown files with YAML frontmatter:
|
|
@@ -11,7 +11,10 @@ import sys
|
|
|
11
11
|
|
|
12
12
|
from .db import RagtimeDB
|
|
13
13
|
from .config import RagtimeConfig, init_config
|
|
14
|
-
from .indexers
|
|
14
|
+
from .indexers import (
|
|
15
|
+
discover_docs, index_doc_file, DocEntry,
|
|
16
|
+
discover_code_files, index_code_file, CodeEntry,
|
|
17
|
+
)
|
|
15
18
|
from .memory import Memory, MemoryStore
|
|
16
19
|
|
|
17
20
|
|
|
@@ -166,7 +169,7 @@ def get_remote_branches_with_ragtime(path: Path) -> list[str]:
|
|
|
166
169
|
|
|
167
170
|
|
|
168
171
|
@click.group()
|
|
169
|
-
@click.version_option(version="0.2.
|
|
172
|
+
@click.version_option(version="0.2.6")
|
|
170
173
|
def main():
|
|
171
174
|
"""Ragtime - semantic search over code and documentation."""
|
|
172
175
|
pass
|
|
@@ -273,34 +276,98 @@ def index(path: Path, index_type: str, clear: bool):
|
|
|
273
276
|
db.clear(type_filter=index_type)
|
|
274
277
|
|
|
275
278
|
if index_type in ("all", "docs"):
|
|
276
|
-
|
|
279
|
+
# Discover all doc files first
|
|
280
|
+
all_doc_files = []
|
|
277
281
|
for docs_path in config.docs.paths:
|
|
278
282
|
docs_root = path / docs_path
|
|
279
283
|
if not docs_root.exists():
|
|
280
284
|
click.echo(f" Docs path {docs_root} not found, skipping...")
|
|
281
285
|
continue
|
|
282
|
-
|
|
283
|
-
entries = index_docs(
|
|
286
|
+
files = discover_docs(
|
|
284
287
|
docs_root,
|
|
285
288
|
patterns=config.docs.patterns,
|
|
286
289
|
exclude=config.docs.exclude,
|
|
287
290
|
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
all_doc_files.extend(files)
|
|
292
|
+
|
|
293
|
+
if all_doc_files:
|
|
294
|
+
click.echo(f"Found {len(all_doc_files)} doc files")
|
|
295
|
+
total_entries = []
|
|
296
|
+
with click.progressbar(
|
|
297
|
+
all_doc_files,
|
|
298
|
+
label=" Processing",
|
|
299
|
+
show_percent=True,
|
|
300
|
+
show_pos=True,
|
|
301
|
+
item_show_func=lambda f: f.name[:30] if f else "",
|
|
302
|
+
) as files:
|
|
303
|
+
for file_path in files:
|
|
304
|
+
entry = index_doc_file(file_path)
|
|
305
|
+
if entry:
|
|
306
|
+
total_entries.append(entry)
|
|
307
|
+
|
|
308
|
+
if total_entries:
|
|
309
|
+
ids = [e.file_path for e in total_entries]
|
|
310
|
+
documents = [e.content for e in total_entries]
|
|
311
|
+
metadatas = [e.to_metadata() for e in total_entries]
|
|
312
|
+
db.upsert(ids=ids, documents=documents, metadatas=metadatas)
|
|
313
|
+
click.echo(f" Indexed {len(total_entries)} documents")
|
|
314
|
+
else:
|
|
315
|
+
click.echo(" No valid documents found")
|
|
296
316
|
else:
|
|
297
317
|
click.echo(" No documents found")
|
|
298
318
|
|
|
299
319
|
if index_type in ("all", "code"):
|
|
320
|
+
# Build exclusion list for code
|
|
300
321
|
code_exclude = list(config.code.exclude)
|
|
301
322
|
for docs_path in config.docs.paths:
|
|
302
323
|
code_exclude.append(f"**/{docs_path}/**")
|
|
303
|
-
|
|
324
|
+
|
|
325
|
+
# Discover all code files first
|
|
326
|
+
all_code_files = []
|
|
327
|
+
for code_path_str in config.code.paths:
|
|
328
|
+
code_root = path / code_path_str
|
|
329
|
+
if not code_root.exists():
|
|
330
|
+
click.echo(f" Code path {code_root} not found, skipping...")
|
|
331
|
+
continue
|
|
332
|
+
files = discover_code_files(
|
|
333
|
+
code_root,
|
|
334
|
+
languages=config.code.languages,
|
|
335
|
+
exclude=code_exclude,
|
|
336
|
+
)
|
|
337
|
+
all_code_files.extend(files)
|
|
338
|
+
|
|
339
|
+
if all_code_files:
|
|
340
|
+
click.echo(f"Found {len(all_code_files)} code files")
|
|
341
|
+
total_entries = []
|
|
342
|
+
with click.progressbar(
|
|
343
|
+
all_code_files,
|
|
344
|
+
label=" Processing",
|
|
345
|
+
show_percent=True,
|
|
346
|
+
show_pos=True,
|
|
347
|
+
item_show_func=lambda f: f.name[:30] if f else "",
|
|
348
|
+
) as files:
|
|
349
|
+
for file_path in files:
|
|
350
|
+
file_entries = index_code_file(file_path)
|
|
351
|
+
total_entries.extend(file_entries)
|
|
352
|
+
|
|
353
|
+
if total_entries:
|
|
354
|
+
# Create unique IDs: file:line:symbol
|
|
355
|
+
ids = [f"{e.file_path}:{e.line_number}:{e.symbol_name}" for e in total_entries]
|
|
356
|
+
documents = [e.content for e in total_entries]
|
|
357
|
+
metadatas = [e.to_metadata() for e in total_entries]
|
|
358
|
+
db.upsert(ids=ids, documents=documents, metadatas=metadatas)
|
|
359
|
+
click.echo(f" Indexed {len(total_entries)} code symbols")
|
|
360
|
+
|
|
361
|
+
# Show breakdown by type
|
|
362
|
+
by_type = {}
|
|
363
|
+
for e in total_entries:
|
|
364
|
+
by_type[e.symbol_type] = by_type.get(e.symbol_type, 0) + 1
|
|
365
|
+
breakdown = ", ".join(f"{count} {typ}s" for typ, count in sorted(by_type.items()))
|
|
366
|
+
click.echo(f" ({breakdown})")
|
|
367
|
+
else:
|
|
368
|
+
click.echo(" No code symbols found")
|
|
369
|
+
else:
|
|
370
|
+
click.echo(" No code files found")
|
|
304
371
|
|
|
305
372
|
stats = db.stats()
|
|
306
373
|
click.echo(f"\nIndex stats: {stats['total']} total ({stats['docs']} docs, {stats['code']} code)")
|
|
@@ -1041,7 +1108,15 @@ def daemon_start(path: Path, interval: str):
|
|
|
1041
1108
|
|
|
1042
1109
|
Runs git fetch && ragtime sync on an interval to keep
|
|
1043
1110
|
remote branches synced automatically.
|
|
1111
|
+
|
|
1112
|
+
Note: This command requires Unix (Linux/macOS). On Windows, use Task Scheduler instead.
|
|
1044
1113
|
"""
|
|
1114
|
+
# Check for Windows - os.fork() is Unix-only
|
|
1115
|
+
if sys.platform == "win32":
|
|
1116
|
+
click.echo("✗ Daemon mode is not supported on Windows.", err=True)
|
|
1117
|
+
click.echo(" Use Windows Task Scheduler to run 'ragtime sync' periodically instead.")
|
|
1118
|
+
return
|
|
1119
|
+
|
|
1045
1120
|
path = Path(path).resolve()
|
|
1046
1121
|
pid_file = get_pid_file(path)
|
|
1047
1122
|
log_file = get_log_file(path)
|
|
@@ -1088,6 +1163,7 @@ def daemon_start(path: Path, interval: str):
|
|
|
1088
1163
|
pid_file.write_text(str(os.getpid()))
|
|
1089
1164
|
|
|
1090
1165
|
# Redirect output to log file
|
|
1166
|
+
# Note: log_fd is intentionally kept open for the lifetime of the daemon
|
|
1091
1167
|
log_fd = open(log_file, "a")
|
|
1092
1168
|
os.dup2(log_fd.fileno(), sys.stdout.fileno())
|
|
1093
1169
|
os.dup2(log_fd.fileno(), sys.stderr.fileno())
|
|
@@ -1652,7 +1728,7 @@ def get_function_params(node) -> list:
|
|
|
1652
1728
|
if arg.annotation:
|
|
1653
1729
|
try:
|
|
1654
1730
|
type_hint = ast.unparse(arg.annotation)
|
|
1655
|
-
except:
|
|
1731
|
+
except Exception:
|
|
1656
1732
|
type_hint = "Any"
|
|
1657
1733
|
|
|
1658
1734
|
default = "-"
|
|
@@ -1660,7 +1736,7 @@ def get_function_params(node) -> list:
|
|
|
1660
1736
|
if default_idx >= 0 and default_idx < len(defaults):
|
|
1661
1737
|
try:
|
|
1662
1738
|
default = f"`{ast.unparse(defaults[default_idx])}`"
|
|
1663
|
-
except:
|
|
1739
|
+
except Exception:
|
|
1664
1740
|
default = "..."
|
|
1665
1741
|
|
|
1666
1742
|
params.append({
|
|
@@ -1679,7 +1755,7 @@ def get_return_annotation(node) -> str:
|
|
|
1679
1755
|
if node.returns:
|
|
1680
1756
|
try:
|
|
1681
1757
|
return ast.unparse(node.returns)
|
|
1682
|
-
except:
|
|
1758
|
+
except Exception:
|
|
1683
1759
|
return "Any"
|
|
1684
1760
|
return "None"
|
|
1685
1761
|
|
|
@@ -1803,7 +1879,7 @@ def audit(docs_path: Path, path: Path, fix: bool, as_json: bool):
|
|
|
1803
1879
|
parts = content.split("---", 2)
|
|
1804
1880
|
if len(parts) >= 3:
|
|
1805
1881
|
existing_meta = yaml.safe_load(parts[1]) or {}
|
|
1806
|
-
except:
|
|
1882
|
+
except Exception:
|
|
1807
1883
|
pass
|
|
1808
1884
|
|
|
1809
1885
|
# Analyze file for suggestions
|
|
@@ -1950,7 +2026,7 @@ def update(check: bool):
|
|
|
1950
2026
|
from urllib.request import urlopen
|
|
1951
2027
|
from urllib.error import URLError
|
|
1952
2028
|
|
|
1953
|
-
current = "0.2.
|
|
2029
|
+
current = "0.2.6"
|
|
1954
2030
|
|
|
1955
2031
|
click.echo(f"Current version: {current}")
|
|
1956
2032
|
click.echo("Checking PyPI for updates...")
|
|
@@ -26,7 +26,7 @@ class DocsConfig:
|
|
|
26
26
|
class CodeConfig:
|
|
27
27
|
"""Configuration for code indexing."""
|
|
28
28
|
paths: list[str] = field(default_factory=lambda: ["."])
|
|
29
|
-
languages: list[str] = field(default_factory=lambda: ["
|
|
29
|
+
languages: list[str] = field(default_factory=lambda: ["python", "typescript", "javascript", "vue", "dart"])
|
|
30
30
|
exclude: list[str] = field(default_factory=lambda: [
|
|
31
31
|
"**/node_modules/**",
|
|
32
32
|
"**/.git/**",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Indexers for ragtime - parse different content types for vector search."""
|
|
2
|
+
|
|
3
|
+
from .docs import index_directory as index_docs, DocEntry, discover_docs, index_file as index_doc_file
|
|
4
|
+
from .code import index_directory as index_code, CodeEntry, discover_code_files, index_file as index_code_file
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"index_docs", "index_code",
|
|
8
|
+
"DocEntry", "CodeEntry",
|
|
9
|
+
"discover_docs", "discover_code_files",
|
|
10
|
+
"index_doc_file", "index_code_file",
|
|
11
|
+
]
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code indexer - extracts functions, classes, and types from source files.
|
|
3
|
+
|
|
4
|
+
Parses code to create searchable chunks for each meaningful unit (function, class, etc).
|
|
5
|
+
This allows searching for specific code constructs like "useAsyncState" or "JWTManager".
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import re
|
|
10
|
+
from fnmatch import fnmatch
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Language file extensions
|
|
16
|
+
LANGUAGE_EXTENSIONS = {
|
|
17
|
+
"python": [".py"],
|
|
18
|
+
"typescript": [".ts", ".tsx"],
|
|
19
|
+
"javascript": [".js", ".jsx"],
|
|
20
|
+
"vue": [".vue"],
|
|
21
|
+
"dart": [".dart"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class CodeEntry:
|
|
27
|
+
"""A parsed code symbol ready for indexing."""
|
|
28
|
+
content: str # The actual code + context
|
|
29
|
+
file_path: str # Full path to file
|
|
30
|
+
language: str # python, typescript, etc.
|
|
31
|
+
symbol_name: str # Function/class/component name
|
|
32
|
+
symbol_type: str # function, class, interface, component, etc.
|
|
33
|
+
line_number: int # Line where symbol starts
|
|
34
|
+
docstring: str | None = None # Extracted docstring/JSDoc
|
|
35
|
+
|
|
36
|
+
def to_metadata(self) -> dict:
|
|
37
|
+
"""Convert to ChromaDB metadata dict."""
|
|
38
|
+
return {
|
|
39
|
+
"type": "code",
|
|
40
|
+
"file": self.file_path,
|
|
41
|
+
"language": self.language,
|
|
42
|
+
"symbol_name": self.symbol_name,
|
|
43
|
+
"symbol_type": self.symbol_type,
|
|
44
|
+
"line": self.line_number,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_extensions_for_languages(languages: list[str]) -> list[str]:
|
|
49
|
+
"""Get file extensions for the specified languages."""
|
|
50
|
+
extensions = []
|
|
51
|
+
for lang in languages:
|
|
52
|
+
extensions.extend(LANGUAGE_EXTENSIONS.get(lang, []))
|
|
53
|
+
return extensions
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def discover_code_files(
|
|
57
|
+
root: Path,
|
|
58
|
+
languages: list[str],
|
|
59
|
+
exclude: list[str] | None = None,
|
|
60
|
+
) -> list[Path]:
|
|
61
|
+
"""
|
|
62
|
+
Find all code files to index.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
root: Directory to search
|
|
66
|
+
languages: List of languages to include
|
|
67
|
+
exclude: Patterns to exclude
|
|
68
|
+
"""
|
|
69
|
+
exclude = exclude or [
|
|
70
|
+
"**/node_modules/**",
|
|
71
|
+
"**/.git/**",
|
|
72
|
+
"**/build/**",
|
|
73
|
+
"**/dist/**",
|
|
74
|
+
"**/__pycache__/**",
|
|
75
|
+
"**/.venv/**",
|
|
76
|
+
"**/venv/**",
|
|
77
|
+
"**/.dart_tool/**",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
extensions = get_extensions_for_languages(languages)
|
|
81
|
+
files = []
|
|
82
|
+
|
|
83
|
+
for ext in extensions:
|
|
84
|
+
for path in root.rglob(f"*{ext}"):
|
|
85
|
+
if path.is_file():
|
|
86
|
+
# Check exclusions using proper glob matching
|
|
87
|
+
skip = False
|
|
88
|
+
# Use relative path for matching to avoid absolute path issues
|
|
89
|
+
try:
|
|
90
|
+
rel_path = str(path.relative_to(root))
|
|
91
|
+
except ValueError:
|
|
92
|
+
rel_path = str(path)
|
|
93
|
+
|
|
94
|
+
for ex in exclude:
|
|
95
|
+
# Handle ** patterns by checking if pattern appears in path
|
|
96
|
+
if "**" in ex:
|
|
97
|
+
# Convert glob to a simpler check: **/node_modules/** means
|
|
98
|
+
# any path containing /node_modules/ segment
|
|
99
|
+
core_pattern = ex.replace("**", "").strip("/")
|
|
100
|
+
if core_pattern and f"/{core_pattern}/" in f"/{rel_path}/":
|
|
101
|
+
skip = True
|
|
102
|
+
break
|
|
103
|
+
elif fnmatch(rel_path, ex) or fnmatch(path.name, ex):
|
|
104
|
+
skip = True
|
|
105
|
+
break
|
|
106
|
+
if not skip:
|
|
107
|
+
files.append(path)
|
|
108
|
+
|
|
109
|
+
return files
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def index_python_file(file_path: Path, content: str) -> list[CodeEntry]:
|
|
113
|
+
"""Extract code entries from a Python file."""
|
|
114
|
+
entries = []
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
tree = ast.parse(content)
|
|
118
|
+
except SyntaxError:
|
|
119
|
+
return entries
|
|
120
|
+
|
|
121
|
+
for node in ast.walk(tree):
|
|
122
|
+
if isinstance(node, ast.ClassDef):
|
|
123
|
+
# Get class code (signature + docstring + method signatures)
|
|
124
|
+
start_line = node.lineno
|
|
125
|
+
docstring = ast.get_docstring(node) or ""
|
|
126
|
+
|
|
127
|
+
# Build a summary of the class
|
|
128
|
+
method_names = []
|
|
129
|
+
for item in node.body:
|
|
130
|
+
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
131
|
+
method_names.append(item.name)
|
|
132
|
+
|
|
133
|
+
class_summary = f"class {node.name}:\n"
|
|
134
|
+
if docstring:
|
|
135
|
+
class_summary += f' """{docstring}"""\n'
|
|
136
|
+
if method_names:
|
|
137
|
+
class_summary += f"\n # Methods: {', '.join(method_names)}\n"
|
|
138
|
+
|
|
139
|
+
entries.append(CodeEntry(
|
|
140
|
+
content=class_summary,
|
|
141
|
+
file_path=str(file_path),
|
|
142
|
+
language="python",
|
|
143
|
+
symbol_name=node.name,
|
|
144
|
+
symbol_type="class",
|
|
145
|
+
line_number=start_line,
|
|
146
|
+
docstring=docstring,
|
|
147
|
+
))
|
|
148
|
+
|
|
149
|
+
# Also index public methods
|
|
150
|
+
for item in node.body:
|
|
151
|
+
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
152
|
+
if item.name.startswith("_") and item.name != "__init__":
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
method_doc = ast.get_docstring(item) or ""
|
|
156
|
+
async_prefix = "async " if isinstance(item, ast.AsyncFunctionDef) else ""
|
|
157
|
+
|
|
158
|
+
# Get signature
|
|
159
|
+
args = []
|
|
160
|
+
for arg in item.args.args:
|
|
161
|
+
if arg.arg == "self":
|
|
162
|
+
continue
|
|
163
|
+
type_hint = ""
|
|
164
|
+
if arg.annotation:
|
|
165
|
+
try:
|
|
166
|
+
type_hint = f": {ast.unparse(arg.annotation)}"
|
|
167
|
+
except Exception:
|
|
168
|
+
pass
|
|
169
|
+
args.append(f"{arg.arg}{type_hint}")
|
|
170
|
+
|
|
171
|
+
ret_type = ""
|
|
172
|
+
if item.returns:
|
|
173
|
+
try:
|
|
174
|
+
ret_type = f" -> {ast.unparse(item.returns)}"
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
method_sig = f"{async_prefix}def {item.name}({', '.join(args)}){ret_type}"
|
|
179
|
+
method_content = f"class {node.name}:\n {method_sig}:\n"
|
|
180
|
+
if method_doc:
|
|
181
|
+
method_content += f' """{method_doc}"""\n'
|
|
182
|
+
|
|
183
|
+
entries.append(CodeEntry(
|
|
184
|
+
content=method_content,
|
|
185
|
+
file_path=str(file_path),
|
|
186
|
+
language="python",
|
|
187
|
+
symbol_name=f"{node.name}.{item.name}",
|
|
188
|
+
symbol_type="method",
|
|
189
|
+
line_number=item.lineno,
|
|
190
|
+
docstring=method_doc,
|
|
191
|
+
))
|
|
192
|
+
|
|
193
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
194
|
+
# Top-level function
|
|
195
|
+
if hasattr(node, 'col_offset') and node.col_offset > 0:
|
|
196
|
+
continue # Skip nested functions
|
|
197
|
+
|
|
198
|
+
docstring = ast.get_docstring(node) or ""
|
|
199
|
+
async_prefix = "async " if isinstance(node, ast.AsyncFunctionDef) else ""
|
|
200
|
+
|
|
201
|
+
# Get signature
|
|
202
|
+
args = []
|
|
203
|
+
for arg in node.args.args:
|
|
204
|
+
type_hint = ""
|
|
205
|
+
if arg.annotation:
|
|
206
|
+
try:
|
|
207
|
+
type_hint = f": {ast.unparse(arg.annotation)}"
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
args.append(f"{arg.arg}{type_hint}")
|
|
211
|
+
|
|
212
|
+
ret_type = ""
|
|
213
|
+
if node.returns:
|
|
214
|
+
try:
|
|
215
|
+
ret_type = f" -> {ast.unparse(node.returns)}"
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
func_sig = f"{async_prefix}def {node.name}({', '.join(args)}){ret_type}"
|
|
220
|
+
func_content = f"{func_sig}:\n"
|
|
221
|
+
if docstring:
|
|
222
|
+
func_content += f' """{docstring}"""\n'
|
|
223
|
+
|
|
224
|
+
entries.append(CodeEntry(
|
|
225
|
+
content=func_content,
|
|
226
|
+
file_path=str(file_path),
|
|
227
|
+
language="python",
|
|
228
|
+
symbol_name=node.name,
|
|
229
|
+
symbol_type="function",
|
|
230
|
+
line_number=node.lineno,
|
|
231
|
+
docstring=docstring,
|
|
232
|
+
))
|
|
233
|
+
|
|
234
|
+
return entries
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def index_typescript_file(file_path: Path, content: str) -> list[CodeEntry]:
|
|
238
|
+
"""Extract code entries from a TypeScript/JavaScript file."""
|
|
239
|
+
entries = []
|
|
240
|
+
lines = content.split("\n")
|
|
241
|
+
|
|
242
|
+
# Patterns for different constructs
|
|
243
|
+
patterns = [
|
|
244
|
+
# Exported functions
|
|
245
|
+
(r'export\s+(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*(?:<[^>]+>)?\s*\(([^)]*)\)(?:\s*:\s*([^\{]+))?',
|
|
246
|
+
"function"),
|
|
247
|
+
# Arrow function exports
|
|
248
|
+
(r'export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>',
|
|
249
|
+
"function"),
|
|
250
|
+
# Class exports
|
|
251
|
+
(r'export\s+(?:default\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?',
|
|
252
|
+
"class"),
|
|
253
|
+
# Interface exports
|
|
254
|
+
(r'export\s+(?:default\s+)?interface\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+([^{]+))?',
|
|
255
|
+
"interface"),
|
|
256
|
+
# Type exports
|
|
257
|
+
(r'export\s+type\s+(\w+)(?:<[^>]+>)?\s*=',
|
|
258
|
+
"type"),
|
|
259
|
+
# Const exports (useful for config objects, composables, etc.)
|
|
260
|
+
(r'export\s+const\s+(\w+)\s*(?::\s*([^=]+))?\s*=\s*(?!.*=>)',
|
|
261
|
+
"constant"),
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
for i, line in enumerate(lines):
|
|
265
|
+
for pattern, symbol_type in patterns:
|
|
266
|
+
match = re.match(pattern, line.strip())
|
|
267
|
+
if match:
|
|
268
|
+
symbol_name = match.group(1)
|
|
269
|
+
|
|
270
|
+
# Get context (a few lines around the definition)
|
|
271
|
+
start = max(0, i - 1)
|
|
272
|
+
end = min(len(lines), i + 10)
|
|
273
|
+
context_lines = lines[start:end]
|
|
274
|
+
|
|
275
|
+
# Extract JSDoc if present
|
|
276
|
+
jsdoc = ""
|
|
277
|
+
if i > 0 and lines[i - 1].strip().endswith("*/"):
|
|
278
|
+
# Look backward for JSDoc start
|
|
279
|
+
for j in range(i - 1, max(0, i - 20), -1):
|
|
280
|
+
if "/**" in lines[j]:
|
|
281
|
+
jsdoc_lines = lines[j:i]
|
|
282
|
+
jsdoc = "\n".join(jsdoc_lines)
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
entries.append(CodeEntry(
|
|
286
|
+
content="\n".join(context_lines),
|
|
287
|
+
file_path=str(file_path),
|
|
288
|
+
language="typescript" if file_path.suffix in [".ts", ".tsx"] else "javascript",
|
|
289
|
+
symbol_name=symbol_name,
|
|
290
|
+
symbol_type=symbol_type,
|
|
291
|
+
line_number=i + 1,
|
|
292
|
+
docstring=jsdoc if jsdoc else None,
|
|
293
|
+
))
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
# Also look for Vue composables pattern (useXxx functions)
|
|
297
|
+
composable_pattern = r'(?:export\s+)?(?:const|function)\s+(use[A-Z]\w*)'
|
|
298
|
+
for i, line in enumerate(lines):
|
|
299
|
+
match = re.search(composable_pattern, line)
|
|
300
|
+
if match:
|
|
301
|
+
symbol_name = match.group(1)
|
|
302
|
+
# Check if we already indexed this
|
|
303
|
+
if not any(e.symbol_name == symbol_name for e in entries):
|
|
304
|
+
start = max(0, i - 1)
|
|
305
|
+
end = min(len(lines), i + 15)
|
|
306
|
+
|
|
307
|
+
entries.append(CodeEntry(
|
|
308
|
+
content="\n".join(lines[start:end]),
|
|
309
|
+
file_path=str(file_path),
|
|
310
|
+
language="typescript" if file_path.suffix in [".ts", ".tsx"] else "javascript",
|
|
311
|
+
symbol_name=symbol_name,
|
|
312
|
+
symbol_type="composable",
|
|
313
|
+
line_number=i + 1,
|
|
314
|
+
))
|
|
315
|
+
|
|
316
|
+
return entries
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def index_vue_file(file_path: Path, content: str) -> list[CodeEntry]:
|
|
320
|
+
"""Extract code entries from a Vue SFC file."""
|
|
321
|
+
entries = []
|
|
322
|
+
|
|
323
|
+
# Get component name from filename
|
|
324
|
+
component_name = file_path.stem
|
|
325
|
+
|
|
326
|
+
# Extract script section
|
|
327
|
+
script_match = re.search(
|
|
328
|
+
r'<script[^>]*(?:setup)?[^>]*>(.*?)</script>',
|
|
329
|
+
content,
|
|
330
|
+
re.DOTALL | re.IGNORECASE
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
script_content = script_match.group(1) if script_match else ""
|
|
334
|
+
|
|
335
|
+
# Add the component itself
|
|
336
|
+
entries.append(CodeEntry(
|
|
337
|
+
content=f"Vue Component: {component_name}\n\n{script_content[:500]}",
|
|
338
|
+
file_path=str(file_path),
|
|
339
|
+
language="vue",
|
|
340
|
+
symbol_name=component_name,
|
|
341
|
+
symbol_type="component",
|
|
342
|
+
line_number=1,
|
|
343
|
+
))
|
|
344
|
+
|
|
345
|
+
# If there's script content, parse it for composables and functions
|
|
346
|
+
if script_content:
|
|
347
|
+
# Look for composable usage (useXxx calls)
|
|
348
|
+
composable_usages = re.findall(r'(use[A-Z]\w*)\s*\(', script_content)
|
|
349
|
+
for composable in set(composable_usages):
|
|
350
|
+
# Find the line
|
|
351
|
+
for i, line in enumerate(content.split("\n")):
|
|
352
|
+
if composable in line:
|
|
353
|
+
entries.append(CodeEntry(
|
|
354
|
+
content=f"Uses composable: {composable}\n{line.strip()}",
|
|
355
|
+
file_path=str(file_path),
|
|
356
|
+
language="vue",
|
|
357
|
+
symbol_name=f"{component_name}:{composable}",
|
|
358
|
+
symbol_type="composable_usage",
|
|
359
|
+
line_number=i + 1,
|
|
360
|
+
))
|
|
361
|
+
break
|
|
362
|
+
|
|
363
|
+
# Parse script for functions
|
|
364
|
+
ts_entries = index_typescript_file(file_path, script_content)
|
|
365
|
+
for entry in ts_entries:
|
|
366
|
+
entry.language = "vue"
|
|
367
|
+
entry.symbol_name = f"{component_name}.{entry.symbol_name}"
|
|
368
|
+
entries.append(entry)
|
|
369
|
+
|
|
370
|
+
return entries
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def index_dart_file(file_path: Path, content: str) -> list[CodeEntry]:
|
|
374
|
+
"""Extract code entries from a Dart file."""
|
|
375
|
+
entries = []
|
|
376
|
+
lines = content.split("\n")
|
|
377
|
+
|
|
378
|
+
# Patterns for Dart constructs
|
|
379
|
+
patterns = [
|
|
380
|
+
# Class definitions
|
|
381
|
+
(r'(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+(\w+))?(?:\s+with\s+([^{]+))?(?:\s+implements\s+([^{]+))?',
|
|
382
|
+
"class"),
|
|
383
|
+
# Function definitions
|
|
384
|
+
(r'(?:Future<[^>]+>|void|int|String|bool|double|dynamic|\w+)\s+(\w+)\s*(?:<[^>]+>)?\s*\(',
|
|
385
|
+
"function"),
|
|
386
|
+
# Mixins
|
|
387
|
+
(r'mixin\s+(\w+)(?:\s+on\s+(\w+))?',
|
|
388
|
+
"mixin"),
|
|
389
|
+
# Extensions
|
|
390
|
+
(r'extension\s+(\w+)\s+on\s+(\w+)',
|
|
391
|
+
"extension"),
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
for i, line in enumerate(lines):
|
|
395
|
+
for pattern, symbol_type in patterns:
|
|
396
|
+
match = re.match(r'\s*' + pattern, line)
|
|
397
|
+
if match:
|
|
398
|
+
symbol_name = match.group(1)
|
|
399
|
+
|
|
400
|
+
# Get context
|
|
401
|
+
start = max(0, i - 1)
|
|
402
|
+
end = min(len(lines), i + 10)
|
|
403
|
+
|
|
404
|
+
# Extract doc comment if present
|
|
405
|
+
doc_comment = ""
|
|
406
|
+
if i > 0:
|
|
407
|
+
for j in range(i - 1, max(0, i - 20), -1):
|
|
408
|
+
if lines[j].strip().startswith("///"):
|
|
409
|
+
doc_comment = lines[j].strip() + "\n" + doc_comment
|
|
410
|
+
elif lines[j].strip():
|
|
411
|
+
break
|
|
412
|
+
|
|
413
|
+
entries.append(CodeEntry(
|
|
414
|
+
content="\n".join(lines[start:end]),
|
|
415
|
+
file_path=str(file_path),
|
|
416
|
+
language="dart",
|
|
417
|
+
symbol_name=symbol_name,
|
|
418
|
+
symbol_type=symbol_type,
|
|
419
|
+
line_number=i + 1,
|
|
420
|
+
docstring=doc_comment if doc_comment else None,
|
|
421
|
+
))
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
return entries
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def index_file(file_path: Path) -> list[CodeEntry]:
|
|
428
|
+
"""
|
|
429
|
+
Parse a single code file into CodeEntry objects.
|
|
430
|
+
|
|
431
|
+
Returns empty list if file can't be parsed.
|
|
432
|
+
"""
|
|
433
|
+
try:
|
|
434
|
+
content = file_path.read_text(encoding='utf-8')
|
|
435
|
+
except (IOError, UnicodeDecodeError):
|
|
436
|
+
return []
|
|
437
|
+
|
|
438
|
+
# Skip empty files
|
|
439
|
+
if not content.strip():
|
|
440
|
+
return []
|
|
441
|
+
|
|
442
|
+
suffix = file_path.suffix.lower()
|
|
443
|
+
|
|
444
|
+
if suffix == ".py":
|
|
445
|
+
return index_python_file(file_path, content)
|
|
446
|
+
elif suffix in [".ts", ".tsx", ".js", ".jsx"]:
|
|
447
|
+
return index_typescript_file(file_path, content)
|
|
448
|
+
elif suffix == ".vue":
|
|
449
|
+
return index_vue_file(file_path, content)
|
|
450
|
+
elif suffix == ".dart":
|
|
451
|
+
return index_dart_file(file_path, content)
|
|
452
|
+
|
|
453
|
+
return []
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def index_directory(
|
|
457
|
+
root: Path,
|
|
458
|
+
languages: list[str],
|
|
459
|
+
exclude: list[str] | None = None,
|
|
460
|
+
) -> list[CodeEntry]:
|
|
461
|
+
"""
|
|
462
|
+
Index all code files in a directory.
|
|
463
|
+
|
|
464
|
+
Returns list of CodeEntry objects ready for vector DB.
|
|
465
|
+
"""
|
|
466
|
+
files = discover_code_files(root, languages, exclude)
|
|
467
|
+
entries = []
|
|
468
|
+
|
|
469
|
+
for file_path in files:
|
|
470
|
+
file_entries = index_file(file_path)
|
|
471
|
+
entries.extend(file_entries)
|
|
472
|
+
|
|
473
|
+
return entries
|
|
@@ -191,7 +191,12 @@ class MemoryStore:
|
|
|
191
191
|
return None
|
|
192
192
|
|
|
193
193
|
metadata = results["metadatas"][0]
|
|
194
|
-
|
|
194
|
+
file_rel_path = metadata.get("file", "")
|
|
195
|
+
|
|
196
|
+
if not file_rel_path:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
file_path = self.memory_dir / file_rel_path
|
|
195
200
|
|
|
196
201
|
if file_path.exists():
|
|
197
202
|
return Memory.from_file(file_path)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|