[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7BVJWVC0BSBJ2HZP8HPJB69":3,"skill-same:01M7BVJWVC0BSBJ2HZP8HPJB69":45},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":27,"repositoryHtmlUrl":28,"repositoryHomepageUrl":6,"ownerHtmlUrl":29,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":30,"install":31,"resources":44},"01M7BVJWVC0BSBJ2HZP8HPJB69","academic-pdf-redaction",null,"Redact text from PDF documents for blind review anonymization","linyouxue\u002Fskill-repair-benchmark","linyouxue","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F216151464?v=4","01M7BJEVCXKD74KH6FG9CBVSQB","林宥学","01M7BJEVE02NP217V93BKZFC0J",0,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fevaluation\u002Fresults\u002FMMG2Skill-Opus47-Gold15-20260924\u002Fruns\u002Fmmg2skill-opus47-gold15-20260924\u002Fpaper-anonymizer-mmg2skill-opus47-r001\u002Finputs\u002Fskills\u002Facademic-pdf-redaction\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: academic-pdf-redaction\ndescription: Redact text from PDF documents for blind review anonymization\n---\n\n# PDF Redaction for Blind Review\nRedact identifying information from academic papers for blind review.\n## CRITICAL RULES\n1. **PRESERVE References section** - Self-citations MUST remain intact. Prefer keeping the References pages **byte-identical** to the original (skip redaction entirely on those pages) so self-citations are provably untouched.\n2. **ONLY redact specific text matches** - Never redact entire pages\u002Fregions\n3. **VERIFY output** - Check page counts preserved and ≥80% text retention, then run a regex leak scan on pre-references pages.\n## Common Pitfalls to AVOID\n```python\n# ❌ WRONG - This removes ALL text from the page:\nfor block in page.get_text(\"blocks\"):\n    page.add_redact_annot(fitz.Rect(block[:4]))\n# ❌ WRONG - Drawing rectangles over text:\npage.draw_rect(fitz.Rect(0, 0, 600, 100), fill=(0,0,0))\n# ✅ CORRECT - Only redact specific search matches:\nfor rect in page.search_for(\"John Smith\"):\n    page.add_redact_annot(rect)\n```\n## Patterns to Redact (Before References Only)\n**IMPORTANT: Use FULL names\u002Fphrases, not partial matches!**\n- ✅ \"John Smith\" (full name)\n- ❌ \"Smith\" (partial - would incorrectly match \"Smith et al.\" citations in References)\n1. **Author names** - FULL names only (e.g., \"John Smith\", not just \"Smith\")\n2. **Affiliations** - Universities, companies (e.g., \"Duke University\")\n3. **Email addresses** - Pattern: `*@*.edu`, `*@*.com`\n4. **Venue names** - Conference\u002Fworkshop names (e.g., \"ICML 2024\", \"ICML Workshop\"), including \"Accepted at ...\" \u002F \"To appear in ...\" banners.\n5. **arXiv identifiers** - Pattern: `arXiv:XXXX.XXXXX`, AND arXiv banner fragments left on the side margin such as subject tags `[eess.AS]`, `[cs.CL]` and submission dates like `30 Sep 2025`. These often appear on a rotated margin and must be searched for explicitly.\n6. **DOIs** - Pattern: `10.XXXX\u002F...`\n7. **Acknowledgement content** - Names AND grant\u002Ffunder identifiers (e.g., ARO\u002FNSF\u002FDARPA grant numbers like `W911NF-23-2-0224`). Grant numbers frequently break across lines (e.g., `W911NF` \u002F `-23-2-` \u002F `0224`), so also add patterns for each broken fragment and the surrounding phrase (e.g., `and ARO`, `was supported in part by`). After redaction, re-open the file and inspect residual context around funder acronyms to catch stray connective tokens like `and ARO`.\n8. **Equal contribution footnotes** - e.g., \"Equal contribution\", \"* Equal contribution\"\n## PyMuPDF (fitz) - Recommended Approach\n```python\nimport fitz\nimport os\ndef redact_with_pymupdf(input_path: str, output_path: str, patterns: list[str]):\n    \"\"\"Redact specific patterns from PDF using PyMuPDF.\"\"\"\n    doc = fitz.open(input_path)\n    # Find References page - stop redacting there (leave those pages byte-identical)\n    references_page = None\n    for i, page in enumerate(doc):\n        if \"references\" in page.get_text().lower():\n            references_page = i\n            break\n    for page_num, page in enumerate(doc):\n        if references_page is not None and page_num >= references_page:\n            continue  # Skip References section entirely -> byte-identical\n        for pattern in patterns:\n            for rect in page.search_for(pattern):\n                page.add_redact_annot(rect, fill=(0, 0, 0))\n        page.apply_redactions()\n    os.makedirs(os.path.dirname(output_path), exist_ok=True)\n    doc.save(output_path)\n    doc.close()\n    verify_redaction(input_path, output_path)\n```\n## REQUIRED: Verification + Leak Scan\n**Always run BOTH after redaction:**\n```python\nimport fitz, re\ndef verify_redaction(original_path, output_path):\n    \"\"\"Verify redaction didn't corrupt the PDF.\"\"\"\n    orig = fitz.open(original_path)\n    redc = fitz.open(output_path)\n    orig_len = sum(len(p.get_text()) for p in orig)\n    redc_len = sum(len(p.get_text()) for p in redc)\n    print(f\"Original: {len(orig)} pages, {orig_len} chars\")\n    print(f\"Redacted: {len(redc)} pages, {redc_len} chars\")\n    print(f\"Retained: {redc_len\u002Forig_len:.1%}\")\n    if len(redc) != len(orig):\n        raise ValueError(f\"Page count changed: {len(orig)} -> {len(redc)}\")\n    if redc_len \u003C 1000:\n        raise ValueError(f\"PDF corrupted: only {redc_len} chars remain!\")\n    if redc_len \u003C orig_len * 0.7:\n        raise ValueError(f\"Too much removed: kept only {redc_len\u002Forig_len:.0%}\")\n    orig.close(); redc.close()\n    print(\"✓ Verification passed\")\ndef leak_scan(output_path, references_page, known_leaks: list[str], regexes: list[str]):\n    \"\"\"Scan pre-references pages for any residual identifying tokens.\"\"\"\n    doc = fitz.open(output_path)\n    hits = []\n    for i, page in enumerate(doc):\n        if references_page is not None and i >= references_page:\n            break\n        txt = page.get_text()\n        for s in known_leaks:\n            if s.lower() in txt.lower():\n                hits.append((i, s))\n        for pat in regexes:\n            for m in re.finditer(pat, txt, flags=re.IGNORECASE):\n                hits.append((i, m.group(0)))\n    doc.close()\n    if hits:\n        print(\"✗ LEAKS:\", hits)\n    else:\n        print(\"✓ CLEAN\")\n    return hits\n```\nRun `leak_scan` with regexes covering: author-name list, affiliations, `\\w+@\\w+\\.\\w+`, `arXiv:\\d{4}\\.\\d{4,5}`, subject tags `\\[[a-z]+\\.[A-Z]{2}\\]`, dates like `\\d{1,2}\\s+[A-Z][a-z]{2}\\s+20\\d{2}`, DOIs `10\\.\\d{4,9}\u002F`, grant IDs (e.g., `W911NF[-\\d]*`, `\\d{7}` NSF-style), and connective tokens near funders (`and ARO`, `and NSF`).\n## Iterative fix loop\nIf the leak scan surfaces a residual token, inspect its **surrounding context** with `page.get_text()` slice to catch line-broken fragments (e.g., a grant ID split across three lines yielding a stray `and ARO`). Add each fragment as its own pattern, re-run, and re-scan until CLEAN for every paper.\n## Preserving self-citations\nBecause References pages are skipped entirely, self-citations remain intact by construction. After saving, optionally confirm by comparing extracted text of References pages between original and redacted files and asserting they are byte-identical.\n","# PDF Redaction for Blind Review\nRedact identifying information from academic papers for blind review.\n## CRITICAL RULES\n1. **PRESERVE References section** - Self-citations MUST remain intact. Prefer keeping the References pages **byte-identical** to the original (skip redaction entirely on those pages) so self-citations are provably untouched.\n2. **ONLY redact specific text matches** - Never redact entire pages\u002Fregions\n3. **VERIFY output** - Check page counts preserved and ≥80% text retention, then run a regex leak scan on pre-references pages.\n## Common Pitfalls to AVOID\n```python\n# ❌ WRONG - This removes ALL text from the page:\nfor block in page.get_text(\"blocks\"):\n    page.add_redact_annot(fitz.Rect(block[:4]))\n# ❌ WRONG - Drawing rectangles over text:\npage.draw_rect(fitz.Rect(0, 0, 600, 100), fill=(0,0,0))\n# ✅ CORRECT - Only redact specific search matches:\nfor rect in page.search_for(\"John Smith\"):\n    page.add_redact_annot(rect)\n```\n## Patterns to Redact (Before References Only)\n**IMPORTANT: Use FULL names\u002Fphrases, not partial matches!**\n- ✅ \"John Smith\" (full name)\n- ❌ \"Smith\" (partial - would incorrectly match \"Smith et al.\" citations in References)\n1. **Author names** - FULL names only (e.g., \"John Smith\", not just \"Smith\")\n2. **Affiliations** - Universities, companies (e.g., \"Duke University\")\n3. **Email addresses** - Pattern: `*@*.edu`, `*@*.com`\n4. **Venue names** - Conference\u002Fworkshop names (e.g., \"ICML 2024\", \"ICML Workshop\"), including \"Accepted at ...\" \u002F \"To appear in ...\" banners.\n5. **arXiv identifiers** - Pattern: `arXiv:XXXX.XXXXX`, AND arXiv banner fragments left on the side margin such as subject tags `[eess.AS]`, `[cs.CL]` and submission dates like `30 Sep 2025`. These often appear on a rotated margin and must be searched for explicitly.\n6. **DOIs** - Pattern: `10.XXXX\u002F...`\n7. **Acknowledgement content** - Names AND grant\u002Ffunder identifiers (e.g., ARO\u002FNSF\u002FDARPA grant numbers like `W911NF-23-2-0224`). Grant numbers frequently break across lines (e.g., `W911NF` \u002F `-23-2-` \u002F `0224`), so also add patterns for each broken fragment and the surrounding phrase (e.g., `and ARO`, `was supported in part by`). After redaction, re-open the file and inspect residual context around funder acronyms to catch stray connective tokens like `and ARO`.\n8. **Equal contribution footnotes** - e.g., \"Equal contribution\", \"* Equal contribution\"\n## PyMuPDF (fitz) - Recommended Approach\n```python\nimport fitz\nimport os\ndef redact_with_pymupdf(input_path: str, output_path: str, patterns: list[str]):\n    \"\"\"Redact specific patterns from PDF using PyMuPDF.\"\"\"\n    doc = fitz.open(input_path)\n    # Find References page - stop redacting there (leave those pages byte-identical)\n    references_page = None\n    for i, page in enumerate(doc):\n        if \"references\" in page.get_text().lower():\n            references_page = i\n            break\n    for page_num, page in enumerate(doc):\n        if references_page is not None and page_num >= references_page:\n            continue  # Skip References section entirely -> byte-identical\n        for pattern in patterns:\n            for rect in page.search_for(pattern):\n                page.add_redact_annot(rect, fill=(0, 0, 0))\n        page.apply_redactions()\n    os.makedirs(os.path.dirname(output_path), exist_ok=True)\n    doc.save(output_path)\n    doc.close()\n    verify_redaction(input_path, output_path)\n```\n## REQUIRED: Verification + Leak Scan\n**Always run BOTH after redaction:**\n```python\nimport fitz, re\ndef verify_redaction(original_path, output_path):\n    \"\"\"Verify redaction didn't corrupt the PDF.\"\"\"\n    orig = fitz.open(original_path)\n    redc = fitz.open(output_path)\n    orig_len = sum(len(p.get_text()) for p in orig)\n    redc_len = sum(len(p.get_text()) for p in redc)\n    print(f\"Original: {len(orig)} pages, {orig_len} chars\")\n    print(f\"Redacted: {len(redc)} pages, {redc_len} chars\")\n    print(f\"Retained: {redc_len\u002Forig_len:.1%}\")\n    if len(redc) != len(orig):\n        raise ValueError(f\"Page count changed: {len(orig)} -> {len(redc)}\")\n    if redc_len \u003C 1000:\n        raise ValueError(f\"PDF corrupted: only {redc_len} chars remain!\")\n    if redc_len \u003C orig_len * 0.7:\n        raise ValueError(f\"Too much removed: kept only {redc_len\u002Forig_len:.0%}\")\n    orig.close(); redc.close()\n    print(\"✓ Verification passed\")\ndef leak_scan(output_path, references_page, known_leaks: list[str], regexes: list[str]):\n    \"\"\"Scan pre-references pages for any residual identifying tokens.\"\"\"\n    doc = fitz.open(output_path)\n    hits = []\n    for i, page in enumerate(doc):\n        if references_page is not None and i >= references_page:\n            break\n        txt = page.get_text()\n        for s in known_leaks:\n            if s.lower() in txt.lower():\n                hits.append((i, s))\n        for pat in regexes:\n            for m in re.finditer(pat, txt, flags=re.IGNORECASE):\n                hits.append((i, m.group(0)))\n    doc.close()\n    if hits:\n        print(\"✗ LEAKS:\", hits)\n    else:\n        print(\"✓ CLEAN\")\n    return hits\n```\nRun `leak_scan` with regexes covering: author-name list, affiliations, `\\w+@\\w+\\.\\w+`, `arXiv:\\d{4}\\.\\d{4,5}`, subject tags `\\[[a-z]+\\.[A-Z]{2}\\]`, dates like `\\d{1,2}\\s+[A-Z][a-z]{2}\\s+20\\d{2}`, DOIs `10\\.\\d{4,9}\u002F`, grant IDs (e.g., `W911NF[-\\d]*`, `\\d{7}` NSF-style), and connective tokens near funders (`and ARO`, `and NSF`).\n## Iterative fix loop\nIf the leak scan surfaces a residual token, inspect its **surrounding context** with `page.get_text()` slice to catch line-broken fragments (e.g., a grant ID split across three lines yielding a stray `and ARO`). Add each fragment as its own pattern, re-run, and re-scan until CLEAN for every paper.\n## Preserving self-citations\nBecause References pages are skipped entirely, self-citations remain intact by construction. After saving, optionally confirm by comparing extracted text of References pages between original and redacted files and asserting they are byte-identical.\n",{"name":5,"description":7},"main","cdcb6d230f3b15c3c33f8a2bdd16439f43602cbe","evaluation\u002Fresults\u002FMMG2Skill-Opus47-Gold15-20260924\u002Fruns\u002Fmmg2skill-opus47-gold15-20260924\u002Fpaper-anonymizer-mmg2skill-opus47-r001\u002Finputs\u002Fskills\u002Facademic-pdf-redaction",1,"https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark","https:\u002F\u002Fgithub.com\u002Flinyouxue","2026-11-14 20:28:35",{"source":8,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add linyouxue\u002Fskill-repair-benchmark --skill academic-pdf-redaction",{"tool":37,"command":38},"bunx","bunx skills add linyouxue\u002Fskill-repair-benchmark --skill academic-pdf-redaction",{"tool":40,"command":41},"pnpm","pnpm dlx skills add linyouxue\u002Fskill-repair-benchmark --skill academic-pdf-redaction","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fevaluation\u002Fresults\u002FMMG2Skill-Opus47-Gold15-20260924\u002Fruns\u002Fmmg2skill-opus47-gold15-20260924\u002Fpaper-anonymizer-mmg2skill-opus47-r001\u002Finputs\u002Fskills\u002Facademic-pdf-redaction\u002FSKILL.md",true,[],[46,53,60,67,72,77],{"skillNo":47,"name":48,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":50,"categories":51,"keywords":52,"publishedAt":19},"01M7E1HGVRY73NBJ022CJG13X6","video-frame-extraction","Extract frames from video files and save them as images using OpenCV","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_03\u002Finputs\u002Fskills\u002Fvideo-frame-extraction\u002FSKILL.md",[],[],{"skillNo":54,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":57,"categories":58,"keywords":59,"publishedAt":19},"01M7E1HAG5R7CJ9C8NYJS771MB","openai-vision","Analyze images and multi-frame sequences using OpenAI GPT vision models","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_03\u002Finputs\u002Fskills\u002Fopenai-vision\u002FSKILL.md",[],[],{"skillNo":61,"name":62,"nameZh":6,"description":63,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":64,"categories":65,"keywords":66,"publishedAt":19},"01M7E1H7DADKB6QBWKC2KQ63SZ","image-ocr","Extract text content from images using Tesseract OCR via Python, including structured field extraction from receipts and forms","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_03\u002Finputs\u002Fskills\u002Fimage-ocr\u002FSKILL.md",[],[],{"skillNo":68,"name":48,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M7E1H0WBNT43TENGQY7AVZHD","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_02\u002Finputs\u002Fskills\u002Fvideo-frame-extraction\u002FSKILL.md",[],[],{"skillNo":73,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":76,"publishedAt":19},"01M7E1GTC7D4N4Q2EF03875J9E","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_02\u002Finputs\u002Fskills\u002Fopenai-vision\u002FSKILL.md",[],[],{"skillNo":78,"name":62,"nameZh":6,"description":63,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":81,"publishedAt":19},"01M7E1GQ92CWFMGH53KSSE4WER","https:\u002F\u002Fgithub.com\u002Flinyouxue\u002Fskill-repair-benchmark\u002Fblob\u002Fmain\u002Fjpg-ocr-stat\u002Fresult\u002Fskilltta-claude4.7\u002Fgroup_03\u002Frun_02\u002Finputs\u002Fskills\u002Fimage-ocr\u002FSKILL.md",[],[]]