[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6PJ2J0F93KCK9ZYV2FG573C":3,"skill-same:01M6PJ2J0F93KCK9ZYV2FG573C":86},{"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":23,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":6,"ownerHtmlUrl":50,"ownerBlogUrl":51,"ownerDescription":52,"updatedAt":53,"install":54,"resources":67},"01M6PJ2J0F93KCK9ZYV2FG573C","markdown-medic",null,"Check a markdown file or docs folder for broken relative links and images, skipped heading levels, duplicate anchors; or generate a table of contents. Never makes network calls.","microsoft\u002Faibast-agents-library","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M69ACKE6NWSADP5WEMFXBPPJ",8,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fbeta\u002Fstore\u002Fmarkdown_medic\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000002","文档处理","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"markdown","markdown medic","docs","generate","file","network","table","check","broken","never","images","duplicate","folder","links","calls","for","2026-11-10 10:12:30",false,"---\nname: \"markdown-medic\"\ndescription: \"Check a markdown file or docs folder for broken relative links and images, skipped heading levels, duplicate anchors; or generate a table of contents. Never makes network calls.\"\n---\n\nMarkdown Medic — find what's broken in a docs tree before a reader does.\n\nFour checks that catch the things people actually hit:\n\n    links      relative links and image paths that point at nothing\n    headings   skipped levels (h2 -> h4) and duplicate anchors\n    toc        generate a table of contents with GitHub-style anchors\n    stats      per-file size, heading depth, link and code-fence counts\n\nNo network by default: only relative links are resolved, because those are the\nones you broke. External URLs are counted but never fetched — a docs linter\nthat makes network calls is slow, flaky, and fails in CI for reasons that have\nnothing to do with your docs.\n\nWHY DUPLICATE ANCHORS MATTER\n\nTwo headings with the same text generate the same anchor, so every link to the\nsecond one silently lands on the first. Nothing errors. The page just quietly\nsends readers to the wrong section, and it survives every review because the\nlink \"works\".\n\nWHY SKIPPED HEADING LEVELS MATTER\n\nh2 -> h4 renders fine and reads fine. It breaks screen-reader navigation and\nevery tool that builds structure from headings, which is most of them.\n\n\u003C!-- toaster:generated:begin -->\n\n## Parameters\n\nThe typed contract this capability answers to (JSON Schema — the deterministic layer):\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"action\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"links\",\n        \"headings\",\n        \"toc\",\n        \"stats\"\n      ],\n      \"description\": \"Which check to run.\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"description\": \"A .md file or a folder of them.\"\n    }\n  },\n  \"required\": [\n    \"action\",\n    \"path\"\n  ]\n}\n```\n\n\u003C!-- toaster:generated:end -->\n\n\u003C!-- toaster:generated:begin -->\n\n## Deterministic implementation\n\nRun this instead of improvising when the inputs are well-formed:\n\n```python  # rapp:deterministic\ndef perform(self, **kwargs):\n        action, path = kwargs.get(\"action\"), kwargs.get(\"path\")\n        if not path or not os.path.exists(path):\n            return json.dumps({\"status\": \"error\",\n                               \"message\": f\"not found: {path}\"}, indent=2)\n        files = _md_files(path)\n        if not files:\n            return json.dumps({\"status\": \"ok\", \"files\": 0,\n                               \"note\": \"no .md files found\"}, indent=2)\n        base = path if os.path.isdir(path) else os.path.dirname(path) or \".\"\n\n        try:\n            if action == \"links\":\n                broken, ext, ok = [], 0, 0\n                for f in files:\n                    body = _strip_code(open(f, encoding=\"utf-8\", errors=\"ignore\").read())\n                    for bang, text, href in LINK.findall(body):\n                        if href.startswith((\"http:\u002F\u002F\", \"https:\u002F\u002F\", \"mailto:\", \"#\")):\n                            ext += 1\n                            continue\n                        target = os.path.normpath(\n                            os.path.join(os.path.dirname(f), href.split(\"#\")[0]))\n                        if href.split(\"#\")[0] and not os.path.exists(target):\n                            broken.append({\"file\": os.path.relpath(f, base),\n                                           \"kind\": \"image\" if bang else \"link\",\n                                           \"text\": text[:40], \"href\": href})\n                        else:\n                            ok += 1\n                return json.dumps({\n                    \"status\": \"ok\", \"files\": len(files), \"broken\": len(broken),\n                    \"relative_ok\": ok, \"external_not_checked\": ext,\n                    \"findings\": broken[:100],\n                    \"note\": \"External URLs are counted, never fetched — a linter \"\n                            \"that makes network calls fails in CI for reasons \"\n                            \"unrelated to your docs.\",\n                }, indent=2)\n\n            if action == \"headings\":\n                issues = []\n                for f in files:\n                    body = _strip_code(open(f, encoding=\"utf-8\", errors=\"ignore\").read())\n                    hs = HEADING.findall(body)\n                    seen, prev = {}, 0\n                    for hashes, text in hs:\n                        lvl = len(hashes)\n                        if prev and lvl > prev + 1:\n                            issues.append({\"file\": os.path.relpath(f, base),\n                                           \"kind\": \"skipped-level\",\n                                           \"detail\": f\"h{prev} -> h{lvl}\",\n                                           \"heading\": text[:50]})\n                        a = _anchor(text)\n                        if a in seen:\n                            issues.append({\"file\": os.path.relpath(f, base),\n                                           \"kind\": \"duplicate-anchor\",\n                                           \"detail\": f\"#{a}\", \"heading\": text[:50]})\n                        seen[a] = True\n                        prev = lvl\n                return json.dumps({\n                    \"status\": \"ok\", \"files\": len(files), \"issues\": len(issues),\n                    \"findings\": issues[:100],\n                    \"note\": \"Duplicate anchors silently send every link to the \"\n                            \"second heading to the first one instead.\",\n                }, indent=2)\n\n            if action == \"toc\":\n                out = []\n                for f in files:\n                    body = _strip_code(open(f, encoding=\"utf-8\", errors=\"ignore\").read())\n                    lines = []\n                    for hashes, text in HEADING.findall(body):\n                        lvl = len(hashes)\n                        if lvl == 1:\n                            continue\n                        clean = re.sub(r\"`([^`]*)`\", r\"\\1\", text)\n                        lines.append(\"  \" * (lvl - 2) + f\"- [{clean}](#{_anchor(text)})\")\n                    if lines:\n                        out.append({\"file\": os.path.relpath(f, base),\n                                    \"toc\": \"\\n\".join(lines)})\n                return json.dumps({\"status\": \"ok\", \"files\": len(out),\n                                   \"tables_of_contents\": out[:20]}, indent=2)\n\n            if action == \"stats\":\n                rows = []\n                for f in files:\n                    raw = open(f, encoding=\"utf-8\", errors=\"ignore\").read()\n                    body = _strip_code(raw)\n                    hs = HEADING.findall(body)\n                    rows.append({\n                        \"file\": os.path.relpath(f, base),\n                        \"bytes\": len(raw.encode()), \"lines\": raw.count(\"\\n\") + 1,\n                        \"headings\": len(hs),\n                        \"max_depth\": max([len(h) for h, _ in hs], default=0),\n                        \"links\": len(LINK.findall(body)),\n                        \"code_fences\": len(FENCE.findall(raw)) \u002F\u002F 2,\n                    })\n                rows.sort(key=lambda r: -r[\"bytes\"])\n                return json.dumps({\"status\": \"ok\", \"files\": len(rows),\n                                   \"total_bytes\": sum(r[\"bytes\"] for r in rows),\n                                   \"documents\": rows[:60]}, indent=2)\n\n            return json.dumps({\"status\": \"error\",\n                               \"message\": f\"unknown action {action!r}\",\n                               \"valid\": [\"links\", \"headings\", \"toc\", \"stats\"]}, indent=2)\n        except Exception as e:\n            return json.dumps({\"status\": \"error\",\n                               \"message\": f\"{type(e).__name__}: {e}\"}, indent=2)\n```\n\n\u003C!-- toaster:generated:end -->\n\n\u003C!-- rci-capsule:v1:H4sIAAAAAAAC\u002F+08B3PiSpp\u002FRce7qrUHDAogIV\u002FN7oFA5CAymFk\u002FIbUCKKEspvzfr1sCZ8\u002FM27e3t1VnT9Wg0P31118ODd9zYuBrtpu7tQLDKORk4Emu7vi6beVuc5wGpAMmYqboHmQ7sjBFNwBmu5hsSx6m2IYMXPjhYjvXPgALc4Eh+noIMEO3Dh4mWjKmm6IKvALmHXTHATKmAVHWLRUzQAgM+FwOHEOXRB\u002FA0RJExPsvBF8FFnDTh5gv7tCaCibZlg8s3ytiQzjXhUgdgIdZwI9s94BJomF4xVwhB2LRdAzg5W7vvhVymm0CB2Jw2Z8O3+Vuv+ckQ\u002FTgkNzgvLMBkHWpBpf1IQhDtFT4zkkgZSx47wAXbtKEj2SgYOe7Kw8YSgH78uUQia7qXd9uLez8J0qIfgXMEX0N+4plA4oq8K+2uezdNnddePkcjYVPn4DoCmbZfgYDUgRd214R3RZBrHu+d4Wuny+L\u002FlzgB66F7T3bKsqB6XhX37c5zxf9wNvmbrFtDriu7W5zhZfT3vnb5kzgeZB0aJ6yzSEEFDuw5FvsO1r5YZt7KGC6JUOafSWfIY5kxIPbvjfl+\u002FQ6Q\u002FTNztJ3fxR9+wBxh5\u002FpZPQI\u002F5WdwOVANt+ysaIpn3FMt\u002FPRNnaiB+AuUvpDjC+01z1Zd7MdYVCAweML+NgSTXB+BTm2zRW3ua31BNF3k1fbhXAzecC+foXjU62BeL7dUaZfBQzEfgGzDxAvKNxw7xj+dizSRwXu6F0CPwK05QTxyPOhtt9LtgyubAdYV1CigQVvoYp+3eYCX7mpIoKnUuPBJ7pq2S6k5XXRhYp8dX39PvjUJkAtKmB+irLmghSlfmfYKyqQ2lBbrxAO17cfsw9SB80rQv67vhfpvnYFFUXzfee2VMrEAN14j3emqBu+fZvd\u002FAaR\u002FBF09Adxw\u002FJfMeLHo5Dh0a0AfDwKIgi1GNLzIgyQSCa6uPox5Mvwva1bV68FSbkunPcPLSQyEWhHd\u002Fi3j2j+gmQvp6SW+B0LkuH9MyplwlcUof22ZKSQSLCQOl2gQbOfbhYKD9Ka65+r5Ev1POhIDZF6pt5im0P7QOKTaVimGL9is16CRaKHwKLPu9sy\u002Fi2VGEge9BB9PvyAkmjln9AF6uH7wvOeFbPex\u002FGHts1AColurtHjjA2X59ndR6Te5i6u+B5Bhaw6IBCQEAAKl3EPZeFeQs4dpIRHOvoRIKSt0BqkCGWL3t0SOCTmRxOejG3zvBw2n\u002FRhNOACqEoBdOJyAbpt5MIV4EMsZGwbkDhRhs4eMhpOwZDd\u002FAlvNdF\u002FLwLAFGgDPGRquE5qhaCZgmzwfgFkYKU0g+j4NpbYQRblFN+Vu5cu44dG\u002FRzwvG\u002FXdc8LUmd59+3f05BrCLl2s9boDFsvDff74z2A3JTjghDO+\u002F7wroO67E0TPQ2Fhkg\u002F0TY17wcKZ4QGhIgkP5v2YyOYIoCMHpr21+w2jxE\u002FUeiMG\u002F8SQ3cOh2\u002FSMPiPmzYZ+FDOz5GZ9h3t7wG7+SumfYcbfvjj8M5C+mQtK\u002Fi3H1lHEYlcFrBfoQk\u002F5oaIuIsk49+H\u002Fo9px022iz\u002FJgt++iw\u002FniOQPUhKR5U78Buk5c38UYpxVCrL3X+VvMm5cnmd317\u002FkJrKxv+wmGq9TQMyDSFi+kUDqQB1GriJJU0pkmX0N\u002FIIx9wCM2p6yzfM8RXc9GARZAAqk58N3f9q6+7b0rmG3A\u002F\u002Ff2KpDWn7odT6yzu86gX+awU5Hf\u002F2phf55KC4ZQIS8gTpR9ILdFVTs36\u002Fu\u002Fv77ty\u002FXvyNKwfvtlkBXPzFbKYkupghGpJDX2BfsCuF5g5HX0JtAtb\u002FB7r6nCz58u\u002Frt+wuDCHU+9wF4tF0E\u002FQd7heLzzzeDF2lF6gPFOpelHSkq1++ZqD+ejCNuQ9R\u002FESGID6rtePe2cn8p7qTbDKDZJKHZ\u002FHU1RFi9H2G5dvSn4itXjFBe948o3y+rNlzjnxR+od0+CY71MeX\u002FhEjBTCTxn\u002FgNkS+mZAHQ3BSyZC17i96kAf\u002FVWeCQ1hA\u002FhPwsWM5Mh\u002FdjTEwxvpeBg0pntzAdiK\u002Fu0lnXmQUrYPdZXAkzPxkoYmD4X\u002FEfA7yUYNLV31YrfjwZEeFegcR4og7fHHLNRxiI0ddYqYSRH8B5Vw0RTz3b9a8OIPlqiOZOFjH3Frtx7x5Z8e2fpr5otV\u002FXX9uHyeSjOHiBefUcqSwBQyz4Q1Bh2hWYF2OAZt7d0j+2Bv97Vc\u002FAOlio8H02Nt+zz\u002F9wH34NWCgaehpx3j2KVuGFmBcuVrnwZMW+vV+QBLEEJR1rph8IGdHDwL+s\u002FPvdTxxwBa6L9\u002FeoOnV\u002F\u002F3CLfQevisC5Qs6DibMEcre5bfrvUl\u002FH0gL7JdFH+oBFMIX\u002Fi3dpHUAhEbO2gu8CgO0AlB1U\u002FkemFKBUHHpjxHYeJeZp6QKOREUAGDhKWhrd+RqiKeYA2zEA4lgAdS7BNN2\u002FvQhM1pc4k+r9XkVa8T3DdqCD9DF4AYNVBDwDcuEeit7PXY2sm4FdaWSahZWvU3hvehvZfMjvC5V\u002F1OfAUMUTa+l+O9jdeH5ivAKTSss5OQDuTdqa8fQTKDxGvallLGSRM8IH2aeb1D5lhRgPkWVoP1ZQdsnFTELHYEHSvaYR5IgLPNsIUQlnByQx8BDZbfg\u002FegeZsLVsFFomdpAxtoh9WATCdjBG\u002FqAOlApCVgzaWh\u002FWenSYKRh2VMAUQzwkhXSPH9V\u002FUiCaGEIMz8xESYFsZ1R+Vu6BJFm211hjPu53uNqsidWGXHs0mWKD2mzWnKD3s8h+EoJ0PhI\u002FD6pFFi0\u002FcvXxcca4AubZb7OZrXVOVlBe8pj5GHAzHnz0lLcUseEZ8SziKGIzDUkrFNl9ANOaY6ADOBOBQ1MzxfEuqU\u002Fk2nAmXClrTaXi7kOL7YaQv94ZK5hj6iB6xlmIXIrpNofI7qGWxpk+015nPG42LsEJ1m8umv3nRLqoAgRqpYhApQfpugiz7LaIdXwoJ0CEwuVJUO+tm7O+W2Koq2Jm5mDObmX4+bZtZJzcBboBgcAgKoB6DoVKcW3zkSkFaFx0aBWggJg2yvgUtBcTIZ\u002FZJYifbjrQp6am8vHG9h4vYSB3ufSSVFGe2jfpWqKaNiNhtKRL9+kNdh5fR4\u002FSXiLEOzPanfRNE\u002FHtFsN+Q7qLIgLE8bPMWzYkhJimpSZcHDLTBXK2XNqrfAb1mc1H\u002Fcj7e93S\u002Ffv7c0MSWeevQwi5gJnAF2XRF9Pb67e9JzT0nbAXwSmidzD2RB9v5l3gfjT38h7Ov1w+99i\u002F1kR95s62OSj5iLoGQHEBkF811hBA375H0pEC\u002FAAMdIXIiWWOUAmscye28OIOerW3u9rmUjqgEOdCm8J7o551zx8HXyhw7vO+GIKWRq3g92A5oguXgQbQ+wjU8xEF7PvD9cMDosrWur83RUtXgAelApVhM\u002FAwEIBG1hSz\u002FbswS7hJxbZEFPHHiOBpp9vcf6MxpUvv\u002F8ZEHvzZQF+HxjkdKKVpz9MLF5qjyxvTDKB0Js9eQ032zgTa5uDaL1b3xSz4v0vDQO\u002FS18tQyO6QW3i8ukRUhg1dwk1qJ8+xFLQDBvDROt8eoZ\u002FPBtwj9\u002FGK1dscEsDs4WOT\u002FvblIlmPHj0sljLsHgrPITyVtHg9tXMfnIZ4P7h4qkUhqCkrUQKS1TIgIR3oGlA94+o\u002F\u002Fna93d5d3f19u\u002F327Qu8\u002FrbdXsFbeOV9y19f\u002Fe0WXuS3220ODYH7\u002F4L+v\u002F7bdpvVJC4m+zXgv1\u002F99p0o0A8ITv6qmEfLeF\u002F+My2bgOIAzkyzmbfzfv\u002F992eDkGe4b3Sgx0Silyuq+pklFkqPTFsO0nQDPbm\u002FdxJJhFJ5f589KIbACrNLWb+wMrX1F64iCp\u002FFPDV+Lw8ZnBX\u002FRb8evX5zVuJsEu7Q83Np4GXJLo0dbNuHmSMUqsL5xACMKyDgSDQObyCiYXe3qJB7J6ezZTQaPUXoZO1X+OCROs8KEmjlPJz3oh2cLa5cvypTvNNDUjClCMMg4F5dF5Hrv3TKi+aZasUnBbq+Pq97JgDKK4GcFm2eEfV5Iev2ojzoXxaNwqjdhUyECSNaVRI96GvScgaU8SQLUdBuRcMKzJLniBIoaYmjoc5QevcYB2PZYxjKTMAz847B4EmDQQBUEwtTA5iHoGjxHJSI51AmwzGLC8\u002FwUJij2TAMno04GNrsUBwFp0CqoTZe5vjTgX+wPvhy\u002FFvdg6qHdOz6FYBns\u002F1iSh\u002FIoDOj3gOMgEZQ5W6+XTzDE5Azu55jAVU8HXZzGffIvmelpbcs7KfGCAYaugyjJpQMZIlBGppfDk8VUgamdquIcSheTwNm6D4wx4Ua\u002FMhDRUQd+r+ckzg04S8QSRTsZAbPB0i9H5MyyPKsSpYlbNBhv2DMm11Cy1L88rezfTlTJD1QAiN+1fsKB07PG8+CpLfHua6e4qYLHd4ETc\u002FV+Hnws315POx51+F1nPMqaPh5wPAmWLh6Lwj4kwfw3uuTQO\u002F2Dx\u002FLex\u002FeP3pY723k8zrqeTcUe4rh7N0e5jPvVjMgJNeGQSYMUj6CdCbGs7jvGWikQqiL9yuFEgDN3B+u8fwK4FcSss0t06wmrX4gVXIDq\u002FgyBnlNzCxe+Yd29s7ytcezc0gYxIv8PWZX76Ly8C53HjOcjG7is1g8w\u002Fo1gZ6DeXimh4GT+r1HfU7Tnyele8qBXmjsYwXx80Dn54HOzwOdnwc6Pw90fh7o\u002FDzQ+Xmg8\u002FNA5+eBzs8DnZ8HOj8PdH4e6Pw80Pl5oPPzQOfngc7PA52fBzo\u002FD3R+Huj8f3igM+ut6ahflg3KrDM6YKJbqGl\u002FRhelD14CLZWrhnfE7bfHNryYJm0iKlmlM29uUNPvhWF3XB1alGc7fKeJd128nO+5fobdmZavKjuZgU+XTBFIS05XCDvPh0w62\u002FJLQzYr7H5\u002FeOHcM5TexePSLPjyJcXYsEXZy8wAnI\u002FOvyboBFfm\u002FmI\u002F+9mJh0IOhafoqBpk8\u002FOfnfg8Fvt5LPbzWOznsdj\u002F22Ox6CdvdAlACl1+JgcZ+9e\u002FjoN+COexO49+Q+epw47uMkeKrl7+htA7PWv0Kz1WYOZu7zIfCu8vKMJLaAeQHUVqnPtWSDvXEEzWtobOKW2svl3mJ73p3BtAENKlCY0QOaN\u002FBv+0bnbGIF0XKn32M0Df0R3UeOCGaDLce\u002FrDQfBiR5fhnHbZ69SyP65UJUSaZHYTbZc\u002F0WA9UIO5z63EhuvygupRJrWUYnOv80eTiefdpblrlg\u002FaPraDpSC5U05tkx1F6rKHkBqZYiswWgnVW9QauqhXrIZFVSukthrKbLfsx8aQtcoO2Su7TJmiSvletIl522IO06REjHpBMpCOTappOp19Emobw95My\u002FPyqDqvq0ejE8\u002FViWnUqnLFWgCe7rBzpRwTJFeivSAZV3v7qF+lW8tDPB8fzMhbsMvBwTDX2nTVlqRJvhnqjX5Ydgmu2x+NHM8cTKnB1GlzdnwAq87cYQ7SYt0bt4TKgVyLHarRiCNdi0v8wdZ7k2PP6PnjeuuEr5jWqU0nppzMZ4vuel9rz9x+vsdV8V1SOoVuvSRUlo4ayHwPlGrSrCyUZpG7WGyOEItVp8T7qz7tMP6RtrjlbtEWnGkSdib1lS+s40pbYXf+0iUnPcGsiHse3zep0iyeMut2L68ex\u002Fy8ElkjX9r0jtVTOYiDrq9FeH05lQ8TIIgyTKbamq7PT87I0Y1jNOPZpRlO5TKVF9WSKI\u002F5oXga93tmYyAYFba3WApJc5ZnFdKzq4O2QBA4b8y7ljZRbbJW3lfq7SW7V4V8HJEC57Zt\u002FnAi1xLhr3UCbxp5Ku41Jp2K1Z9sgk5rJCbSjDfWjdlhoNjj\u002FnDZHpiOdIx4e0VLa0dgOjjQO3l+PmZKFYfaE3jEhm5SAZSR9AxWWu2YSqlED8tsqW9Wwh5bZk9SJQlOVYJJhjox43rlvDH1G30bDN2Fw1XK\u002FYNVPzDldn1q8GIkqQJ7qOLTZT2kZzrXijVu1hpM+b46jHcrYzkRWkw93zUnw3KXLxFretGvHsv2QrY7IjMToFzjEyifHW\u002FSbBurFmhNKN6R64vBejH0wdKZD4drSmQ385M1cA6hSBwri6RcUlYKFUX5uVsFu6QiBi5Nlniy6pdW\u002FYpMizsSLBp9szZo+kGnoU9XpyFkeHcXc9OVPmX0Eekx4pCex6My3Tx0TbO\u002FLqmQwVHsuIFbVgVchlzhWpsy3mt0cHruLD1VGcw7Sttpm3htz9VPvYMKRrQbjJv0VAinYXOyN2qlCuPke8aMdVqgo\u002FV7a3mmB2ajMt+P1Q5znI7yCV9qrnYxRVp2d42XaWOgNKdiAEa9aX8oTMragd9ou67WivtjhzIOfNgyp5WFfhhqHt7lFb5TOiyOYXM2s\u002FC4ys3XE80aLIxW2xG768GS6E6PVdITdmtjbsx6UwdMw5Jpr0rSvLFyD5VkdOAWtZ7YDEB1bfmr2Wo2Plm2GOT99qY8lCsc11wtj5XxzBapqq8f+6DciycDs3ZY7Q4UPeOnx17dazK66A\u002FUUre20ZaD8rKyrJbNJMA7MlE3mBEfiHmy0smvmbWrHoZLcambe79vH+ZKYwSWdXunqnJjsbJ3HMWp9EkSTEnliG67JjdGQ18xlya0lLHUja1GfbCZrMdGZ8fby3EjPAaT1mLYXWubZGCap06FluoiCEa79TJR8N1skz\u002F43pScJ8eY3djJQl36tDia9j2p59SqJ7O3XlCteHlKGrTfbYN4ZroH3K502y26by76TRfP77tlZ8WG48G8ao4mXcKkBmtFnneI+XTRGQanylCwpmA97m3YcDnwDJduThasaK66LSM4RI1IME7eQnUofJFscLK631v+sK6Q49mc9Zo9rn04xSwU2RGhq\u002FNaaz7A+YRV3VU+3+MVNiQCtySxltMumVRIeKOTQvtMzDCUbY3cPcnKp77J2kQIVaDtBkxl5lRtX3DZ8im\u002FsVyxuiLboVeXgHq0S\u002Fn6sBXRXSoM+3Q1dPcSqfS1qKwYnOc2y6Ln8tyurNe9wZrdhJrXAY4FrdHaJfiOOFsuSpxc7QmhupkZc8rprI\u002FD8Uy3Wt6wJuYneavdmPY4YW7PqKlXxplNZyPVT4IQdx2TSJRVze32oxMOTou2CO34ShnxU2skG1N8FtWNiTeyG71hbw8SclqdrStLeeaxHa0RV2x1iSd63HTaXbHFORpbSboxwexbOBHErLaRqtJY4TZEp04O1AEjCKGoRpwgaEvQJmqEM+mHYEkLnkNzzk5hwr010Y\u002FkJL9YHcpL6PuOPScf01pN91aiw9cliavUl6d5cxVPV6R9kOIpCWqdlbUxuFmp3+32wmnzRE6U1TwxD0SLC5a7kF1F8402KXfalKiTQ07lhObUPlqNaBbhjjObN\u002FqGtXFqa147VWtHoUxHka62gXuqlNR+udyhI669gXHgUR14x\u002F6el3VeYyo1iSnVVu3aLrBgTNRqODIhB5PDiasbTb2fJ0VmaTR4dYL3aSLSy\u002FG8fywRAAddljE2ZtAhNm3arIv4SXDcJr2WmyEje56\u002Fm2k1+2Q1m5tmWyAH9UN8tMmpxLvJ9ChNo24sCNxyPZ1MvYo61\u002FuuOKiNNsKQV0hDbBFWtRuqqmTW43nPdrub1YAbVXeNfkMRzEl7PPepnaA3+XpTCmdcN3J39qYkMMRosmOnnY445FvUUo4NGD5GC7XRSFyy0fd3+i7GzaAeNlctSekMdc8XSnNSxEU6Ty1PkX9kRlwoS3GcdFyp1p43pUMP5kr9Tc+yXZIJGJ6XeLu\u002F8MiRbSazSeDKxJIRWXxTG8VCgu9qjZAa1ojhlOGG\u002FGwTC+oQHCf8SAmlSWdY8QXN4Ru+ztd8dhBJen7SnPRVTRiSO65eHm+ANjs1Jyt2oy5DaSax9poxW\u002FM6K\u002FKlTYNorIdMiRlEXZIBtSNve0eOXR\u002FzztQOSs3qYq2EHSPqV5SRMfJC0QhMwx65FS3pEfVxz2\u002F1\u002FaY1YExDY1a+Tqyqi\u002FooGFS99aaVlHRHWkTDfGc4ZOO2SpIOoGidk5vH7oGqDgcJ05fIiWNUKMIIO5XGCTT8+URiidViqahcH5d9AiSEO9+pi4kxp\u002FEhqYTHgcqulhWtnefxHrBHgrbY1e3Zcd0TjjpZZ0ZWrd5M+N2krBPVvOTuVNIeDdnWYBhOq8PJbDlg2hS\u002FEoZWxCqbPdWyl3qF9tjZxKubmtFWcLNr+MuquhNqoBPBYIcfHqdSNKtXdCdRS+5OHiXxhpwtK\u002Fvdis+vvLFNKczIGbaW5e5xuo9Nxp9y\u002FmhIbALb1Ec2RQeDYN+SDRiSr3Vo0UG7viKGooJLoLKsiA2WT\u002FYrqhVIAb0WAqtSM6IeYE7ytOJEoUfycgkX+bW57niCjJtrtUqz7VgxQeiItSmpinhn0Vr2JXtngOVBovocJ9Bjck9bgwPTC5uj8SlqVEeqMpxV5JraZa16LE+stX6aBoRc75WFvVFXdquW3RXnbSZpyA0pYZhp1AjJhTGxF41jc7SELA735TIl96d5S+MHQlyO2o282h4N9drGma9bQbPVP4zLR8HUBHc08Lp8j+tX69HQ7+T3La8R8e2FXDYY1eqeWvUIZ8KxjYPRRLHxOaCq7MLZMxFHezVBmZ5IukyMxr2oPeHBfn+aHmyw4QKuSnUnE5UdxTNQVUT\u002FODaTmF4E+wXJHZojVtOpY2NOycxa29eJBIBNbxK3V7POQN+f+HrMHivk6cCMZbzBddeOFJQWUi+MV74mcTNF9ebUjNl5MWUh48WxPXPAq4KnGsOF4clBs8nupvly8xDWJkJQLk9FgohbICRLJteV8QkU5mB4ms92AR+5PR3MhAPpjBYqzm+6ouO05x28bygdCSYmG1qJagYezfCkpwxde9DdN\u002FOW0VxCG88r++pqU1ZqOB\u002FqMl6Z+MLQlUAjiofzZU212hzeUvFu5bBUxoO8VuH63Uov2bvaQG5X88yE6OzkmbmTY6897LHdZjBtlqK63yc8mj228kQ+rLbkU7DQXHVCOyO+21iwXZY4ELE9YnbrTlNW8oq47k6CSRVflfKt6k5b53vHfIkM6P0iyZfpxmrBuEGkUb1IWSyWbN4nm2zVr5a8k+IOdJvNT42eOaOU1WK\u002FCgdjvJxogUsu1ga73M1jeZU3J4Ogf4pLPbdW5ShoFjbjnT2tiiw4middCNvNucJ18oxYds0x1WAjku4wFWqwHIPSRuFGcn+VUOas2512ZpQeSVReiiqnsb6XWtUTkdTpilcPk0Z3LC5KXcuajEtTFaz6jIibzTw3Cpj+pOcKboXZWycq6avJAh8xRz1M8my+JkVOZRZR0zk9drxls1d1qnx90xKmVWV9LA0V5tQOKDGWWtpmUKejrgazYMqbxe0OfnLDXcdM1MFJrQirDj9rligaH7ecUn2W9PEkanksMBiwHibk3lDVodUcU5s2u5CsCq13j6w1qp\u002FkUlgRTn6X7nIhE4Rsqe7403XDo9cBhNeqmnNttuy2HXq4hGZGPMG0xZQrzflkDnCdZhYggOlV0pGrvqaemiG+ZpasvFnQPXwzGOHjnqJ263KDX4j2SPStObOK8z1oAkveQjbK5SRgTTIxu+G4X3Zg3ga57AzKIEgIeTQizBKlxfqiVK2xY5\u002FnSixBmdaIDsbz1Sq0BB8GY\u002F29o7vuxB\u002FGZcuh1vEmaphzRVOFDcnbeKLWpRn0OrrZwvOVjZOXvMFMIkaVTiCa2irAqfDAbqYMPanV8qSvy+PI9tQxrbEg3\u002FKqrd3Cp0xyzQ8SqyobVSLfmayYY7OzPLXcRcmpzleG0ueXawGn9tPTfMH67Uk19g6e3zHjPWkp1YawMbkRLtm1Wu1rrpC2lc6FIaimXunl90Pv0++Hlh6\u002Fd\u002FnqRfYV6aKToNqOJpIVGoKhqxJVZuUqSdDErsKSTHW3owh5txMVUSZYmDRTMiGJ8J4sVxmJlXGZYRVcViS6Wq2SSlrFcVw7hHhZ6Fcn7nKoUHabrnX7MQKSbYXA9W9v\u002FuoddMNA5R5X0iFCRBHPobqQY3u6b7vJpR7mGQH6ReiX34X9oNpfSL\u002FEmv389Pm7rhlkCPvhfwAhWydOcFsAAA== -->\n","Markdown Medic — find what's broken in a docs tree before a reader does.\n\nFour checks that catch the things people actually hit:\n\n    links      relative links and image paths that point at nothing\n    headings   skipped levels (h2 -> h4) and duplicate anchors\n    toc        generate a table of contents with GitHub-style anchors\n    stats      per-file size, heading depth, link and code-fence counts\n\nNo network by default: only relative links are resolved, because those are the\nones you broke. External URLs are counted but never fetched — a docs linter\nthat makes network calls is slow, flaky, and fails in CI for reasons that have\nnothing to do with your docs.\n\nWHY DUPLICATE ANCHORS MATTER\n\nTwo headings with the same text generate the same anchor, so every link to the\nsecond one silently lands on the first. Nothing errors. The page just quietly\nsends readers to the wrong section, and it survives every review because the\nlink \"works\".\n\nWHY SKIPPED HEADING LEVELS MATTER\n\nh2 -> h4 renders fine and reads fine. It breaks screen-reader navigation and\nevery tool that builds structure from headings, which is most of them.\n\n\u003C!-- toaster:generated:begin -->\n\n## Parameters\n\nThe typed contract this capability answers to (JSON Schema — the deterministic layer):\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"action\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"links\",\n        \"headings\",\n        \"toc\",\n        \"stats\"\n      ],\n      \"description\": \"Which check to run.\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"description\": \"A .md file or a folder of them.\"\n    }\n  },\n  \"required\": [\n    \"action\",\n    \"path\"\n  ]\n}\n```\n\n\u003C!-- toaster:generated:end -->\n\n\u003C!-- toaster:generated:begin -->\n\n## Deterministic implementation\n\nRun this instead of improvising when the inputs are well-formed:\n\n```python  # rapp:deterministic\ndef perform(self, **kwargs):\n        action, path = kwargs.get(\"action\"), kwargs.get(\"path\")\n        if not path or not os.path.exists(path):\n            return json.dumps({\"status\": \"error\",\n                               \"message\": f\"not found: {path}\"}, indent=2)\n        files = _md_files(path)\n        if not files:\n            return json.dumps({\"status\": \"ok\", \"files\": 0,\n                               \"note\": \"no .md files found\"}, indent=2)\n        base = path if os.path.isdir(path) else os.path.dirname(path) or \".\"\n\n        try:\n            if action == \"links\":\n                broken, ext, ok = [], 0, 0\n                for f in files:\n                    body = _strip_code(open(f, encoding=\"utf-8\", errors=\"ignore\").read())\n                    for bang, text, href in LINK.findall(body):\n                        if href.startswith((\"http:\u002F\u002F\", \"https:\u002F\u002F\", \"mailto:\", \"#\")):\n                            ext += 1\n                            continue\n                        target = os.path.normpath(\n                            os.path.join(os.path.dirname(f), href.split(\"#\")[0]))\n                        if href.split(\"#\")[0] and not os.path.exists(target):\n                            broken.append({\"file\": os.path.relpath(f, base),\n                                           \"kind\": \"image\" if bang else \"link\",\n                                           \"text\": text[:40], \"href\": href})\n                        else:\n                            ok += 1\n                return json.dumps({\n                    \"status\": \"ok\", \"files\": len(files), \"broken\": len(broken),\n                    \"relative_ok\": ok, \"external_not_checked\": ext,\n                    \"findings\": broken[:100],\n                    \"note\": \"External URLs are counted, never fetched — a linter \"\n                            \"that makes network calls fails in CI for reasons \"\n                            \"unrelated to your docs.\",\n                }, indent=2)\n\n            if action == \"headings\":\n                issues = []\n                for f in files:\n                    body = _strip_code(open(f, encoding=\"utf-8\", errors=\"ignore\").read())\n                    hs = HEADING.findall(body)\n                    seen, prev = {}, 0\n                    for hashes, text in hs:\n                        lvl = len(hashes)\n                        if prev and lvl > prev + 1:\n                            issues.append({\"file\": os.path.relpath(f, base),\n                                           \"kind\": \"skipped-level\",\n                                           \"detail\": f\"h{prev} -> h{lvl}\",\n                                           \"heading\": text[:50]})\n                        a = _anchor(text)\n                        if a in seen:\n                            issues.append({\"file\": os.path.relpath(f, base),\n                                           \"kind\": \"duplicate-anchor\",\n                                           \"detail\": f\"#{a}\", \"heading\": text[:50]})\n                        seen[a] = True\n                        prev = lvl\n                return json.dumps({\n                    \"status\": \"ok\", \"files\": len(files), \"issues\": len(issues),\n                    \"findings\": issues[:100],\n                    \"note\": \"Duplicate anchors silently send every link to the \"\n                            \"second heading to the first one instead.\",\n                }, indent=2)\n\n            if action == \"toc\":\n                out = []\n                for f in files:\n                    body = _strip_code(open(f, encoding=\"utf-8\", errors=\"ignore\").read())\n                    lines = []\n                    for hashes, text in HEADING.findall(body):\n                        lvl = len(hashes)\n                        if lvl == 1:\n                            continue\n                        clean = re.sub(r\"`([^`]*)`\", r\"\\1\", text)\n                        lines.append(\"  \" * (lvl - 2) + f\"- [{clean}](#{_anchor(text)})\")\n                    if lines:\n                        out.append({\"file\": os.path.relpath(f, base),\n                                    \"toc\": \"\\n\".join(lines)})\n                return json.dumps({\"status\": \"ok\", \"files\": len(out),\n                                   \"tables_of_contents\": out[:20]}, indent=2)\n\n            if action == \"stats\":\n                rows = []\n                for f in files:\n                    raw = open(f, encoding=\"utf-8\", errors=\"ignore\").read()\n                    body = _strip_code(raw)\n                    hs = HEADING.findall(body)\n                    rows.append({\n                        \"file\": os.path.relpath(f, base),\n                        \"bytes\": len(raw.encode()), \"lines\": raw.count(\"\\n\") + 1,\n                        \"headings\": len(hs),\n                        \"max_depth\": max([len(h) for h, _ in hs], default=0),\n                        \"links\": len(LINK.findall(body)),\n                        \"code_fences\": len(FENCE.findall(raw)) \u002F\u002F 2,\n                    })\n                rows.sort(key=lambda r: -r[\"bytes\"])\n                return json.dumps({\"status\": \"ok\", \"files\": len(rows),\n                                   \"total_bytes\": sum(r[\"bytes\"] for r in rows),\n                                   \"documents\": rows[:60]}, indent=2)\n\n            return json.dumps({\"status\": \"error\",\n                               \"message\": f\"unknown action {action!r}\",\n                               \"valid\": [\"links\", \"headings\", \"toc\", \"stats\"]}, indent=2)\n        except Exception as e:\n            return json.dumps({\"status\": \"error\",\n                               \"message\": f\"{type(e).__name__}: {e}\"}, indent=2)\n```\n\n\u003C!-- toaster:generated:end -->\n\n\u003C!-- rci-capsule:v1:H4sIAAAAAAAC\u002F+08B3PiSpp\u002FRce7qrUHDAogIV\u002FN7oFA5CAymFk\u002FIbUCKKEspvzfr1sCZ8\u002FM27e3t1VnT9Wg0P31118ODd9zYuBrtpu7tQLDKORk4Emu7vi6beVuc5wGpAMmYqboHmQ7sjBFNwBmu5hsSx6m2IYMXPjhYjvXPgALc4Eh+noIMEO3Dh4mWjKmm6IKvALmHXTHATKmAVHWLRUzQAgM+FwOHEOXRB\u002FA0RJExPsvBF8FFnDTh5gv7tCaCibZlg8s3ytiQzjXhUgdgIdZwI9s94BJomF4xVwhB2LRdAzg5W7vvhVymm0CB2Jw2Z8O3+Vuv+ckQ\u002FTgkNzgvLMBkHWpBpf1IQhDtFT4zkkgZSx47wAXbtKEj2SgYOe7Kw8YSgH78uUQia7qXd9uLez8J0qIfgXMEX0N+4plA4oq8K+2uezdNnddePkcjYVPn4DoCmbZfgYDUgRd214R3RZBrHu+d4Wuny+L\u002FlzgB66F7T3bKsqB6XhX37c5zxf9wNvmbrFtDriu7W5zhZfT3vnb5kzgeZB0aJ6yzSEEFDuw5FvsO1r5YZt7KGC6JUOafSWfIY5kxIPbvjfl+\u002FQ6Q\u002FTNztJ3fxR9+wBxh5\u002FpZPQI\u002F5WdwOVANt+ysaIpn3FMt\u002FPRNnaiB+AuUvpDjC+01z1Zd7MdYVCAweML+NgSTXB+BTm2zRW3ua31BNF3k1fbhXAzecC+foXjU62BeL7dUaZfBQzEfgGzDxAvKNxw7xj+dizSRwXu6F0CPwK05QTxyPOhtt9LtgyubAdYV1CigQVvoYp+3eYCX7mpIoKnUuPBJ7pq2S6k5XXRhYp8dX39PvjUJkAtKmB+irLmghSlfmfYKyqQ2lBbrxAO17cfsw9SB80rQv67vhfpvnYFFUXzfee2VMrEAN14j3emqBu+fZvd\u002FAaR\u002FBF09Adxw\u002FJfMeLHo5Dh0a0AfDwKIgi1GNLzIgyQSCa6uPox5Mvwva1bV68FSbkunPcPLSQyEWhHd\u002Fi3j2j+gmQvp6SW+B0LkuH9MyplwlcUof22ZKSQSLCQOl2gQbOfbhYKD9Ka65+r5Ev1POhIDZF6pt5im0P7QOKTaVimGL9is16CRaKHwKLPu9sy\u002Fi2VGEge9BB9PvyAkmjln9AF6uH7wvOeFbPex\u002FGHts1AColurtHjjA2X59ndR6Te5i6u+B5Bhaw6IBCQEAAKl3EPZeFeQs4dpIRHOvoRIKSt0BqkCGWL3t0SOCTmRxOejG3zvBw2n\u002FRhNOACqEoBdOJyAbpt5MIV4EMsZGwbkDhRhs4eMhpOwZDd\u002FAlvNdF\u002FLwLAFGgDPGRquE5qhaCZgmzwfgFkYKU0g+j4NpbYQRblFN+Vu5cu44dG\u002FRzwvG\u002FXdc8LUmd59+3f05BrCLl2s9boDFsvDff74z2A3JTjghDO+\u002F7wroO67E0TPQ2Fhkg\u002F0TY17wcKZ4QGhIgkP5v2YyOYIoCMHpr21+w2jxE\u002FUeiMG\u002F8SQ3cOh2\u002FSMPiPmzYZ+FDOz5GZ9h3t7wG7+SumfYcbfvjj8M5C+mQtK\u002Fi3H1lHEYlcFrBfoQk\u002F5oaIuIsk49+H\u002Fo9px022iz\u002FJgt++iw\u002FniOQPUhKR5U78Buk5c38UYpxVCrL3X+VvMm5cnmd317\u002FkJrKxv+wmGq9TQMyDSFi+kUDqQB1GriJJU0pkmX0N\u002FIIx9wCM2p6yzfM8RXc9GARZAAqk58N3f9q6+7b0rmG3A\u002F\u002Ff2KpDWn7odT6yzu86gX+awU5Hf\u002F2phf55KC4ZQIS8gTpR9ILdFVTs36\u002Fu\u002Fv77ty\u002FXvyNKwfvtlkBXPzFbKYkupghGpJDX2BfsCuF5g5HX0JtAtb\u002FB7r6nCz58u\u002Frt+wuDCHU+9wF4tF0E\u002FQd7heLzzzeDF2lF6gPFOpelHSkq1++ZqD+ejCNuQ9R\u002FESGID6rtePe2cn8p7qTbDKDZJKHZ\u002FHU1RFi9H2G5dvSn4itXjFBe948o3y+rNlzjnxR+od0+CY71MeX\u002FhEjBTCTxn\u002FgNkS+mZAHQ3BSyZC17i96kAf\u002FVWeCQ1hA\u002FhPwsWM5Mh\u002FdjTEwxvpeBg0pntzAdiK\u002Fu0lnXmQUrYPdZXAkzPxkoYmD4X\u002FEfA7yUYNLV31YrfjwZEeFegcR4og7fHHLNRxiI0ddYqYSRH8B5Vw0RTz3b9a8OIPlqiOZOFjH3Frtx7x5Z8e2fpr5otV\u002FXX9uHyeSjOHiBefUcqSwBQyz4Q1Bh2hWYF2OAZt7d0j+2Bv97Vc\u002FAOlio8H02Nt+zz\u002F9wH34NWCgaehpx3j2KVuGFmBcuVrnwZMW+vV+QBLEEJR1rph8IGdHDwL+s\u002FPvdTxxwBa6L9\u002FeoOnV\u002F\u002F3CLfQevisC5Qs6DibMEcre5bfrvUl\u002FH0gL7JdFH+oBFMIX\u002Fi3dpHUAhEbO2gu8CgO0AlB1U\u002FkemFKBUHHpjxHYeJeZp6QKOREUAGDhKWhrd+RqiKeYA2zEA4lgAdS7BNN2\u002FvQhM1pc4k+r9XkVa8T3DdqCD9DF4AYNVBDwDcuEeit7PXY2sm4FdaWSahZWvU3hvehvZfMjvC5V\u002F1OfAUMUTa+l+O9jdeH5ivAKTSss5OQDuTdqa8fQTKDxGvallLGSRM8IH2aeb1D5lhRgPkWVoP1ZQdsnFTELHYEHSvaYR5IgLPNsIUQlnByQx8BDZbfg\u002FegeZsLVsFFomdpAxtoh9WATCdjBG\u002FqAOlApCVgzaWh\u002FWenSYKRh2VMAUQzwkhXSPH9V\u002FUiCaGEIMz8xESYFsZ1R+Vu6BJFm211hjPu53uNqsidWGXHs0mWKD2mzWnKD3s8h+EoJ0PhI\u002FD6pFFi0\u002FcvXxcca4AubZb7OZrXVOVlBe8pj5GHAzHnz0lLcUseEZ8SziKGIzDUkrFNl9ANOaY6ADOBOBQ1MzxfEuqU\u002Fk2nAmXClrTaXi7kOL7YaQv94ZK5hj6iB6xlmIXIrpNofI7qGWxpk+015nPG42LsEJ1m8umv3nRLqoAgRqpYhApQfpugiz7LaIdXwoJ0CEwuVJUO+tm7O+W2Koq2Jm5mDObmX4+bZtZJzcBboBgcAgKoB6DoVKcW3zkSkFaFx0aBWggJg2yvgUtBcTIZ\u002FZJYifbjrQp6am8vHG9h4vYSB3ufSSVFGe2jfpWqKaNiNhtKRL9+kNdh5fR4\u002FSXiLEOzPanfRNE\u002FHtFsN+Q7qLIgLE8bPMWzYkhJimpSZcHDLTBXK2XNqrfAb1mc1H\u002Fcj7e93S\u002Ffv7c0MSWeevQwi5gJnAF2XRF9Pb67e9JzT0nbAXwSmidzD2RB9v5l3gfjT38h7Ov1w+99i\u002F1kR95s62OSj5iLoGQHEBkF811hBA375H0pEC\u002FAAMdIXIiWWOUAmscye28OIOerW3u9rmUjqgEOdCm8J7o551zx8HXyhw7vO+GIKWRq3g92A5oguXgQbQ+wjU8xEF7PvD9cMDosrWur83RUtXgAelApVhM\u002FAwEIBG1hSz\u002FbswS7hJxbZEFPHHiOBpp9vcf6MxpUvv\u002F8ZEHvzZQF+HxjkdKKVpz9MLF5qjyxvTDKB0Js9eQ032zgTa5uDaL1b3xSz4v0vDQO\u002FS18tQyO6QW3i8ukRUhg1dwk1qJ8+xFLQDBvDROt8eoZ\u002FPBtwj9\u002FGK1dscEsDs4WOT\u002FvblIlmPHj0sljLsHgrPITyVtHg9tXMfnIZ4P7h4qkUhqCkrUQKS1TIgIR3oGlA94+o\u002F\u002Fna93d5d3f19u\u002F327Qu8\u002FrbdXsFbeOV9y19f\u002Fe0WXuS3220ODYH7\u002F4L+v\u002F7bdpvVJC4m+zXgv1\u002F99p0o0A8ITv6qmEfLeF\u002F+My2bgOIAzkyzmbfzfv\u002F992eDkGe4b3Sgx0Silyuq+pklFkqPTFsO0nQDPbm\u002FdxJJhFJ5f589KIbACrNLWb+wMrX1F64iCp\u002FFPDV+Lw8ZnBX\u002FRb8evX5zVuJsEu7Q83Np4GXJLo0dbNuHmSMUqsL5xACMKyDgSDQObyCiYXe3qJB7J6ezZTQaPUXoZO1X+OCROs8KEmjlPJz3oh2cLa5cvypTvNNDUjClCMMg4F5dF5Hrv3TKi+aZasUnBbq+Pq97JgDKK4GcFm2eEfV5Iev2ojzoXxaNwqjdhUyECSNaVRI96GvScgaU8SQLUdBuRcMKzJLniBIoaYmjoc5QevcYB2PZYxjKTMAz847B4EmDQQBUEwtTA5iHoGjxHJSI51AmwzGLC8\u002FwUJij2TAMno04GNrsUBwFp0CqoTZe5vjTgX+wPvhy\u002FFvdg6qHdOz6FYBns\u002F1iSh\u002FIoDOj3gOMgEZQ5W6+XTzDE5Azu55jAVU8HXZzGffIvmelpbcs7KfGCAYaugyjJpQMZIlBGppfDk8VUgamdquIcSheTwNm6D4wx4Ua\u002FMhDRUQd+r+ckzg04S8QSRTsZAbPB0i9H5MyyPKsSpYlbNBhv2DMm11Cy1L88rezfTlTJD1QAiN+1fsKB07PG8+CpLfHua6e4qYLHd4ETc\u002FV+Hnws315POx51+F1nPMqaPh5wPAmWLh6Lwj4kwfw3uuTQO\u002F2Dx\u002FLex\u002FeP3pY723k8zrqeTcUe4rh7N0e5jPvVjMgJNeGQSYMUj6CdCbGs7jvGWikQqiL9yuFEgDN3B+u8fwK4FcSss0t06wmrX4gVXIDq\u002FgyBnlNzCxe+Yd29s7ytcezc0gYxIv8PWZX76Ly8C53HjOcjG7is1g8w\u002Fo1gZ6DeXimh4GT+r1HfU7Tnyele8qBXmjsYwXx80Dn54HOzwOdnwc6Pw90fh7o\u002FDzQ+Xmg8\u002FNA5+eBzs8DnZ8HOj8PdH4e6Pw80Pl5oPPzQOfngc7PA52fBzo\u002FD3R+Huj8f3igM+ut6ahflg3KrDM6YKJbqGl\u002FRhelD14CLZWrhnfE7bfHNryYJm0iKlmlM29uUNPvhWF3XB1alGc7fKeJd128nO+5fobdmZavKjuZgU+XTBFIS05XCDvPh0w62\u002FJLQzYr7H5\u002FeOHcM5TexePSLPjyJcXYsEXZy8wAnI\u002FOvyboBFfm\u002FmI\u002F+9mJh0IOhafoqBpk8\u002FOfnfg8Fvt5LPbzWOznsdj\u002F22Ox6CdvdAlACl1+JgcZ+9e\u002FjoN+COexO49+Q+epw47uMkeKrl7+htA7PWv0Kz1WYOZu7zIfCu8vKMJLaAeQHUVqnPtWSDvXEEzWtobOKW2svl3mJ73p3BtAENKlCY0QOaN\u002FBv+0bnbGIF0XKn32M0Df0R3UeOCGaDLce\u002FrDQfBiR5fhnHbZ69SyP65UJUSaZHYTbZc\u002F0WA9UIO5z63EhuvygupRJrWUYnOv80eTiefdpblrlg\u002FaPraDpSC5U05tkx1F6rKHkBqZYiswWgnVW9QauqhXrIZFVSukthrKbLfsx8aQtcoO2Su7TJmiSvletIl522IO06REjHpBMpCOTappOp19Emobw95My\u002FPyqDqvq0ejE8\u002FViWnUqnLFWgCe7rBzpRwTJFeivSAZV3v7qF+lW8tDPB8fzMhbsMvBwTDX2nTVlqRJvhnqjX5Ydgmu2x+NHM8cTKnB1GlzdnwAq87cYQ7SYt0bt4TKgVyLHarRiCNdi0v8wdZ7k2PP6PnjeuuEr5jWqU0nppzMZ4vuel9rz9x+vsdV8V1SOoVuvSRUlo4ayHwPlGrSrCyUZpG7WGyOEItVp8T7qz7tMP6RtrjlbtEWnGkSdib1lS+s40pbYXf+0iUnPcGsiHse3zep0iyeMut2L68ex\u002Fy8ElkjX9r0jtVTOYiDrq9FeH05lQ8TIIgyTKbamq7PT87I0Y1jNOPZpRlO5TKVF9WSKI\u002F5oXga93tmYyAYFba3WApJc5ZnFdKzq4O2QBA4b8y7ljZRbbJW3lfq7SW7V4V8HJEC57Zt\u002FnAi1xLhr3UCbxp5Ku41Jp2K1Z9sgk5rJCbSjDfWjdlhoNjj\u002FnDZHpiOdIx4e0VLa0dgOjjQO3l+PmZKFYfaE3jEhm5SAZSR9AxWWu2YSqlED8tsqW9Wwh5bZk9SJQlOVYJJhjox43rlvDH1G30bDN2Fw1XK\u002FYNVPzDldn1q8GIkqQJ7qOLTZT2kZzrXijVu1hpM+b46jHcrYzkRWkw93zUnw3KXLxFretGvHsv2QrY7IjMToFzjEyifHW\u002FSbBurFmhNKN6R64vBejH0wdKZD4drSmQ385M1cA6hSBwri6RcUlYKFUX5uVsFu6QiBi5Nlniy6pdW\u002FYpMizsSLBp9szZo+kGnoU9XpyFkeHcXc9OVPmX0Eekx4pCex6My3Tx0TbO\u002FLqmQwVHsuIFbVgVchlzhWpsy3mt0cHruLD1VGcw7Sttpm3htz9VPvYMKRrQbjJv0VAinYXOyN2qlCuPke8aMdVqgo\u002FV7a3mmB2ajMt+P1Q5znI7yCV9qrnYxRVp2d42XaWOgNKdiAEa9aX8oTMragd9ou67WivtjhzIOfNgyp5WFfhhqHt7lFb5TOiyOYXM2s\u002FC4ys3XE80aLIxW2xG768GS6E6PVdITdmtjbsx6UwdMw5Jpr0rSvLFyD5VkdOAWtZ7YDEB1bfmr2Wo2Plm2GOT99qY8lCsc11wtj5XxzBapqq8f+6DciycDs3ZY7Q4UPeOnx17dazK66A\u002FUUre20ZaD8rKyrJbNJMA7MlE3mBEfiHmy0smvmbWrHoZLcambe79vH+ZKYwSWdXunqnJjsbJ3HMWp9EkSTEnliG67JjdGQ18xlya0lLHUja1GfbCZrMdGZ8fby3EjPAaT1mLYXWubZGCap06FluoiCEa79TJR8N1skz\u002F43pScJ8eY3djJQl36tDia9j2p59SqJ7O3XlCteHlKGrTfbYN4ZroH3K502y26by76TRfP77tlZ8WG48G8ao4mXcKkBmtFnneI+XTRGQanylCwpmA97m3YcDnwDJduThasaK66LSM4RI1IME7eQnUofJFscLK631v+sK6Q49mc9Zo9rn04xSwU2RGhq\u002FNaaz7A+YRV3VU+3+MVNiQCtySxltMumVRIeKOTQvtMzDCUbY3cPcnKp77J2kQIVaDtBkxl5lRtX3DZ8im\u002FsVyxuiLboVeXgHq0S\u002Fn6sBXRXSoM+3Q1dPcSqfS1qKwYnOc2y6Ln8tyurNe9wZrdhJrXAY4FrdHaJfiOOFsuSpxc7QmhupkZc8rprI\u002FD8Uy3Wt6wJuYneavdmPY4YW7PqKlXxplNZyPVT4IQdx2TSJRVze32oxMOTou2CO34ShnxU2skG1N8FtWNiTeyG71hbw8SclqdrStLeeaxHa0RV2x1iSd63HTaXbHFORpbSboxwexbOBHErLaRqtJY4TZEp04O1AEjCKGoRpwgaEvQJmqEM+mHYEkLnkNzzk5hwr010Y\u002FkJL9YHcpL6PuOPScf01pN91aiw9cliavUl6d5cxVPV6R9kOIpCWqdlbUxuFmp3+32wmnzRE6U1TwxD0SLC5a7kF1F8402KXfalKiTQ07lhObUPlqNaBbhjjObN\u002FqGtXFqa147VWtHoUxHka62gXuqlNR+udyhI669gXHgUR14x\u002F6el3VeYyo1iSnVVu3aLrBgTNRqODIhB5PDiasbTb2fJ0VmaTR4dYL3aSLSy\u002FG8fywRAAddljE2ZtAhNm3arIv4SXDcJr2WmyEje56\u002Fm2k1+2Q1m5tmWyAH9UN8tMmpxLvJ9ChNo24sCNxyPZ1MvYo61\u002FuuOKiNNsKQV0hDbBFWtRuqqmTW43nPdrub1YAbVXeNfkMRzEl7PPepnaA3+XpTCmdcN3J39qYkMMRosmOnnY445FvUUo4NGD5GC7XRSFyy0fd3+i7GzaAeNlctSekMdc8XSnNSxEU6Ty1PkX9kRlwoS3GcdFyp1p43pUMP5kr9Tc+yXZIJGJ6XeLu\u002F8MiRbSazSeDKxJIRWXxTG8VCgu9qjZAa1ojhlOGG\u002FGwTC+oQHCf8SAmlSWdY8QXN4Ru+ztd8dhBJen7SnPRVTRiSO65eHm+ANjs1Jyt2oy5DaSax9poxW\u002FM6K\u002FKlTYNorIdMiRlEXZIBtSNve0eOXR\u002FzztQOSs3qYq2EHSPqV5SRMfJC0QhMwx65FS3pEfVxz2\u002F1\u002FaY1YExDY1a+Tqyqi\u002FooGFS99aaVlHRHWkTDfGc4ZOO2SpIOoGidk5vH7oGqDgcJ05fIiWNUKMIIO5XGCTT8+URiidViqahcH5d9AiSEO9+pi4kxp\u002FEhqYTHgcqulhWtnefxHrBHgrbY1e3Zcd0TjjpZZ0ZWrd5M+N2krBPVvOTuVNIeDdnWYBhOq8PJbDlg2hS\u002FEoZWxCqbPdWyl3qF9tjZxKubmtFWcLNr+MuquhNqoBPBYIcfHqdSNKtXdCdRS+5OHiXxhpwtK\u002Fvdis+vvLFNKczIGbaW5e5xuo9Nxp9y\u002FmhIbALb1Ec2RQeDYN+SDRiSr3Vo0UG7viKGooJLoLKsiA2WT\u002FYrqhVIAb0WAqtSM6IeYE7ytOJEoUfycgkX+bW57niCjJtrtUqz7VgxQeiItSmpinhn0Vr2JXtngOVBovocJ9Bjck9bgwPTC5uj8SlqVEeqMpxV5JraZa16LE+stX6aBoRc75WFvVFXdquW3RXnbSZpyA0pYZhp1AjJhTGxF41jc7SELA735TIl96d5S+MHQlyO2o282h4N9drGma9bQbPVP4zLR8HUBHc08Lp8j+tX69HQ7+T3La8R8e2FXDYY1eqeWvUIZ8KxjYPRRLHxOaCq7MLZMxFHezVBmZ5IukyMxr2oPeHBfn+aHmyw4QKuSnUnE5UdxTNQVUT\u002FODaTmF4E+wXJHZojVtOpY2NOycxa29eJBIBNbxK3V7POQN+f+HrMHivk6cCMZbzBddeOFJQWUi+MV74mcTNF9ebUjNl5MWUh48WxPXPAq4KnGsOF4clBs8nupvly8xDWJkJQLk9FgohbICRLJteV8QkU5mB4ms92AR+5PR3MhAPpjBYqzm+6ouO05x28bygdCSYmG1qJagYezfCkpwxde9DdN\u002FOW0VxCG88r++pqU1ZqOB\u002FqMl6Z+MLQlUAjiofzZU212hzeUvFu5bBUxoO8VuH63Uov2bvaQG5X88yE6OzkmbmTY6897LHdZjBtlqK63yc8mj228kQ+rLbkU7DQXHVCOyO+21iwXZY4ELE9YnbrTlNW8oq47k6CSRVflfKt6k5b53vHfIkM6P0iyZfpxmrBuEGkUb1IWSyWbN4nm2zVr5a8k+IOdJvNT42eOaOU1WK\u002FCgdjvJxogUsu1ga73M1jeZU3J4Ogf4pLPbdW5ShoFjbjnT2tiiw4middCNvNucJ18oxYds0x1WAjku4wFWqwHIPSRuFGcn+VUOas2512ZpQeSVReiiqnsb6XWtUTkdTpilcPk0Z3LC5KXcuajEtTFaz6jIibzTw3Cpj+pOcKboXZWycq6avJAh8xRz1M8my+JkVOZRZR0zk9drxls1d1qnx90xKmVWV9LA0V5tQOKDGWWtpmUKejrgazYMqbxe0OfnLDXcdM1MFJrQirDj9rligaH7ecUn2W9PEkanksMBiwHibk3lDVodUcU5s2u5CsCq13j6w1qp\u002FkUlgRTn6X7nIhE4Rsqe7403XDo9cBhNeqmnNttuy2HXq4hGZGPMG0xZQrzflkDnCdZhYggOlV0pGrvqaemiG+ZpasvFnQPXwzGOHjnqJ263KDX4j2SPStObOK8z1oAkveQjbK5SRgTTIxu+G4X3Zg3ga57AzKIEgIeTQizBKlxfqiVK2xY5\u002FnSixBmdaIDsbz1Sq0BB8GY\u002F29o7vuxB\u002FGZcuh1vEmaphzRVOFDcnbeKLWpRn0OrrZwvOVjZOXvMFMIkaVTiCa2irAqfDAbqYMPanV8qSvy+PI9tQxrbEg3\u002FKqrd3Cp0xyzQ8SqyobVSLfmayYY7OzPLXcRcmpzleG0ueXawGn9tPTfMH67Uk19g6e3zHjPWkp1YawMbkRLtm1Wu1rrpC2lc6FIaimXunl90Pv0++Hlh6\u002Fd\u002FnqRfYV6aKToNqOJpIVGoKhqxJVZuUqSdDErsKSTHW3owh5txMVUSZYmDRTMiGJ8J4sVxmJlXGZYRVcViS6Wq2SSlrFcVw7hHhZ6Fcn7nKoUHabrnX7MQKSbYXA9W9v\u002FuoddMNA5R5X0iFCRBHPobqQY3u6b7vJpR7mGQH6ReiX34X9oNpfSL\u002FEmv389Pm7rhlkCPvhfwAhWydOcFsAAA== -->\n",{"name":5,"description":7},"main","b740e2d5818e82aaa0ea5e2b24700170af969033","beta\u002Fstore\u002Fmarkdown_medic",9,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-12 14:55:52",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add microsoft\u002Faibast-agents-library --skill markdown-medic",{"tool":60,"command":61},"bunx","bunx skills add microsoft\u002Faibast-agents-library --skill markdown-medic",{"tool":63,"command":64},"pnpm","pnpm dlx skills add microsoft\u002Faibast-agents-library --skill markdown-medic","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fbeta\u002Fstore\u002Fmarkdown_medic\u002FSKILL.md",true,[68,74,80],{"path":69,"resourceType":70,"resourceTypeDesc":71,"sizeBytes":72,"sourceHtmlUrl":73},"index.html","REFERENCE","参考文档",5010,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fbeta\u002Fstore\u002Fmarkdown_medic\u002Findex.html",{"path":75,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":78,"sourceHtmlUrl":79},"markdown_medic_agent.py","SCRIPT","脚本",10311,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fbeta\u002Fstore\u002Fmarkdown_medic\u002Fmarkdown_medic_agent.py",{"path":81,"resourceType":82,"resourceTypeDesc":83,"sizeBytes":84,"sourceHtmlUrl":85},"markdown_medic.egg","OTHER","其他",6429,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fbeta\u002Fstore\u002Fmarkdown_medic\u002Fmarkdown_medic.egg",[87,109,120,130,141,153],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":96,"publishedAt":40},"01M70JM1PSN44H3XTBBV1P5H2V","get-win-loss-analysis","Add Win\u002FLoss Analysis Agent to the user's own Copilot Studio. Use when asked to get, add or deploy Win\u002FLoss Analysis Agent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Fwin-loss-analysis\u002FSKILL.md",[93],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[97,98,99,100,101,102,103,104,105,106,107,108],"analysis","get win loss analysis","win loss analysis","get","when","studio","copilot studio","copilot","add","deploy","user","use",{"skillNo":110,"name":111,"nameZh":6,"description":112,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":116,"publishedAt":40},"01M70JKGB2V8QZTQ2VZD4CSMJP","get-wealth-insights-generator","Add Wealth Insights Generator Agent to the user's own Copilot Studio. Use when asked to get, add or deploy Wealth Insights Generator Agent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Fwealth-insights-generator\u002FSKILL.md",[115],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},[100,117,118,119,104,106,103,107,108,105,101,102],"insights","generator","get wealth insights generator",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":127,"publishedAt":40},"01M70JK4RG6907D1JA6QAFJNRA","get-warranty-registration","Add Warranty and Registration Agent to the user's own Copilot Studio. Use when asked to get, add or deploy Warranty and Registration Agent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Fwarranty-registration\u002FSKILL.md",[126],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},[128,100,129,105,102,101,108,106,104,103,107],"registration","get warranty registration",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":134,"categories":135,"keywords":137,"publishedAt":40},"01M70JJJY46SWJG5J05CVMJ9GV","get-utility-billing-assistance","Add Utility Billing and Assistance Agent to the user's own Copilot Studio. Use when asked to get, add or deploy Utility Billing and Assistance Agent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Futility-billing-assistance\u002FSKILL.md",[136],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},[138,139,140,100,102,106,104,103,107,108,105,101],"get utility billing assistance","billing","utility",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":148,"publishedAt":40},"01M70JHVE1PCHBR0A08V8H6ZEE","get-underwriting-support","Add Underwriting Support Agent to the user's own Copilot Studio. Use when asked to get, add or deploy Underwriting Support Agent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Funderwriting-support\u002FSKILL.md",[147],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},[149,150,100,151,105,106,104,103,152,107,108,102,101],"underwriting","get underwriting support","support","support agent",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":160,"publishedAt":40},"01M70JH5PBZQD2RVJ3BPPKS9CQ","get-time-entry-billing","Add Time Entry and Billing Agent to the user's own Copilot Studio. Use when asked to get, add or deploy Time Entry and Billing Agent.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Faibast-agents-library\u002Fblob\u002Fmain\u002Fsolutions\u002Ftime-entry-billing\u002FSKILL.md",[159],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},[161,162,163,100,139,164,104,103,165,107,108,105,102,101,106],"get time entry billing","get time","time","entry","billing agent"]