mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-10-10 03:27:56 +00:00
Adds the fourth skill to the markdown-html/ domain. The Tier-2 use case
from Shihipar's essay ("Code Review and PR Writeups"): a markdown PR
writeup with ```diff blocks and severity callouts becomes a single-file
2-column HTML review with top jump-nav, diff on the left, severity-tagged
annotation cards on the right, and a mandatory named reviewer footer.
Three stdlib tools pipeline together:
1. diff_parser.py — scans markdown for ```diff fenced blocks, parses each
as a unified diff (--- a/file, +++ b/file, @@ -10,7 +10,8 @@,
space/+/- body lines), assigns per-line numbers on both old (lo) and
new (ln) sides, preserves the per-hunk @@ header context. Supports
--infer-diff for unfenced blocks. Stdlib regex + state machine.
2. annotation_extractor.py — extracts severity callouts (GFM
> [!BLOCKER] style) and inline markers (nit:, blocker:, etc.).
Default convention BLOCKER/MAJOR/MINOR/NIT per Google's Code Review
Developer Guide; overridable via --severity-convention. Attaches each
annotation to the nearest preceding diff block by source-line index;
unanchored annotations go to a "general comments" section. Also
captures LGTM/approve markers separately as approvals.
3. review_html_renderer.py — emits single-file 2-col HTML. Top jump-nav
lists every annotation with severity badge + 80-char preview + jump
link + per-tier counts in heading. Each hunk-row is a CSS grid with
diff on the left (per-line numbers, +/- marks, addition/deletion bg
tints from --md-success/--md-warn via color-mix) and annotation cards
on the right. WCAG-1.4.1-compliant severity badges (color + icon +
aria-label + text — color is NEVER the sole signal); BLOCKER danger
color computed by hue-rotating the design-system accent 120° toward
red so it stays brand-coherent. Approval bar when LGTM markers
present and no findings. Collapses to stacked on viewports < 900px.
Mandatory --reviewer (refuses exit 3 otherwise — research-ops
named-owner discipline). Refuses exit 4 if no hunks present (wrong
skill → route to md-document). No Prism CDN (diff coloring conflicts
with syntax highlighting).
Plus 3 references each citing 5-7 sources:
- diff_rendering_canon.md — POSIX diff format + GitHub/GitLab UI +
difftastic + SWE at Google ch. 9
- severity_coding.md — WCAG 1.4.1 + Google review taxonomy + Don
Norman Design of Everyday Things + NN/g color UX
- pr_annotation_ux.md — convergent 2-col UX from GitHub / GitLab /
Reviewable / CodeStream + SWE at Google + NN/g F-shape
1 template asset documenting the canonical 2-col review HTML shape.
/cs:md-review slash command with 4 pre-flight gates
(under-100-lines, no-onboarding, missing-reviewer, no-hunks) + pipeline
+ output digest.
Repo-level updates:
- markdown-html/.claude-plugin/plugin.json: skills array adds
./skills/md-review; version 2.10.1 → 2.10.2; description updated.
- .claude-plugin/marketplace.json: markdown-html-skills entry version
and description; top-level counters 341 → 342 skills, 542 → 545 Python
tools, 685 → 688 references, 88 → 89 slash commands; metadata.version
2.10.1 → 2.10.2.
- Root CLAUDE.md: v2.10.2 release-notes block above v2.10.1.
Validation:
- check_plugin_json.py → OK
- sync-codex-skills.py --dry-run → 1 new symlink, documentation: 4 skills,
total 343
- skill_description_validator.py → PASS (all 5 checks: present, length,
third-person, trigger, action verb)
- skill_review_checklist_runner.py → 5/6 PASS (97 lines passes
under-100-lines check; minor WARN on "user" vs "developer" terminology
which are contextually distinct — converter operator vs review subject)
- All 3 tools pass --help and --sample
- Hard rules verified end-to-end:
no --reviewer → exit 3 with refusal message
no hunks → exit 4 with refusal message + md-document routing hint
custom severity convention "critical,important,suggestion,nit" works
- Full pipeline on sample PR (2 diff blocks, 2 callouts) produces 11.3 KB
single-file HTML with all 14 expected components (reviewer footer, PR
title, aria-labels per WCAG 1.4.1, --md-danger computed color, modern
color-mix tints, 2-col grid, 900px responsive collapse, both file
paths, annotation cards, jump-nav, addition+deletion classes, Findings
heading).
Coming in v2.10.3: md-slides — slide splitter + presenter-notes parser
+ arrow-key/space-bar nav + @media print for PDF export. Reuses
md-document's renderer scaffolding + design-system/scripts/config_loader.py.
https://claude.ai/code/session_01BK2KoQot1U7J5oSosrCQdc
305 lines
9.9 KiB
Python
305 lines
9.9 KiB
Python
#!/usr/bin/env python3
|
|
"""diff_parser.py - Extract unified-diff hunks from markdown code review notes.
|
|
|
|
Stdlib-only. Scans markdown for ```diff fenced code blocks (the convention used
|
|
in PR writeups), parses each one as a unified diff, and returns structured
|
|
hunk data the renderer can lay out as a 2-column annotated diff.
|
|
|
|
Pattern (the standard unified-diff shape):
|
|
|
|
```diff
|
|
--- a/path/to/file.py
|
|
+++ b/path/to/file.py
|
|
@@ -10,7 +10,8 @@ def existing_context
|
|
context line (unchanged)
|
|
-removed line
|
|
+added line
|
|
+another added line
|
|
context line
|
|
@@ -50,3 +51,4 @@
|
|
...
|
|
```
|
|
|
|
Also accepts:
|
|
- Multiple ```diff blocks (each treated as a separate "section")
|
|
- Inline-fenced diffs (no language tag) IF --infer-diff is passed
|
|
- Per-hunk @@ header context strings (preserved in output)
|
|
|
|
NO LLM CALLS. Pure regex + state machine.
|
|
|
|
Usage:
|
|
python diff_parser.py --input review.md --output hunks.json
|
|
python diff_parser.py --sample
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
FENCE_OPEN_RE = re.compile(r"^```(diff)?\s*$")
|
|
FENCE_CLOSE_RE = re.compile(r"^```\s*$")
|
|
FILE_OLD_RE = re.compile(r"^---\s+(?:a/)?(.+?)\s*$")
|
|
FILE_NEW_RE = re.compile(r"^\+\+\+\s+(?:b/)?(.+?)\s*$")
|
|
HUNK_RE = re.compile(
|
|
r"^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@\s*(.*)$"
|
|
)
|
|
|
|
|
|
def _parse_hunk_body(lines: list[str], old_start: int, new_start: int) -> list[dict[str, Any]]:
|
|
"""Walk hunk body lines and assign source-side and target-side line numbers."""
|
|
body: list[dict[str, Any]] = []
|
|
old_num = old_start
|
|
new_num = new_start
|
|
for ln in lines:
|
|
if not ln:
|
|
# treat as context (truly empty line in diff)
|
|
body.append({"kind": "context", "text": "", "old": old_num, "new": new_num})
|
|
old_num += 1
|
|
new_num += 1
|
|
continue
|
|
prefix = ln[0]
|
|
text = ln[1:] if len(ln) > 1 else ""
|
|
if prefix == "+":
|
|
body.append({"kind": "addition", "text": text, "old": None, "new": new_num})
|
|
new_num += 1
|
|
elif prefix == "-":
|
|
body.append({"kind": "deletion", "text": text, "old": old_num, "new": None})
|
|
old_num += 1
|
|
elif prefix == " ":
|
|
body.append({"kind": "context", "text": text, "old": old_num, "new": new_num})
|
|
old_num += 1
|
|
new_num += 1
|
|
elif prefix == "\\":
|
|
body.append({"kind": "meta", "text": text.strip(), "old": None, "new": None})
|
|
else:
|
|
# Unknown line — preserve as context with the raw prefix
|
|
body.append({"kind": "context", "text": ln, "old": old_num, "new": new_num})
|
|
old_num += 1
|
|
new_num += 1
|
|
return body
|
|
|
|
|
|
def _parse_single_diff(block_lines: list[str]) -> list[dict[str, Any]]:
|
|
"""Parse one fenced diff block. Returns a list of file entries.
|
|
|
|
File entry shape:
|
|
{
|
|
"path_old": str | None,
|
|
"path_new": str | None,
|
|
"hunks": [
|
|
{"old_start": int, "new_start": int, "header_context": str,
|
|
"lines": [...], "hunk_index_in_block": int}
|
|
]
|
|
}
|
|
"""
|
|
files: list[dict[str, Any]] = []
|
|
current_file: dict[str, Any] | None = None
|
|
current_hunk: dict[str, Any] | None = None
|
|
hunk_body: list[str] = []
|
|
hunk_index_in_block = 0
|
|
|
|
def flush_hunk() -> None:
|
|
nonlocal current_hunk, hunk_body, hunk_index_in_block
|
|
if current_hunk is None or current_file is None:
|
|
current_hunk = None
|
|
hunk_body = []
|
|
return
|
|
current_hunk["lines"] = _parse_hunk_body(
|
|
hunk_body, current_hunk["old_start"], current_hunk["new_start"]
|
|
)
|
|
current_hunk["hunk_index_in_block"] = hunk_index_in_block
|
|
hunk_index_in_block += 1
|
|
current_file["hunks"].append(current_hunk)
|
|
current_hunk = None
|
|
hunk_body = []
|
|
|
|
def flush_file() -> None:
|
|
nonlocal current_file
|
|
flush_hunk()
|
|
if current_file:
|
|
files.append(current_file)
|
|
current_file = None
|
|
|
|
for raw in block_lines:
|
|
m_old = FILE_OLD_RE.match(raw)
|
|
m_new = FILE_NEW_RE.match(raw)
|
|
m_hunk = HUNK_RE.match(raw)
|
|
|
|
if m_old:
|
|
# New file boundary; flush previous
|
|
flush_file()
|
|
current_file = {"path_old": m_old.group(1), "path_new": None, "hunks": []}
|
|
continue
|
|
if m_new:
|
|
if current_file is None:
|
|
current_file = {"path_old": None, "path_new": m_new.group(1), "hunks": []}
|
|
else:
|
|
current_file["path_new"] = m_new.group(1)
|
|
continue
|
|
if m_hunk:
|
|
flush_hunk()
|
|
current_hunk = {
|
|
"old_start": int(m_hunk.group(1)),
|
|
"old_count": int(m_hunk.group(2) or 1),
|
|
"new_start": int(m_hunk.group(3)),
|
|
"new_count": int(m_hunk.group(4) or 1),
|
|
"header_context": m_hunk.group(5).strip(),
|
|
"lines": [],
|
|
}
|
|
continue
|
|
if current_hunk is not None:
|
|
hunk_body.append(raw)
|
|
|
|
flush_file()
|
|
return files
|
|
|
|
|
|
def parse_markdown_for_diffs(text: str, infer_unfenced: bool = False) -> dict[str, Any]:
|
|
"""Top-level: find every ```diff block and parse it.
|
|
|
|
Returns:
|
|
{
|
|
"blocks": [
|
|
{"block_index": int, "files": [...]},
|
|
...
|
|
],
|
|
"summary": {"total_files": int, "total_hunks": int, "total_blocks": int}
|
|
}
|
|
"""
|
|
lines = text.splitlines()
|
|
in_block = False
|
|
is_diff_block = False
|
|
block_buf: list[str] = []
|
|
blocks: list[dict[str, Any]] = []
|
|
block_index = 0
|
|
block_line_starts: list[int] = []
|
|
|
|
i = 0
|
|
while i < len(lines):
|
|
ln = lines[i]
|
|
if not in_block:
|
|
m = FENCE_OPEN_RE.match(ln)
|
|
if m:
|
|
in_block = True
|
|
lang = m.group(1)
|
|
is_diff_block = (lang == "diff")
|
|
block_buf = []
|
|
block_line_starts.append(i)
|
|
else:
|
|
if FENCE_CLOSE_RE.match(ln):
|
|
if is_diff_block:
|
|
files = _parse_single_diff(block_buf)
|
|
blocks.append({
|
|
"block_index": block_index,
|
|
"source_line": block_line_starts[block_index],
|
|
"files": files,
|
|
})
|
|
block_index += 1
|
|
elif infer_unfenced:
|
|
# Try to detect if this looks like a diff (starts with --- / +++ / @@)
|
|
head = "\n".join(block_buf[:3])
|
|
if "@@" in head or head.startswith("---") or head.startswith("+++"):
|
|
files = _parse_single_diff(block_buf)
|
|
blocks.append({
|
|
"block_index": block_index,
|
|
"source_line": block_line_starts[block_index],
|
|
"files": files,
|
|
"inferred": True,
|
|
})
|
|
block_index += 1
|
|
in_block = False
|
|
is_diff_block = False
|
|
block_buf = []
|
|
else:
|
|
block_buf.append(ln)
|
|
i += 1
|
|
|
|
total_files = sum(len(b["files"]) for b in blocks)
|
|
total_hunks = sum(
|
|
sum(len(f["hunks"]) for f in b["files"]) for b in blocks
|
|
)
|
|
|
|
return {
|
|
"blocks": blocks,
|
|
"summary": {
|
|
"total_files": total_files,
|
|
"total_hunks": total_hunks,
|
|
"total_blocks": len(blocks),
|
|
},
|
|
}
|
|
|
|
|
|
SAMPLE_MARKDOWN = """# PR Review: Add payment retry logic
|
|
|
|
Two changes worth flagging.
|
|
|
|
```diff
|
|
--- a/payments/retry.py
|
|
+++ b/payments/retry.py
|
|
@@ -10,7 +10,8 @@ def schedule_retry(payment_id, attempt):
|
|
if attempt > MAX_ATTEMPTS:
|
|
return None
|
|
delay = 2 ** attempt
|
|
- return _enqueue(payment_id, delay)
|
|
+ jitter = random.uniform(0, delay * 0.1)
|
|
+ return _enqueue(payment_id, delay + jitter)
|
|
```
|
|
|
|
> [!MAJOR]
|
|
> This `random.uniform()` call is not seeded. In test runs it'll produce
|
|
> non-deterministic retry delays — make tests flaky.
|
|
|
|
```diff
|
|
--- a/payments/queue.py
|
|
+++ b/payments/queue.py
|
|
@@ -40,3 +40,7 @@ def _enqueue(payment_id, delay):
|
|
redis.zadd("retries", {payment_id: time.time() + delay})
|
|
log.info("scheduled", payment_id=payment_id, delay=delay)
|
|
+
|
|
+def cancel_retries(payment_id):
|
|
+ redis.zrem("retries", payment_id)
|
|
+ log.info("cancelled retries", payment_id=payment_id)
|
|
```
|
|
|
|
> [!NIT]
|
|
> Minor — would prefer `log.info("retries.cancelled", ...)` (dotted event name).
|
|
"""
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
p = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
p.add_argument("--input", help="Path to markdown file, or '-' for stdin")
|
|
p.add_argument("--output", help="Path to write JSON output (else stdout)")
|
|
p.add_argument("--infer-diff", action="store_true",
|
|
help="Also parse unfenced or language-less blocks that look like diffs")
|
|
p.add_argument("--sample", action="store_true",
|
|
help="Parse a built-in sample PR-review markdown")
|
|
args = p.parse_args(argv)
|
|
|
|
if args.sample:
|
|
text = SAMPLE_MARKDOWN
|
|
elif args.input:
|
|
text = sys.stdin.read() if args.input == "-" else Path(args.input).read_text(encoding="utf-8")
|
|
else:
|
|
p.print_help()
|
|
return 0
|
|
|
|
result = parse_markdown_for_diffs(text, infer_unfenced=args.infer_diff)
|
|
payload = json.dumps(result, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(payload, encoding="utf-8")
|
|
print(f"wrote {args.output}: {result['summary']['total_files']} files, "
|
|
f"{result['summary']['total_hunks']} hunks across "
|
|
f"{result['summary']['total_blocks']} diff blocks")
|
|
else:
|
|
print(payload)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|