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.

@@ -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
+ }
File without changes