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 --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
mrsf add uses user.name from the repository-local .git/config when
--author is omitted. Pass --author to override it; global Git identity is
not used.
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 |
create_revision_projection(source, target) |
Build revision-aware line projections |
create_anchor_context_index(source, target) |
Index Markdown blocks and neighboring context |
reconcile_comment_anchors(comments, results, context) |
Resolve ambiguous anchors from same-section landmarks |
calibrate_anchor_evidence(id, text, projection, context) |
Combine evidence into confidence bands |
The Python implementation uses the same revision projection, bounded candidate retrieval, Markdown structure, global reconciliation, and confidence calibration as the TypeScript implementation. Browser integrations can use the same pure building blocks when they retain a source document snapshot. See the non-normative implementation guide for the complete pipeline and runtime boundaries.
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.
Metadata
Release files for mrsf 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mrsf-0.5.1.tar.gz | 81.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mrsf-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 136.9 kB
Release files / mrsf-0.5.1.tar.gz
| Download URL | mrsf-0.5.1.tar.gz |
|---|---|
| Size | 81.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38841266188bffcb91a8077c61e5603d89c1b0f69929112fe5b3ea113f90dbd8
|
|
BLAKE2b-256 checksum How to use checksums |
49214176cff73fe19bab9187ac5d7a9921273a71ec2fda8d4d9d5c56f0730fdd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / mrsf-0.5.1-py3-none-any.whl
| Download URL | mrsf-0.5.1-py3-none-any.whl |
|---|---|
| Size | 55.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ecbc34bdf2a47c611694a2545aa7d59b22ca1e1f800a1ad88f0ecc7d9ebb9fd5
|
|
BLAKE2b-256 checksum How to use checksums |
08a9989dfb685357ddbed95dd1b6f027dea27d937920e8e6672fc4c1e33d033f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for mrsf 0.5.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.