CLI and library for the Markdown Review Sidecar Format (MRSF / Sidemark)
Project description
mrsf — Markdown Review Sidecar Format (Python)
Python CLI and SDK for the MRSF (Sidemark) specification. A 1:1 port of the Node.js @mrsf/cli package.
Installation
pip install mrsf
CLI Usage
# Validate sidecar files
mrsf validate docs/*.review.yaml
mrsf validate --strict README.md
# Add a comment
mrsf add README.md --author "Alice" --text "Fix this" --line 10
# Re-anchor after edits
mrsf reanchor docs/
mrsf reanchor --staged --force
mrsf reanchor --dry-run --threshold 0.8
# List comments
mrsf list docs/
mrsf list --open --severity high --json
# Resolve/unresolve
mrsf resolve doc.md.review.yaml abc123
mrsf resolve doc.md.review.yaml abc123 --undo
# Check anchor health
mrsf status docs/
mrsf status --json
# Create empty sidecar
mrsf init README.md
# Rename document + sidecar
mrsf rename old.md new.md
# Watch for changes
mrsf watch docs/ --reanchor --force
Global Options
| Option | Description |
|---|---|
--cwd <dir> |
Working directory |
--config <path> |
Path to .mrsf.yaml config |
-q, --quiet |
Suppress non-essential output |
-v, --verbose |
Detailed diagnostic output |
--no-color |
Disable color output |
SDK Usage
import mrsf
# Parse a sidecar file
doc = mrsf.parse_sidecar("README.md.review.yaml")
# Iterate comments
for comment in doc.comments:
print(f"{comment.author}: {comment.text} (line {comment.line})")
# Add a comment
opts = mrsf.AddCommentOptions(
author="Alice",
text="Consider rephrasing this section",
line=42,
selected_text="The quick brown fox",
type="suggestion",
severity="medium",
)
new_comment = mrsf.add_comment(doc, opts)
# Add a comment with tool-specific x_* metadata
bot_opts = mrsf.AddCommentOptions(
author="review-bot",
text="Needs a second pass",
line=12,
extensions={
"x_source": "review-bot",
"x_score": 0.91,
"x_labels": ["needs-review", "docs"],
},
)
mrsf.add_comment(doc, bot_opts)
# Write back (preserves YAML formatting)
mrsf.write_sidecar("README.md.review.yaml", doc)
# Validate
result = mrsf.validate(doc)
if not result.valid:
for err in result.errors:
print(f"Error: {err.message}")
# Re-anchor after edits
results = mrsf.reanchor_document(doc, document_lines)
for r in results:
print(f"{r.comment_id}: {r.status} ({r.reason})")
# Discover sidecar for a document
sidecar_path = mrsf.discover_sidecar("docs/guide.md")
# Fuzzy text matching
score = mrsf.combined_score("original text", "edited text")
matches = mrsf.fuzzy_search("search text", ["line 1", "line 2", "line 3"])
API Reference
Parsing
| Function | Description |
|---|---|
parse_sidecar(path) |
Parse a .review.yaml or .review.json file |
parse_sidecar_content(content, format) |
Parse sidecar from string content |
read_document_lines(path) |
Read document lines for anchoring |
Writing
| Function | Description |
|---|---|
write_sidecar(path, doc) |
Write with round-trip YAML preservation |
to_yaml(doc) |
Serialize to YAML string |
to_json(doc) |
Serialize to JSON string |
compute_hash(text) |
SHA-256 hash for selected_text_hash |
sync_hash(comment) |
Sync hash with current selected_text |
Comments
| Function | Description |
|---|---|
add_comment(doc, opts) |
Add a new comment |
resolve_comment(doc, id) |
Mark comment as resolved |
unresolve_comment(doc, id) |
Mark comment as unresolved |
remove_comment(doc, id, opts) |
Remove with reply promotion (§9.1) |
filter_comments(comments, filter) |
Filter by status/author/type/severity |
get_threads(comments) |
Group into reply threads |
summarize(comments) |
Aggregate statistics |
AddCommentOptions accepts an optional extensions map. Keys must start with x_, and entries are written back to the sidecar as flat x_* fields on the comment.
Validation
| Function | Description |
|---|---|
validate(doc) |
Validate an MrsfDocument in memory |
validate_file(path) |
Validate a sidecar file on disk |
Re-anchoring
| Function | Description |
|---|---|
reanchor_comment(comment, lines, opts) |
Re-anchor a single comment |
reanchor_document(doc, lines, opts) |
Re-anchor all comments in a document |
reanchor_file(path, opts) |
High-level file-based re-anchor |
Discovery
| Function | Description |
|---|---|
discover_sidecar(doc_path) |
Find sidecar for a document |
discover_all_sidecars(root) |
Find all sidecars in a directory |
find_workspace_root(cwd) |
Find workspace root (.mrsf.yaml or .git) |
load_config(root) |
Load .mrsf.yaml configuration |
Fuzzy Matching
| Function | Description |
|---|---|
exact_match(needle, haystack) |
Find exact substring matches |
normalized_match(needle, haystack) |
Whitespace-normalized matching |
fuzzy_search(needle, lines) |
Multi-line fuzzy search with scoring |
combined_score(a, b) |
Combined similarity score (0.0–1.0) |
Git Integration
| Function | Description |
|---|---|
is_git_available() |
Check if git is on PATH |
find_repo_root(cwd) |
Find .git root directory |
get_current_commit(root) |
Get HEAD commit SHA |
get_diff(commit, path, root) |
Get unified diff output |
get_line_shift(commit, path, line, root) |
Compute line shift from diff |
Data Types
@dataclass
class MrsfDocument:
mrsf_version: str
document: str
comments: list[Comment]
@dataclass
class Comment:
id: str
author: str
timestamp: str
text: str
resolved: bool = False
line: int | None = None
end_line: int | None = None
selected_text: str | None = None
selected_text_hash: str | None = None
type: str | None = None
severity: str | None = None # "low" | "medium" | "high"
reply_to: str | None = None
commit: str | None = None
# ... plus start_column, end_column, anchored_text
Requirements
- Python ≥ 3.10
- Dependencies:
click,jsonschema,ruamel.yaml,rapidfuzz,rich,watchdog
Development
cd python
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Run tests
pytest -v
# Lint
ruff check src/ tests/
# Type check
mypy src/mrsf/
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mrsf-0.4.2.tar.gz.
File metadata
- Download URL: mrsf-0.4.2.tar.gz
- Upload date:
- Size: 67.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d0b20b445b8fee1c106bb0c7b1654804eb11782a3fd46bffdb592e44e1537d9
|
|
| MD5 |
2608f31685f8efd34c4804390c093e77
|
|
| BLAKE2b-256 |
ed276a421ab9ca06696262b4f3c8357cef411020042472c154fe469ac5401e5b
|
File details
Details for the file mrsf-0.4.2-py3-none-any.whl.
File metadata
- Download URL: mrsf-0.4.2-py3-none-any.whl
- Upload date:
- Size: 42.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7315b151c3536d1c837258b7df37d9c5422480b0e349f6590158769c9949085b
|
|
| MD5 |
732f94c86394585595e1c1de6e65ff3c
|
|
| BLAKE2b-256 |
3703d17c9fb1db4b4f71f864e0e337668e93096bdc09e7c73fef06faced93584
|