ragtime-cli 0.1.0__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.1.0.dist-info/METADATA +220 -0
- ragtime_cli-0.1.0.dist-info/RECORD +21 -0
- ragtime_cli-0.1.0.dist-info/WHEEL +5 -0
- ragtime_cli-0.1.0.dist-info/entry_points.txt +3 -0
- ragtime_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- ragtime_cli-0.1.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/cli.py +773 -0
- src/commands/audit.md +151 -0
- src/commands/handoff.md +176 -0
- src/commands/pr-graduate.md +187 -0
- src/commands/recall.md +175 -0
- src/commands/remember.md +168 -0
- src/commands/save.md +10 -0
- src/commands/start.md +206 -0
- src/config.py +101 -0
- src/db.py +167 -0
- src/indexers/__init__.py +0 -0
- src/indexers/docs.py +129 -0
- src/mcp_server.py +590 -0
- src/memory.py +379 -0
src/commands/remember.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Capture ad-hoc knowledge to local memory mid-session
|
|
3
|
+
allowed-arguments: the thing to remember (e.g., /remember auth uses 15-min JWT expiry)
|
|
4
|
+
allowed-tools: Bash, AskUserQuestion, mcp__ragtime__remember
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Remember
|
|
8
|
+
|
|
9
|
+
Capture ad-hoc knowledge to the local memory system during a session.
|
|
10
|
+
|
|
11
|
+
**Usage:**
|
|
12
|
+
- `/remember auth uses 15-minute JWT expiry` - Quick capture with content
|
|
13
|
+
- `/remember` - Interactive mode, asks what to remember
|
|
14
|
+
|
|
15
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
CUSTOMIZABLE: Modify prompts, add project-specific types, adjust the
|
|
17
|
+
approval flow, etc. Everything above the RAGTIME CORE section can be
|
|
18
|
+
tailored to your project's needs.
|
|
19
|
+
═══════════════════════════════════════════════════════════════════════════ -->
|
|
20
|
+
|
|
21
|
+
## When to Use
|
|
22
|
+
|
|
23
|
+
Use `/remember` when you discover something worth preserving:
|
|
24
|
+
- Architecture patterns ("the validation pipeline works like X")
|
|
25
|
+
- Gotchas ("don't use Y because Z")
|
|
26
|
+
- Decisions made during discussion
|
|
27
|
+
- Team preferences discovered
|
|
28
|
+
- Anything you'd want future sessions to know
|
|
29
|
+
|
|
30
|
+
**Don't use for:**
|
|
31
|
+
- Temporary debug notes (those belong in handoff.md)
|
|
32
|
+
- Personal todos (use GitHub issues)
|
|
33
|
+
- Things that will be captured by `/pr-graduate` anyway
|
|
34
|
+
|
|
35
|
+
## Step 1: Capture the Knowledge
|
|
36
|
+
|
|
37
|
+
**If `$ARGUMENTS` provided:**
|
|
38
|
+
- Use it as the memory content
|
|
39
|
+
|
|
40
|
+
**If no arguments:**
|
|
41
|
+
- Ask: "What would you like me to remember?"
|
|
42
|
+
|
|
43
|
+
## Step 2: Determine Memory Type
|
|
44
|
+
|
|
45
|
+
Ask the user:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
What type of knowledge is this?
|
|
49
|
+
|
|
50
|
+
1. 🏗️ **Architecture** - How something works in the codebase
|
|
51
|
+
2. 📋 **Convention** - Team rule or standard practice
|
|
52
|
+
3. ⚠️ **Gotcha** - Warning or "don't do this"
|
|
53
|
+
4. 🎯 **Decision** - Why we chose X over Y
|
|
54
|
+
5. 💡 **Pattern** - Reusable approach for common problem
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
<!-- CUSTOMIZABLE: Add your own types here if needed -->
|
|
58
|
+
|
|
59
|
+
## Step 3: Determine Scope
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Where does this knowledge apply?
|
|
63
|
+
|
|
64
|
+
1. 🌐 **Global** - Applies everywhere
|
|
65
|
+
2. 📦 **Component** - Specific area (auth, claims, shifts, etc.)
|
|
66
|
+
3. 📄 **File** - Specific file or folder
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If Component or File, ask which one.
|
|
70
|
+
|
|
71
|
+
<!-- CUSTOMIZABLE: Add your own components here -->
|
|
72
|
+
|
|
73
|
+
## Step 4: Determine Confidence
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
How confident are you in this knowledge?
|
|
77
|
+
|
|
78
|
+
1. ✅ **High** - Verified, tested, or from authoritative source
|
|
79
|
+
2. ⚡ **Medium** - Observed it working, but not extensively tested
|
|
80
|
+
3. 🤔 **Low** - Best guess or inference, should be validated
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Step 5: Determine Namespace
|
|
84
|
+
|
|
85
|
+
Based on the memory type, suggest the appropriate namespace:
|
|
86
|
+
|
|
87
|
+
| Type | Default Namespace |
|
|
88
|
+
|------|------------------|
|
|
89
|
+
| Architecture | `app` |
|
|
90
|
+
| Convention | `team` |
|
|
91
|
+
| Gotcha | `app` |
|
|
92
|
+
| Decision | `app` (or `branch-{branch}` if WIP) |
|
|
93
|
+
| Pattern | `app` |
|
|
94
|
+
|
|
95
|
+
Ask: "Store in `{suggested namespace}`? Or somewhere else?"
|
|
96
|
+
|
|
97
|
+
<!-- CUSTOMIZABLE: Adjust routing rules for your project -->
|
|
98
|
+
|
|
99
|
+
## Step 6: Confirm and Store
|
|
100
|
+
|
|
101
|
+
Present the memory for confirmation:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
## Memory to Store
|
|
105
|
+
|
|
106
|
+
**Content:** {the knowledge}
|
|
107
|
+
**Type:** {type}
|
|
108
|
+
**Namespace:** {namespace}
|
|
109
|
+
**Confidence:** {confidence}
|
|
110
|
+
**Scope:** {scope}
|
|
111
|
+
**Component:** {if applicable}
|
|
112
|
+
|
|
113
|
+
Store this? (yes/edit/cancel)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════
|
|
117
|
+
RAGTIME CORE - DO NOT MODIFY
|
|
118
|
+
These commands must match ragtime's expected format.
|
|
119
|
+
Changing these may break storage/retrieval.
|
|
120
|
+
═══════════════════════════════════════════════════════════════════════════ -->
|
|
121
|
+
|
|
122
|
+
Once confirmed, store using ragtime MCP:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
mcp__ragtime__remember:
|
|
126
|
+
content: "{the knowledge}"
|
|
127
|
+
namespace: "{namespace}"
|
|
128
|
+
type: "{architecture|convention|decision|pattern}"
|
|
129
|
+
component: "{component if applicable}"
|
|
130
|
+
confidence: "{high|medium|low}"
|
|
131
|
+
confidence_reason: "manual"
|
|
132
|
+
source: "remember"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
|
|
136
|
+
|
|
137
|
+
## Step 7: Confirm Storage
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
✅ Remembered!
|
|
141
|
+
|
|
142
|
+
"{abbreviated content}..."
|
|
143
|
+
|
|
144
|
+
Stored to: {namespace}
|
|
145
|
+
Confidence: {confidence}
|
|
146
|
+
Query with: ragtime search "{topic}" --namespace {namespace}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Quick Mode
|
|
150
|
+
|
|
151
|
+
For rapid capture, you can specify everything inline:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
/remember [high] auth uses 15-min JWT expiry #auth #architecture
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Parsing:
|
|
158
|
+
- `[high]` → confidence
|
|
159
|
+
- `#auth` → component
|
|
160
|
+
- `#architecture` → type
|
|
161
|
+
- Rest → content
|
|
162
|
+
|
|
163
|
+
## Notes
|
|
164
|
+
|
|
165
|
+
- Memories stored via `/remember` get `source: "remember"` and default to medium confidence unless specified
|
|
166
|
+
- If on a feature branch, consider whether this is branch-specific or general knowledge
|
|
167
|
+
- For decisions that might be reversed, use low confidence
|
|
168
|
+
- Team conventions should be discussed before storing to `team` namespace
|
src/commands/save.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Save current progress (alias for /handoff)
|
|
3
|
+
allowed-tools: Bash, Read, Write, mcp__ragtime__remember
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Save
|
|
7
|
+
|
|
8
|
+
Alias for `/handoff`. See that command for full documentation.
|
|
9
|
+
|
|
10
|
+
Run `/handoff` to save current context to branch memory.
|
src/commands/start.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Start or resume work on a GitHub issue
|
|
3
|
+
allowed-arguments: issue number (e.g., /start 230)
|
|
4
|
+
allowed-tools: Bash, Read, Write, mcp__ragtime__search, mcp__ragtime__remember, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Start or Resume Work
|
|
8
|
+
|
|
9
|
+
Smart command that starts fresh OR resumes existing work depending on context.
|
|
10
|
+
|
|
11
|
+
**Usage:**
|
|
12
|
+
- `/start 230` - Start or resume work on issue #230
|
|
13
|
+
- `/start` - Show your assigned issues and prompt for selection
|
|
14
|
+
|
|
15
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
CUSTOMIZABLE: Adjust branch naming conventions, issue lookup, workflow
|
|
17
|
+
steps, etc.
|
|
18
|
+
═══════════════════════════════════════════════════════════════════════════ -->
|
|
19
|
+
|
|
20
|
+
## Step 1: Determine the Issue
|
|
21
|
+
|
|
22
|
+
**If an issue number was provided as an argument**, use it directly.
|
|
23
|
+
|
|
24
|
+
**If no argument was provided**, check current state and prompt:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
BRANCH=$(git branch --show-current)
|
|
28
|
+
ISSUE_NUM=$(echo "$BRANCH" | grep -oE '[0-9]+' | head -1)
|
|
29
|
+
DEVNAME=$(gh api user --jq '.login' 2>/dev/null || git config user.name | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
|
|
30
|
+
|
|
31
|
+
echo "Current branch: $BRANCH"
|
|
32
|
+
echo "Developer: $DEVNAME"
|
|
33
|
+
|
|
34
|
+
if [ -n "$ISSUE_NUM" ]; then
|
|
35
|
+
echo "Already on issue branch: #$ISSUE_NUM"
|
|
36
|
+
else
|
|
37
|
+
echo ""
|
|
38
|
+
echo "=== Your Assigned Issues ==="
|
|
39
|
+
gh issue list --assignee @me --state open
|
|
40
|
+
fi
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If not on an issue branch and no argument provided, ask: **"Which issue would you like to start?"**
|
|
44
|
+
|
|
45
|
+
## Step 2: Switch to the Branch
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ISSUE_NUM={issue_number}
|
|
49
|
+
|
|
50
|
+
# Check if branch exists
|
|
51
|
+
EXISTING_BRANCH=$(git branch -a | grep -E "/$ISSUE_NUM-|/$ISSUE_NUM$" | head -1 | tr -d ' *')
|
|
52
|
+
|
|
53
|
+
if [ -n "$EXISTING_BRANCH" ]; then
|
|
54
|
+
echo "Found existing branch: $EXISTING_BRANCH"
|
|
55
|
+
git checkout "$EXISTING_BRANCH"
|
|
56
|
+
else
|
|
57
|
+
# Create new branch
|
|
58
|
+
ISSUE_TITLE=$(gh issue view $ISSUE_NUM --json title --jq '.title' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-' | head -c 40)
|
|
59
|
+
NEW_BRANCH="$DEVNAME/$ISSUE_NUM-$ISSUE_TITLE"
|
|
60
|
+
echo "Creating new branch: $NEW_BRANCH"
|
|
61
|
+
git checkout -b "$NEW_BRANCH" main
|
|
62
|
+
fi
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
<!-- CUSTOMIZABLE: Adjust branch naming convention for your project -->
|
|
66
|
+
|
|
67
|
+
## Step 3: Check for Existing Context
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
BRANCH=$(git branch --show-current)
|
|
71
|
+
BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-')
|
|
72
|
+
CONTEXT_FILE=".claude/memory/branches/$BRANCH_SLUG/context.md"
|
|
73
|
+
|
|
74
|
+
if [ -f "$CONTEXT_FILE" ]; then
|
|
75
|
+
echo "✓ Found branch context"
|
|
76
|
+
cat "$CONTEXT_FILE"
|
|
77
|
+
else
|
|
78
|
+
echo "No existing context found"
|
|
79
|
+
fi
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Decision Tree:
|
|
83
|
+
|
|
84
|
+
**If context.md exists:**
|
|
85
|
+
→ **RESUME MODE** - Go to Step 4a
|
|
86
|
+
|
|
87
|
+
**If NO context.md:**
|
|
88
|
+
→ **FRESH START MODE** - Go to Step 4b
|
|
89
|
+
|
|
90
|
+
## Step 4a: Resume Mode
|
|
91
|
+
|
|
92
|
+
Load and present the existing context:
|
|
93
|
+
|
|
94
|
+
1. Read the branch's `context.md`
|
|
95
|
+
2. Display the current state and what's left
|
|
96
|
+
3. Ask: "Ready to continue? What would you like to work on first?"
|
|
97
|
+
|
|
98
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════
|
|
99
|
+
RAGTIME CORE - DO NOT MODIFY
|
|
100
|
+
═══════════════════════════════════════════════════════════════════════════ -->
|
|
101
|
+
|
|
102
|
+
Also load any branch memories:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
mcp__ragtime__search:
|
|
106
|
+
query: "decisions patterns architecture"
|
|
107
|
+
namespace: "branch-{branch}"
|
|
108
|
+
limit: 10
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
|
|
112
|
+
|
|
113
|
+
Present:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
## Resuming Issue #{issue_num}
|
|
117
|
+
|
|
118
|
+
### Current State:
|
|
119
|
+
{from context.md}
|
|
120
|
+
|
|
121
|
+
### What's Left:
|
|
122
|
+
{from context.md}
|
|
123
|
+
|
|
124
|
+
### Branch Memories:
|
|
125
|
+
{list of decisions/patterns stored}
|
|
126
|
+
|
|
127
|
+
Ready to continue? What would you like to work on first?
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Step 4b: Fresh Start Mode
|
|
131
|
+
|
|
132
|
+
Create a new implementation plan:
|
|
133
|
+
|
|
134
|
+
1. Fetch issue details:
|
|
135
|
+
```bash
|
|
136
|
+
gh issue view "$ISSUE_NUM" --json title,body,labels,assignees
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
2. Parse the GitHub issue (title, body, labels)
|
|
140
|
+
|
|
141
|
+
3. Identify which parts of the app this touches
|
|
142
|
+
|
|
143
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════
|
|
144
|
+
RAGTIME CORE - DO NOT MODIFY
|
|
145
|
+
═══════════════════════════════════════════════════════════════════════════ -->
|
|
146
|
+
|
|
147
|
+
4. Search for relevant context:
|
|
148
|
+
|
|
149
|
+
**App knowledge (architecture for those areas):**
|
|
150
|
+
```
|
|
151
|
+
mcp__ragtime__search:
|
|
152
|
+
query: "{component or feature area}"
|
|
153
|
+
namespace: "app"
|
|
154
|
+
limit: 10
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Team conventions:**
|
|
158
|
+
```
|
|
159
|
+
mcp__ragtime__search:
|
|
160
|
+
query: "conventions standards"
|
|
161
|
+
namespace: "team"
|
|
162
|
+
limit: 10
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
|
|
166
|
+
|
|
167
|
+
5. Create an implementation plan based on:
|
|
168
|
+
- Issue requirements
|
|
169
|
+
- Codebase knowledge from memories
|
|
170
|
+
- Team conventions
|
|
171
|
+
|
|
172
|
+
6. Present the plan and ask if the user wants to proceed or adjust
|
|
173
|
+
|
|
174
|
+
## Step 5: Save Initial Context (Fresh Start only)
|
|
175
|
+
|
|
176
|
+
**After user approves the plan**, save it to context.md:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
BRANCH=$(git branch --show-current)
|
|
180
|
+
BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-')
|
|
181
|
+
mkdir -p ".claude/memory/branches/$BRANCH_SLUG"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Write the context.md with the plan (see `/handoff` for format).
|
|
185
|
+
|
|
186
|
+
## Step 6: Ready to Work
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Ready to work on Issue #{issue_num}!
|
|
190
|
+
|
|
191
|
+
What would you like to tackle first?
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Syncing Teammate Context
|
|
195
|
+
|
|
196
|
+
If you need context from a teammate's branch:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Fetch their branch
|
|
200
|
+
git fetch origin jm/feature-auth
|
|
201
|
+
|
|
202
|
+
# Sync their memories
|
|
203
|
+
ragtime sync origin/jm/feature-auth
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This creates `branches/jm-feature-auth(unmerged)/` with their context and memories, searchable but gitignored.
|
src/config.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for ragtime.
|
|
3
|
+
|
|
4
|
+
Config lives in .ragtime/config.yaml in the project root.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class DocsConfig:
|
|
14
|
+
"""Configuration for docs indexing."""
|
|
15
|
+
paths: list[str] = field(default_factory=lambda: ["docs", ".claude/memory"])
|
|
16
|
+
patterns: list[str] = field(default_factory=lambda: ["**/*.md"])
|
|
17
|
+
exclude: list[str] = field(default_factory=lambda: [
|
|
18
|
+
"**/node_modules/**",
|
|
19
|
+
"**/.git/**",
|
|
20
|
+
"**/.ragtime/**",
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class CodeConfig:
|
|
26
|
+
"""Configuration for code indexing."""
|
|
27
|
+
paths: list[str] = field(default_factory=lambda: ["."])
|
|
28
|
+
languages: list[str] = field(default_factory=lambda: ["dart", "typescript", "python"])
|
|
29
|
+
exclude: list[str] = field(default_factory=lambda: [
|
|
30
|
+
"**/node_modules/**",
|
|
31
|
+
"**/.git/**",
|
|
32
|
+
"**/build/**",
|
|
33
|
+
"**/dist/**",
|
|
34
|
+
"**/.dart_tool/**",
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class RagtimeConfig:
|
|
40
|
+
"""Main ragtime configuration."""
|
|
41
|
+
docs: DocsConfig = field(default_factory=DocsConfig)
|
|
42
|
+
code: CodeConfig = field(default_factory=CodeConfig)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def load(cls, project_path: Path) -> "RagtimeConfig":
|
|
46
|
+
"""Load config from .ragtime/config.yaml or return defaults."""
|
|
47
|
+
config_path = project_path / ".ragtime" / "config.yaml"
|
|
48
|
+
|
|
49
|
+
if not config_path.exists():
|
|
50
|
+
return cls()
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(config_path) as f:
|
|
54
|
+
data = yaml.safe_load(f) or {}
|
|
55
|
+
except (IOError, yaml.YAMLError):
|
|
56
|
+
return cls()
|
|
57
|
+
|
|
58
|
+
docs_data = data.get("docs", {})
|
|
59
|
+
code_data = data.get("code", {})
|
|
60
|
+
|
|
61
|
+
return cls(
|
|
62
|
+
docs=DocsConfig(
|
|
63
|
+
paths=docs_data.get("paths", DocsConfig().paths),
|
|
64
|
+
patterns=docs_data.get("patterns", DocsConfig().patterns),
|
|
65
|
+
exclude=docs_data.get("exclude", DocsConfig().exclude),
|
|
66
|
+
),
|
|
67
|
+
code=CodeConfig(
|
|
68
|
+
paths=code_data.get("paths", CodeConfig().paths),
|
|
69
|
+
languages=code_data.get("languages", CodeConfig().languages),
|
|
70
|
+
exclude=code_data.get("exclude", CodeConfig().exclude),
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def save(self, project_path: Path) -> None:
|
|
75
|
+
"""Save config to .ragtime/config.yaml."""
|
|
76
|
+
config_dir = project_path / ".ragtime"
|
|
77
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
config_path = config_dir / "config.yaml"
|
|
79
|
+
|
|
80
|
+
data = {
|
|
81
|
+
"docs": {
|
|
82
|
+
"paths": self.docs.paths,
|
|
83
|
+
"patterns": self.docs.patterns,
|
|
84
|
+
"exclude": self.docs.exclude,
|
|
85
|
+
},
|
|
86
|
+
"code": {
|
|
87
|
+
"paths": self.code.paths,
|
|
88
|
+
"languages": self.code.languages,
|
|
89
|
+
"exclude": self.code.exclude,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
with open(config_path, "w") as f:
|
|
94
|
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def init_config(project_path: Path) -> RagtimeConfig:
|
|
98
|
+
"""Initialize a new config file with defaults."""
|
|
99
|
+
config = RagtimeConfig()
|
|
100
|
+
config.save(project_path)
|
|
101
|
+
return config
|
src/db.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChromaDB wrapper for ragtime.
|
|
3
|
+
|
|
4
|
+
Handles storage and retrieval of indexed documents and code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
import chromadb
|
|
10
|
+
from chromadb.config import Settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RagtimeDB:
|
|
14
|
+
"""Vector database for ragtime indexes."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, path: Path):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the database.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
path: Directory to store ChromaDB data
|
|
22
|
+
"""
|
|
23
|
+
self.path = path
|
|
24
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
|
|
26
|
+
self.client = chromadb.PersistentClient(
|
|
27
|
+
path=str(path),
|
|
28
|
+
settings=Settings(anonymized_telemetry=False),
|
|
29
|
+
)
|
|
30
|
+
self.collection = self.client.get_or_create_collection(
|
|
31
|
+
name="ragtime",
|
|
32
|
+
metadata={"hnsw:space": "cosine"},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def add(
|
|
36
|
+
self,
|
|
37
|
+
ids: list[str],
|
|
38
|
+
documents: list[str],
|
|
39
|
+
metadatas: list[dict[str, Any]],
|
|
40
|
+
) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Add documents to the index.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
ids: Unique identifiers for each document
|
|
46
|
+
documents: Text content to embed and index
|
|
47
|
+
metadatas: Metadata dicts for filtering
|
|
48
|
+
"""
|
|
49
|
+
self.collection.add(
|
|
50
|
+
ids=ids,
|
|
51
|
+
documents=documents,
|
|
52
|
+
metadatas=metadatas,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def update(
|
|
56
|
+
self,
|
|
57
|
+
ids: list[str],
|
|
58
|
+
documents: list[str],
|
|
59
|
+
metadatas: list[dict[str, Any]],
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Update existing documents."""
|
|
62
|
+
self.collection.update(
|
|
63
|
+
ids=ids,
|
|
64
|
+
documents=documents,
|
|
65
|
+
metadatas=metadatas,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def upsert(
|
|
69
|
+
self,
|
|
70
|
+
ids: list[str],
|
|
71
|
+
documents: list[str],
|
|
72
|
+
metadatas: list[dict[str, Any]],
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Insert or update documents."""
|
|
75
|
+
self.collection.upsert(
|
|
76
|
+
ids=ids,
|
|
77
|
+
documents=documents,
|
|
78
|
+
metadatas=metadatas,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def search(
|
|
82
|
+
self,
|
|
83
|
+
query: str,
|
|
84
|
+
limit: int = 10,
|
|
85
|
+
type_filter: str | None = None,
|
|
86
|
+
namespace: str | None = None,
|
|
87
|
+
**filters,
|
|
88
|
+
) -> list[dict]:
|
|
89
|
+
"""
|
|
90
|
+
Semantic search over indexed content.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
query: Natural language search query
|
|
94
|
+
limit: Max results to return
|
|
95
|
+
type_filter: "code" or "docs" (None = both)
|
|
96
|
+
namespace: Filter by namespace (for docs)
|
|
97
|
+
**filters: Additional metadata filters
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List of dicts with 'content', 'metadata', 'distance'
|
|
101
|
+
"""
|
|
102
|
+
where = {}
|
|
103
|
+
|
|
104
|
+
if type_filter:
|
|
105
|
+
where["type"] = type_filter
|
|
106
|
+
|
|
107
|
+
if namespace:
|
|
108
|
+
where["namespace"] = namespace
|
|
109
|
+
|
|
110
|
+
for key, value in filters.items():
|
|
111
|
+
where[key] = value
|
|
112
|
+
|
|
113
|
+
results = self.collection.query(
|
|
114
|
+
query_texts=[query],
|
|
115
|
+
n_results=limit,
|
|
116
|
+
where=where if where else None,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Flatten results into list of dicts
|
|
120
|
+
output = []
|
|
121
|
+
if results["documents"] and results["documents"][0]:
|
|
122
|
+
for i, doc in enumerate(results["documents"][0]):
|
|
123
|
+
output.append({
|
|
124
|
+
"content": doc,
|
|
125
|
+
"metadata": results["metadatas"][0][i] if results["metadatas"] else {},
|
|
126
|
+
"distance": results["distances"][0][i] if results["distances"] else None,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return output
|
|
130
|
+
|
|
131
|
+
def delete(self, ids: list[str]) -> None:
|
|
132
|
+
"""Delete documents by ID."""
|
|
133
|
+
self.collection.delete(ids=ids)
|
|
134
|
+
|
|
135
|
+
def clear(self, type_filter: str | None = None) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Clear the index.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
type_filter: Only clear "code" or "docs" (None = everything)
|
|
141
|
+
"""
|
|
142
|
+
if type_filter:
|
|
143
|
+
# Get all IDs matching the type
|
|
144
|
+
results = self.collection.get(where={"type": type_filter})
|
|
145
|
+
if results["ids"]:
|
|
146
|
+
self.collection.delete(ids=results["ids"])
|
|
147
|
+
else:
|
|
148
|
+
# Nuclear option - recreate collection
|
|
149
|
+
self.client.delete_collection("ragtime")
|
|
150
|
+
self.collection = self.client.get_or_create_collection(
|
|
151
|
+
name="ragtime",
|
|
152
|
+
metadata={"hnsw:space": "cosine"},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def stats(self) -> dict:
|
|
156
|
+
"""Get index statistics."""
|
|
157
|
+
count = self.collection.count()
|
|
158
|
+
|
|
159
|
+
# Count by type
|
|
160
|
+
docs_count = len(self.collection.get(where={"type": "docs"})["ids"])
|
|
161
|
+
code_count = len(self.collection.get(where={"type": "code"})["ids"])
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"total": count,
|
|
165
|
+
"docs": docs_count,
|
|
166
|
+
"code": code_count,
|
|
167
|
+
}
|
src/indexers/__init__.py
ADDED
|
File without changes
|