feat: Add mermaid-fixer skill for automatic Mermaid syntax error correction
- Add skill that uses check-mermaid.js to validate Mermaid diagrams - Skill guides Claude to parse error reports and apply intelligent fixes - Fixes common Mermaid parser bugs (parentheses, brackets, braces in labels) - Include demo scripts with test Markdown file for validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
150
skills/mermaid-fixer/SKILL.md
Normal file
150
skills/mermaid-fixer/SKILL.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
name: mermaid-fixer
|
||||
description: Fix Mermaid diagram syntax errors in Markdown files. Use when: (1) Mermaid diagrams aren't rendering or show parse errors, (2) User mentions "mermaid syntax error", "mermaid not working", "diagram broken", (3) CI/CD fails on Mermaid validation, (4) Documentation contains Mermaid code blocks that need fixing.
|
||||
---
|
||||
|
||||
# Mermaid Diagram Fixer
|
||||
|
||||
Automatically detects and fixes Mermaid diagram syntax errors in Markdown files using the `check-mermaid.js` validation script.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Run `check-mermaid.js`** to get detailed error reports
|
||||
2. **Parse error information** (line numbers, error types, code snippets)
|
||||
3. **Apply intelligent fixes** based on the specific error
|
||||
4. **Verify** by running the check again
|
||||
|
||||
## Core Script
|
||||
|
||||
The `scripts/check-mermaid.js` script is the heart of this skill. It:
|
||||
- Scans Markdown files for ```` ```mermaid ```` code blocks
|
||||
- Validates each block using the Mermaid CLI (`mmdc`)
|
||||
- Returns detailed error reports with line numbers and error messages
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Run the Check Script
|
||||
|
||||
```bash
|
||||
node scripts/check-mermaid.js <path/to/file.md>
|
||||
```
|
||||
|
||||
**Expected output format:**
|
||||
|
||||
- **No errors**: Returns exit code 0 with success message
|
||||
- **Has errors**: Returns exit code 1 with structured error report:
|
||||
|
||||
```markdown
|
||||
## 🚨 Mermaid 语法检查报告
|
||||
|
||||
**检查文件:** `example.md`
|
||||
**检查结果:** ❌ 发现 2 处语法错误 (共检测到 5 个代码块)
|
||||
|
||||
---
|
||||
|
||||
### ❌ 错误 #1 (代码块 #2)
|
||||
|
||||
- **文档位置:** 第 `51` 行至第 `86` 行
|
||||
|
||||
#### 核心错误详情
|
||||
\`\`\`text
|
||||
Error: Parse error on line 13: ...
|
||||
Expecting 'SQE', 'DOUBLECIRCLEEND', ... got 'PS'
|
||||
\`\`\`
|
||||
|
||||
#### 代码内容片段
|
||||
\`\`\`text
|
||||
[shows the problematic code]
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### Step 2: Parse and Understand Errors
|
||||
|
||||
Key information from each error:
|
||||
- **文档位置**: Which line numbers contain the error
|
||||
- **核心错误详情**: The parser error message
|
||||
- **代码内容片段**: The actual Mermaid code causing the issue
|
||||
|
||||
### Step 3: Apply Fixes Based on Error Type
|
||||
|
||||
#### Error Type: "got 'PS'" (Parentheses Issue)
|
||||
|
||||
**Cause**: Parentheses `()` in node labels or arrow labels within subgraphs
|
||||
|
||||
**Fix**: Quote the label
|
||||
```mermaid
|
||||
# Before (causes error)
|
||||
NodeA[Label (with parens)]
|
||||
-->|Label (parens)| NodeB
|
||||
|
||||
# After (fixed)
|
||||
NodeA["Label (with parens)"]
|
||||
-->"|Label (parens)|" NodeB
|
||||
```
|
||||
|
||||
#### Error Type: "got 'SQS'" (Square Brackets Issue)
|
||||
|
||||
**Cause**: Square brackets `[]` in node labels
|
||||
|
||||
**Fix**: Quote the label
|
||||
```mermaid
|
||||
# Before
|
||||
NodeA[Result[]<br/>text]
|
||||
|
||||
# After
|
||||
NodeA["Result[]<br/>text"]
|
||||
```
|
||||
|
||||
#### Error Type: "got 'DIAMOND_START'" or similar
|
||||
|
||||
**Cause**: Curly braces `{}` or other special characters in labels
|
||||
|
||||
**Fix**: Quote the label
|
||||
```mermaid
|
||||
# Before
|
||||
NodeA[Label {variable}]
|
||||
|
||||
# After
|
||||
NodeA["Label {variable}"]
|
||||
```
|
||||
|
||||
### Step 4: Apply the Fix to the File
|
||||
|
||||
1. Read the Markdown file
|
||||
2. Locate the problematic code block using the reported line numbers
|
||||
3. Apply the appropriate fix (quote labels with special characters)
|
||||
4. Save the file
|
||||
|
||||
### Step 5: Verify
|
||||
|
||||
Run `check-mermaid.js` again to confirm all errors are resolved.
|
||||
|
||||
## Common Fix Patterns
|
||||
|
||||
| Error Pattern | Fix Strategy |
|
||||
|---------------|--------------|
|
||||
| `NodeID[label (text)]` | Change to `NodeID["label (text)"]` |
|
||||
| `-->|label (text)|` | Change to `-->|"label (text)"\|` |
|
||||
| `NodeID[label[]text]` | Change to `NodeID["label[]text"]` |
|
||||
| `NodeID[label{text}]` | Change to `NodeID["label{text}"]` |
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Always quote labels** containing: `()`, `[]`, `{}`, `<`, `>`, `#`, or Chinese characters
|
||||
2. **Preserve the original structure** - only modify the problematic labels
|
||||
3. **Check all reported errors** - don't stop after fixing just one
|
||||
4. **Re-verify** after each fix round
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
User: "My Mermaid diagrams in docs/architecture.md aren't rendering"
|
||||
|
||||
Claude:
|
||||
1. Run: node scripts/check-mermaid.js docs/architecture.md
|
||||
2. Parse error output
|
||||
3. Identify errors (e.g., "got 'PS'" on line 45)
|
||||
4. Read file, locate line 45, find the Mermaid block
|
||||
5. Apply fix: quote labels with parentheses
|
||||
6. Save and verify
|
||||
```
|
||||
23
skills/mermaid-fixer/evals/evals.json
Normal file
23
skills/mermaid-fixer/evals/evals.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"skill_name": "mermaid-fixer",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "The Mermaid diagrams in demo_scripts/demo.md have syntax errors. Please fix them.",
|
||||
"expected_output": "The skill should run check-mermaid.js, identify the 3 syntax errors, and apply fixes by quoting labels with special characters. After fixes, all 9 Mermaid blocks should pass validation.",
|
||||
"files": ["demo_scripts/demo.md"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "My documentation file README.md has broken Mermaid diagrams. Can you check and fix them?",
|
||||
"expected_output": "The skill runs check-mermaid.js on README.md, parses any error reports, applies appropriate fixes based on error types, and verifies the fixes by running the check again.",
|
||||
"files": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "CI/CD is failing with Mermaid validation errors on docs/api.md. Please fix the diagrams.",
|
||||
"expected_output": "The skill identifies all Mermaid syntax errors using check-mermaid.js, applies fixes for each error type (parentheses, brackets, braces in labels), and ensures the file passes validation.",
|
||||
"files": []
|
||||
}
|
||||
]
|
||||
}
|
||||
128
skills/mermaid-fixer/scripts/check-mermaid.js
Normal file
128
skills/mermaid-fixer/scripts/check-mermaid.js
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// 获取命令行传入的 Markdown 文件路径
|
||||
const fileArg = process.argv[2];
|
||||
if (!fileArg) {
|
||||
console.error('❌ 请提供 Markdown 文件路径。用法: node check-mermaid.js <file.md>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = path.resolve(fileArg);
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`❌ 文件不存在: ${filePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// 正则匹配 ```mermaid ... ``` 代码块,兼容 Windows(\r\n) 和 Linux(\n)
|
||||
const mermaidRegex = /```mermaid\r?\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
let blockCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
// 用于收集所有格式化后的错误报告块
|
||||
const errorReports = [];
|
||||
|
||||
while ((match = mermaidRegex.exec(content)) !== null) {
|
||||
blockCount++;
|
||||
|
||||
const fullMatch = match[0];
|
||||
const code = match[1];
|
||||
|
||||
// 1. 计算在 Markdown 文件中的绝对行号
|
||||
const textBeforeMatch = content.substring(0, match.index);
|
||||
const startLine = textBeforeMatch.split(/\r?\n/).length;
|
||||
const endLine = startLine + fullMatch.split(/\r?\n/).length - 1;
|
||||
|
||||
// 创建临时文件存放单个 Mermaid 代码
|
||||
const tmpFile = path.join(os.tmpdir(), `mermaid-check-${Date.now()}-${blockCount}.mmd`);
|
||||
fs.writeFileSync(tmpFile, code.trim());
|
||||
|
||||
try {
|
||||
// 调用 mmdc 进行静默渲染检查
|
||||
execSync(`npx mmdc -i "${tmpFile}" -o "${tmpFile}.svg" -q`, { stdio: 'pipe' });
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
|
||||
// 2. 净化报错信息:剔除底层执行堆栈
|
||||
const stderr = error.stderr ? error.stderr.toString() : error.message;
|
||||
const errorLines = stderr.split(/\r?\n/);
|
||||
const cleanErrorLines = [];
|
||||
|
||||
for (const line of errorLines) {
|
||||
if (line.trim().startsWith('at ') ||
|
||||
line.includes('Parser3.parseError') ||
|
||||
line.includes('fromText')) {
|
||||
break;
|
||||
}
|
||||
cleanErrorLines.push(line);
|
||||
}
|
||||
const cleanErrorOutput = cleanErrorLines.join('\n').trim();
|
||||
|
||||
// 3. 截断代码内容:最少3行,最多6行
|
||||
const codeLines = code.split(/\r?\n/).filter(l => l.trim() !== '');
|
||||
let displayCode = '';
|
||||
|
||||
if (codeLines.length <= 6) {
|
||||
displayCode = codeLines.join('\n');
|
||||
} else {
|
||||
const head = codeLines.slice(0, 3).join('\n');
|
||||
const tail = codeLines.slice(-3).join('\n');
|
||||
displayCode = `${head}\n ...\n ... (中间省略 ${codeLines.length - 6} 行) ...\n ...\n${tail}`;
|
||||
}
|
||||
|
||||
// 4. 组装单个错误的 Markdown 块
|
||||
errorReports.push(`### ❌ 错误 #${errorCount} (代码块 #${blockCount})
|
||||
|
||||
- **文档位置:** 第 \`${startLine}\` 行至第 \`${endLine}\` 行
|
||||
|
||||
#### 核心错误详情
|
||||
\`\`\`text
|
||||
${cleanErrorOutput}
|
||||
\`\`\`
|
||||
|
||||
#### 代码内容片段
|
||||
\`\`\`text
|
||||
${displayCode}
|
||||
\`\`\``);
|
||||
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
|
||||
if (fs.existsSync(`${tmpFile}.svg`)) fs.unlinkSync(`${tmpFile}.svg`);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 生成最终的 Markdown 报告输出
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
if (errorCount > 0) {
|
||||
// 存在错误的情况
|
||||
console.log(`## 🚨 Mermaid 语法检查报告\n`);
|
||||
console.log(`**检查文件:** \`${fileName}\``);
|
||||
console.log(`**检查结果:** ❌ 发现 ${errorCount} 处语法错误 (共检测到 ${blockCount} 个代码块)\n`);
|
||||
console.log(`---\n`);
|
||||
console.log(errorReports.join('\n\n---\n\n'));
|
||||
|
||||
// 返回非零状态码,确保在 CI/CD 或 Git Hook 中能够阻断流程
|
||||
process.exit(1);
|
||||
} else {
|
||||
// 全部正确或没有代码块的情况
|
||||
console.log(`## 🎉 Mermaid 语法检查报告\n`);
|
||||
console.log(`**检查文件:** \`${fileName}\``);
|
||||
if (blockCount === 0) {
|
||||
console.log(`**检查结果:** ⚠️ 未检测到 Mermaid 代码块`);
|
||||
} else {
|
||||
console.log(`**检查结果:** ✅ 全部通过 (共检测到 ${blockCount} 个代码块)`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
Reference in New Issue
Block a user