ragtime-cli 0.2.18__py3-none-any.whl → 0.3.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.

src/config.py CHANGED
@@ -51,9 +51,15 @@ class CodeConfig:
51
51
 
52
52
  @dataclass
53
53
  class ConventionsConfig:
54
- """Configuration for convention checking."""
54
+ """Configuration for convention checking and storage."""
55
+ # Reading conventions
55
56
  files: list[str] = field(default_factory=lambda: [".ragtime/CONVENTIONS.md"])
56
57
  also_search_memories: bool = True
58
+ # Writing conventions
59
+ storage: str = "auto" # "auto" | "file" | "memory" | "ask"
60
+ default_file: str = ".ragtime/CONVENTIONS.md"
61
+ folder: str = ".ragtime/conventions/"
62
+ scan_docs_for_sections: list[str] = field(default_factory=lambda: ["docs/"])
57
63
 
58
64
 
59
65
  @dataclass
@@ -97,6 +103,12 @@ class RagtimeConfig:
97
103
  also_search_memories=conventions_data.get(
98
104
  "also_search_memories", ConventionsConfig().also_search_memories
99
105
  ),
106
+ storage=conventions_data.get("storage", ConventionsConfig().storage),
107
+ default_file=conventions_data.get("default_file", ConventionsConfig().default_file),
108
+ folder=conventions_data.get("folder", ConventionsConfig().folder),
109
+ scan_docs_for_sections=conventions_data.get(
110
+ "scan_docs_for_sections", ConventionsConfig().scan_docs_for_sections
111
+ ),
100
112
  ),
101
113
  )
102
114
 
@@ -120,6 +132,10 @@ class RagtimeConfig:
120
132
  "conventions": {
121
133
  "files": self.conventions.files,
122
134
  "also_search_memories": self.conventions.also_search_memories,
135
+ "storage": self.conventions.storage,
136
+ "default_file": self.conventions.default_file,
137
+ "folder": self.conventions.folder,
138
+ "scan_docs_for_sections": self.conventions.scan_docs_for_sections,
123
139
  },
124
140
  }
125
141
 
src/indexers/docs.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Docs indexer - parses markdown files with YAML frontmatter.
3
3
 
4
- Designed for .claude/memory/ style files but works with any markdown.
4
+ Designed for .ragtime/ style files but works with any markdown.
5
5
  """
6
6
 
7
7
  import os
@@ -70,12 +70,23 @@ class Section:
70
70
  content: str
71
71
  line_start: int
72
72
  parent_path: list[str] # Parent headers for context
73
+ is_convention: bool = False # True if this section contains conventions
74
+
75
+
76
+ # Headers that indicate convention content
77
+ CONVENTION_HEADERS = {
78
+ "conventions", "convention", "rules", "standards", "guidelines",
79
+ "code conventions", "coding conventions", "code standards",
80
+ "coding standards", "style guide", "code style",
81
+ }
73
82
 
74
83
 
75
84
  def chunk_by_headers(
76
85
  content: str,
77
86
  min_chunk_size: int = 100,
78
87
  max_chunk_size: int = 2000,
88
+ convention_sections: list[str] | None = None,
89
+ all_conventions: bool = False,
79
90
  ) -> list[Section]:
80
91
  """
81
92
  Split markdown into sections by headers, preserving hierarchy.
@@ -84,38 +95,69 @@ def chunk_by_headers(
84
95
  content: Markdown body (without frontmatter)
85
96
  min_chunk_size: Minimum chars to make a standalone section
86
97
  max_chunk_size: Maximum chars before splitting further
98
+ convention_sections: List of section titles to mark as conventions
99
+ all_conventions: If True, mark ALL sections as conventions
87
100
 
88
101
  Returns:
89
102
  List of Section objects with hierarchical context
90
103
  """
91
104
  lines = content.split('\n')
92
105
  sections: list[Section] = []
93
- header_stack: list[tuple[int, str]] = [] # (level, title) for building paths
106
+ header_stack: list[tuple[int, str, bool]] = [] # (level, title, is_convention)
94
107
 
95
108
  current_section_lines: list[str] = []
96
109
  current_section_start = 0
97
110
  current_title = ""
98
111
  current_level = 0
112
+ current_is_convention = False
113
+ in_convention_marker = False # Track <!-- convention --> blocks
114
+
115
+ def is_convention_header(title: str) -> bool:
116
+ """Check if header indicates convention content."""
117
+ normalized = title.lower().strip()
118
+ return normalized in CONVENTION_HEADERS or any(
119
+ conv in normalized for conv in ["convention", "rule", "standard", "guideline"]
120
+ )
99
121
 
100
122
  def flush_section():
101
123
  """Save accumulated lines as a section."""
102
- nonlocal current_section_lines, current_section_start, current_title, current_level
124
+ nonlocal current_section_lines, current_section_start, current_title, current_level, current_is_convention
103
125
 
104
126
  text = '\n'.join(current_section_lines).strip()
105
127
  if text:
106
128
  # Build parent path from stack (excluding current)
107
129
  parent_path = [h[1] for h in header_stack[:-1]] if header_stack else []
108
130
 
131
+ # Check if any parent is a convention header
132
+ parent_is_convention = any(h[2] for h in header_stack[:-1]) if header_stack else False
133
+
134
+ # Determine if this section is a convention
135
+ is_conv = (
136
+ all_conventions or
137
+ current_is_convention or
138
+ parent_is_convention or
139
+ (convention_sections and current_title in convention_sections)
140
+ )
141
+
109
142
  sections.append(Section(
110
143
  title=current_title or "Introduction",
111
144
  level=current_level,
112
145
  content=text,
113
146
  line_start=current_section_start,
114
147
  parent_path=parent_path,
148
+ is_convention=is_conv,
115
149
  ))
116
150
  current_section_lines = []
117
151
 
118
152
  for i, line in enumerate(lines):
153
+ # Detect convention markers
154
+ if '<!-- convention -->' in line.lower() or '<!-- conventions -->' in line.lower():
155
+ in_convention_marker = True
156
+ continue
157
+ if '<!-- /convention -->' in line.lower() or '<!-- /conventions -->' in line.lower():
158
+ in_convention_marker = False
159
+ continue
160
+
119
161
  # Detect markdown headers
120
162
  header_match = re.match(r'^(#{1,6})\s+(.+)$', line)
121
163
 
@@ -126,16 +168,23 @@ def chunk_by_headers(
126
168
  level = len(header_match.group(1))
127
169
  title = header_match.group(2).strip()
128
170
 
171
+ # Check if this is a convention header
172
+ is_conv_header = is_convention_header(title)
173
+
129
174
  # Update header stack - pop headers at same or lower level
130
175
  while header_stack and header_stack[-1][0] >= level:
131
176
  header_stack.pop()
132
- header_stack.append((level, title))
177
+ header_stack.append((level, title, is_conv_header))
133
178
 
134
179
  current_title = title
135
180
  current_level = level
136
181
  current_section_start = i
182
+ current_is_convention = is_conv_header or in_convention_marker
137
183
  current_section_lines = [line] # Include header in content
138
184
  else:
185
+ # If inside a convention marker, mark the content
186
+ if in_convention_marker and not current_is_convention:
187
+ current_is_convention = True
139
188
  current_section_lines.append(line)
140
189
 
141
190
  # Don't forget the last section
@@ -145,8 +194,10 @@ def chunk_by_headers(
145
194
  processed: list[Section] = []
146
195
  for section in sections:
147
196
  if len(section.content) < min_chunk_size and processed:
148
- # Merge into previous section
197
+ # Merge into previous section (inherit is_convention if either has it)
149
198
  processed[-1].content += '\n\n' + section.content
199
+ if section.is_convention:
200
+ processed[-1].is_convention = True
150
201
  elif len(section.content) > max_chunk_size:
151
202
  # Split by paragraphs
152
203
  paragraphs = re.split(r'\n\n+', section.content)
@@ -161,6 +212,7 @@ def chunk_by_headers(
161
212
  content=current_chunk.strip(),
162
213
  line_start=section.line_start,
163
214
  parent_path=section.parent_path,
215
+ is_convention=section.is_convention,
164
216
  ))
165
217
  current_chunk = para
166
218
  chunk_num += 1
@@ -175,6 +227,7 @@ def chunk_by_headers(
175
227
  content=current_chunk.strip(),
176
228
  line_start=section.line_start,
177
229
  parent_path=section.parent_path,
230
+ is_convention=section.is_convention,
178
231
  ))
179
232
  else:
180
233
  processed.append(section)
@@ -213,13 +266,29 @@ def index_file(file_path: Path, hierarchical: bool = True) -> list[DocEntry]:
213
266
  base_component = metadata.get("component")
214
267
  base_title = metadata.get("title") or file_path.stem
215
268
 
269
+ # Convention detection from frontmatter
270
+ has_conventions = metadata.get("has_conventions", False)
271
+ convention_sections = metadata.get("convention_sections", [])
272
+
273
+ # Check if filename indicates conventions
274
+ filename_lower = file_path.stem.lower()
275
+ is_convention_file = any(
276
+ term in filename_lower
277
+ for term in ["convention", "conventions", "rules", "standards", "guidelines"]
278
+ )
279
+
216
280
  # Short docs: return as single entry
217
281
  if not hierarchical or len(body) < 500:
282
+ # Determine category - convention file or has_conventions flag
283
+ category = base_category
284
+ if is_convention_file or has_conventions:
285
+ category = "convention"
286
+
218
287
  return [DocEntry(
219
288
  content=body.strip(),
220
289
  file_path=str(file_path),
221
290
  namespace=base_namespace,
222
- category=base_category,
291
+ category=category,
223
292
  component=base_component,
224
293
  title=base_title,
225
294
  mtime=mtime,
@@ -229,7 +298,11 @@ def index_file(file_path: Path, hierarchical: bool = True) -> list[DocEntry]:
229
298
  )]
230
299
 
231
300
  # Hierarchical chunking for longer docs
232
- sections = chunk_by_headers(body)
301
+ sections = chunk_by_headers(
302
+ body,
303
+ convention_sections=convention_sections,
304
+ all_conventions=has_conventions or is_convention_file,
305
+ )
233
306
  entries = []
234
307
 
235
308
  for i, section in enumerate(sections):
@@ -242,11 +315,16 @@ def index_file(file_path: Path, hierarchical: bool = True) -> list[DocEntry]:
242
315
  if section.parent_path:
243
316
  context_prefix += f"Section: {' > '.join(section.parent_path)}\n\n"
244
317
 
318
+ # Determine category - convention sections get "convention" category
319
+ category = base_category
320
+ if section.is_convention:
321
+ category = "convention"
322
+
245
323
  entries.append(DocEntry(
246
324
  content=context_prefix + section.content,
247
325
  file_path=str(file_path),
248
326
  namespace=base_namespace,
249
- category=base_category,
327
+ category=category,
250
328
  component=base_component,
251
329
  title=section.title,
252
330
  mtime=mtime,
@@ -1,26 +0,0 @@
1
- ragtime_cli-0.2.18.dist-info/licenses/LICENSE,sha256=9A0wJs2PRDciGRH4F8JUJ-aMKYQyq_gVu2ixrXs-l5A,1070
2
- src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- src/cli.py,sha256=5UTi586H2S_2rplybhzFbfmfDl-E_YP51KEykVUSi2U,83282
4
- src/config.py,sha256=tQ6gPLr4ksn2bJPIUjtELFr-k01Eg4g-LDo3GNE6P0Q,4600
5
- src/db.py,sha256=eWqFGrg3O6hve67EzRJGcAsIpYxWJo4JlrAtlZUUA_s,15169
6
- src/feedback.py,sha256=cPw_lzusZZPvkgUxs_eV67NtV1FoCfTXUulBPnD78lo,6455
7
- src/mcp_server.py,sha256=ttXBtWPur6D4FWaJyJf80YaYlkF6Ak6tADNB4tD5N7E,27205
8
- src/memory.py,sha256=UiHyudKbseMMY-sdcaDSfVBMGj6sFXXw1GxBsZ7nuBc,18450
9
- src/commands/audit.md,sha256=Xkucm-gfBIMalK9wf7NBbyejpsqBTUAGGlb7GxMtMPY,5137
10
- src/commands/create-pr.md,sha256=u6-jVkDP_6bJQp6ImK039eY9F6B9E2KlAVlvLY-WV6Q,9483
11
- src/commands/generate-docs.md,sha256=9W2Yy-PDyC3p5k39uEb31z5YAHkSKsQLg6gV3tLgSnQ,7015
12
- src/commands/handoff.md,sha256=8VxTddtW08jGTW36GW_rS77JdeSn8vHeMfklrWwVUD4,5055
13
- src/commands/import-docs.md,sha256=ByIdcfbdiF77HoFv5U6zZ_YvZf00-hAs9EMconXssvY,6927
14
- src/commands/pr-graduate.md,sha256=nXJMuXeOp0SZfjQ567NUO02Rg9zPQHQFbZbJ4Q_His0,6692
15
- src/commands/recall.md,sha256=unQPWsmocKRoQR7jRtjrj8aVcMHverjGR6u5mYL8TLw,6008
16
- src/commands/remember.md,sha256=nNewsUhIqF4wtD1jhVDZvmLZjdcmPN6NmUM43SdWepc,5368
17
- src/commands/save.md,sha256=7gTpW46AU9Y4l8XVZ8f4h1sEdBfVqIRA7hlidUxMAC4,251
18
- src/commands/start.md,sha256=qoqhkMgET74DBx8YPIT1-wqCiVBUDxlmevigsCinHSY,6506
19
- src/indexers/__init__.py,sha256=MYoCPZUpHakMX1s2vWnc9shjWfx_X1_0JzUhpKhnKUQ,454
20
- src/indexers/code.py,sha256=G2TbiKbWj0e7DV5KsU8-Ggw6ziDb4zTuZ4Bu3ryV4g8,18059
21
- src/indexers/docs.py,sha256=Q8krHYw0bybUyZaq1sJ0r6Fv-I_6BjTufhqI1eg_25s,9992
22
- ragtime_cli-0.2.18.dist-info/METADATA,sha256=yzNcOmjrR3rL8K5cnwYa1Y6PZeHKpU4CJfTlcSx1A9Q,13354
23
- ragtime_cli-0.2.18.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
24
- ragtime_cli-0.2.18.dist-info/entry_points.txt,sha256=cWLbeyMxZNbew-THS3bHXTpCRXt1EaUy5QUOXGXLjl4,75
25
- ragtime_cli-0.2.18.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
26
- ragtime_cli-0.2.18.dist-info/RECORD,,
src/commands/audit.md DELETED
@@ -1,151 +0,0 @@
1
- ---
2
- description: Audit local memories for duplicates, conflicts, and stale data
3
- allowed-tools: Bash, mcp__ragtime__search, mcp__ragtime__list_memories, mcp__ragtime__forget, mcp__ragtime__update_status, AskUserQuestion
4
- ---
5
-
6
- # Memory Audit
7
-
8
- Periodic cleanup of local memories. This is a **human-in-the-loop** review - no automatic deletions.
9
-
10
- <!-- ═══════════════════════════════════════════════════════════════════════════
11
- CUSTOMIZABLE: Add project-specific audit rules, adjust the report format,
12
- add custom namespace checks, etc.
13
- ═══════════════════════════════════════════════════════════════════════════ -->
14
-
15
- ## Namespaces to Audit
16
-
17
- - `app` - Codebase knowledge (architecture, decisions)
18
- - `team` - Team conventions (standards, processes)
19
- - `user-*` - Developer preferences
20
- - `branch-*` - Branch-specific context and decisions
21
-
22
- ## Step 1: Gather All Memories
23
-
24
- <!-- ═══════════════════════════════════════════════════════════════════════════
25
- RAGTIME CORE - DO NOT MODIFY
26
- ═══════════════════════════════════════════════════════════════════════════ -->
27
-
28
- ```
29
- mcp__ragtime__list_memories:
30
- limit: 100
31
- ```
32
-
33
- For each namespace:
34
- ```
35
- mcp__ragtime__list_memories:
36
- namespace: "{namespace}"
37
- limit: 50
38
- ```
39
-
40
- <!-- ═══════════════════════════════════════════════════════════════════════════ -->
41
-
42
- ## Step 2: Identify Issues
43
-
44
- Group memories by topic and identify:
45
-
46
- | Issue | Description |
47
- |-------|-------------|
48
- | **DUPLICATES** | Memories saying essentially the same thing |
49
- | **CONFLICTS** | Memories that contradict each other |
50
- | **STALE** | References to code/features that no longer exist |
51
- | **ORPHANED** | Branch memories for deleted/merged branches |
52
- | **LOW_VALUE** | Vague memories that aren't useful |
53
-
54
- ## Step 3: Check for Stale Branches
55
-
56
- ```bash
57
- # List branch memory folders
58
- ls -la .claude/memory/branches/
59
-
60
- # For each, check if branch still exists
61
- for dir in .claude/memory/branches/*/; do
62
- branch_slug=$(basename "$dir")
63
- # Check if branch exists on remote
64
- if ! git branch -a | grep -q "$branch_slug"; then
65
- echo "⚠️ Potentially stale: $branch_slug"
66
- fi
67
- done
68
- ```
69
-
70
- Also run:
71
- ```bash
72
- ragtime prune --dry-run
73
- ```
74
-
75
- ## Step 4: Present Report
76
-
77
- ```
78
- ## Memory Audit Report
79
-
80
- ### App Namespace ({count} memories)
81
- - Potential duplicates: {n}
82
- - Potential conflicts: {n}
83
- - Possibly stale: {n}
84
-
85
- ### Team Namespace ({count} memories)
86
- - Potential duplicates: {n}
87
-
88
- ### Branch Namespaces
89
- - Active branches: {n}
90
- - Stale (unmerged) folders: {n}
91
- - Ready to prune: {n}
92
-
93
- ───────────────────────────────────────────
94
-
95
- ### Issues Found:
96
-
97
- **1. Possible Duplicate:**
98
- - "Auth uses JWT tokens" (app, abc123)
99
- - "JWT auth with 15-min expiry" (app, def456)
100
- → Action: Merge into one?
101
-
102
- **2. Possible Conflict:**
103
- - "Use tabs for indentation" (team, ghi789)
104
- - "Use 2-space indentation" (team, jkl012)
105
- → Action: Which is correct?
106
-
107
- **3. Stale Branch:**
108
- - branches/old-feature/ (branch deleted)
109
- → Action: Prune with `ragtime prune`?
110
- ```
111
-
112
- ## Step 5: Get User Approval
113
-
114
- For each issue found, ask:
115
-
116
- - "Should I merge these duplicates?"
117
- - "Which of these conflicting memories is correct?"
118
- - "Should I mark this as abandoned?"
119
- - "Run `ragtime prune` to clean stale branches?"
120
-
121
- Only make changes the user explicitly approves.
122
-
123
- ## Step 6: Execute Approved Actions
124
-
125
- <!-- ═══════════════════════════════════════════════════════════════════════════
126
- RAGTIME CORE - DO NOT MODIFY
127
- ═══════════════════════════════════════════════════════════════════════════ -->
128
-
129
- **Delete a memory:**
130
- ```
131
- mcp__ragtime__forget:
132
- memory_id: "{id}"
133
- ```
134
-
135
- **Mark as abandoned:**
136
- ```
137
- mcp__ragtime__update_status:
138
- memory_id: "{id}"
139
- status: "abandoned"
140
- ```
141
-
142
- **Prune stale branches:**
143
- ```bash
144
- ragtime prune
145
- ```
146
-
147
- <!-- ═══════════════════════════════════════════════════════════════════════════ -->
148
-
149
- ## Suggested Cadence
150
-
151
- Run monthly or when memories feel cluttered.