Spaces:
Build error
Build error
File size: 4,259 Bytes
246bf80 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | """Cursor memory writer — exports to .cursor/rules/*.mdc files.
Cursor's rules system:
- Files in .cursor/rules/ with .mdc extension
- YAML frontmatter with: description, globs (optional), alwaysApply (bool)
- Markdown body with instructions
- Rules with alwaysApply: true are always loaded
- Rules with globs are loaded when matching files are in context
"""
from __future__ import annotations
from collections import defaultdict
from pathlib import Path
from headroom.memory.writers.base import (
MARKER_END,
MARKER_PATTERN,
MARKER_START,
AgentWriter,
ExportResult,
MemoryEntry,
_estimate_tokens,
)
class CursorMemoryWriter(AgentWriter):
"""Writes memories to Cursor's .cursor/rules/*.mdc format."""
agent_name = "cursor"
default_token_budget = 3000
def format_memories(self, memories: list[MemoryEntry]) -> str:
"""Format as Cursor .mdc content (body only, no frontmatter).
Frontmatter is handled in export() since it's outside markers.
"""
lines: list[str] = []
grouped: dict[str, list[MemoryEntry]] = defaultdict(list)
for m in memories:
cat = m.category or "General"
heading = cat.replace("_", " ").title()
grouped[heading].append(m)
for heading, entries in grouped.items():
lines.append(f"## {heading}")
lines.append("")
for entry in entries:
lines.append(f"- {entry.content}")
lines.append("")
return "\n".join(lines)
def default_path(self) -> Path:
"""Default: .cursor/rules/headroom-memory.mdc in project root."""
return self._project_path / ".cursor" / "rules" / "headroom-memory.mdc"
def export(
self,
memories: list[MemoryEntry],
output_path: Path | None = None,
dry_run: bool = True,
) -> ExportResult:
"""Export with Cursor-specific .mdc frontmatter."""
result = ExportResult(dry_run=dry_run)
if not memories:
return result
# Rank, dedup, budget (reuse base logic)
ranked = sorted(memories, key=lambda m: m.score, reverse=True)
seen: set[str] = set()
unique: list[MemoryEntry] = []
for m in ranked:
h = m.content_hash
if h in seen:
result.memories_skipped_dedup += 1
continue
seen.add(h)
unique.append(m)
budgeted: list[MemoryEntry] = []
tokens_used = 0
for m in unique:
entry_tokens = _estimate_tokens(m.content) + 10
if tokens_used + entry_tokens > self._token_budget:
result.memories_skipped_budget += 1
continue
tokens_used += entry_tokens
budgeted.append(m)
if not budgeted:
return result
# Build full .mdc file content
body = self.format_memories(budgeted)
target = output_path or self.default_path()
# If file exists and has our markers, only replace marker section
if target.exists():
existing = target.read_text()
if MARKER_START in existing:
section = f"{MARKER_START}\n{body}\n{MARKER_END}"
full_content = MARKER_PATTERN.sub(section, existing)
else:
# Append our section
section = f"{MARKER_START}\n{body}\n{MARKER_END}"
full_content = existing.rstrip() + "\n\n" + section + "\n"
else:
# Create new .mdc file with frontmatter
full_content = (
"---\n"
"description: Headroom-learned patterns from proxy traffic\n"
"alwaysApply: true\n"
"---\n"
"\n"
"# Headroom Learned Context\n"
"\n"
f"{MARKER_START}\n{body}\n{MARKER_END}\n"
)
result.files_written.append(target)
result.content_by_file[str(target)] = full_content
result.memories_exported = len(budgeted)
if not dry_run:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(full_content)
return result
|