refactor(skills): align MPI skills with Agent Skills best practices
The skill metadata had drifted: every SKILL.md name field lacked the mpi- prefix, contradicting the directory names and the Agent Skills specification. Descriptions were also missing negative triggers, making it easy for the agent to load the wrong skill. Rewrote the pdf-to-docx skill to follow progressive disclosure: the main SKILL.md dropped from 318 lines to 80, with detailed code examples moved to on-demand references. Added uv run instructions and /// script PEP 723 metadata so dependencies are declared inline and installed automatically. Fixed the pptx skill's script paths and added CLI usage messages to both pptx scripts and the normalize script. Removed the empty self-review directory that was superseded by the unified translation-review skill.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# PDF-to-DOCX Config Patterns
|
||||
|
||||
The converter accepts a config dict that maps PDF-specific patterns to DOCX behavior.
|
||||
|
||||
## Example config
|
||||
|
||||
```python
|
||||
config = {
|
||||
"fonts": {
|
||||
"title": "STHeitiSC-Medium",
|
||||
"body": "HYShuSongErKW",
|
||||
"page_number": "HelveticaNeue",
|
||||
},
|
||||
"header_sizes": {"section": 18, "sub": 15},
|
||||
"body_size": 12,
|
||||
"bullet_fonts": ["Wingdings", "Wingdings 2", "Wingdings 3"],
|
||||
"page_number_font": "HelveticaNeue",
|
||||
"numbered_patterns": [r'^\d+\)', r'^\d+\.'],
|
||||
"skip_fonts": ["HelveticaNeue"],
|
||||
}
|
||||
```
|
||||
|
||||
## Common post-processing patterns
|
||||
|
||||
### Compact numbered lists
|
||||
|
||||
Split items that flowed together in one PDF paragraph:
|
||||
|
||||
```python
|
||||
def split_compact_list(text: str) -> list[str]:
|
||||
parts = re.split(r'(?=\d+\))', text)
|
||||
return [p for p in parts if p.strip()]
|
||||
```
|
||||
|
||||
### Verse / poetry lines
|
||||
|
||||
Split merged verse at semantic phrase boundaries:
|
||||
|
||||
```python
|
||||
def split_verse(text: str, split_markers: list[str]) -> list[str]:
|
||||
pattern = '|'.join(f'(?={m})' for m in split_markers)
|
||||
return [p for p in re.split(pattern, text) if p.strip()]
|
||||
```
|
||||
|
||||
### Chinese process steps
|
||||
|
||||
Split `一、foo 二、bar` into separate items:
|
||||
|
||||
```python
|
||||
def split_chinese_steps(text: str) -> list[str]:
|
||||
parts = re.split(r'(?=[一二三四五六七八九十]、)', text)
|
||||
return [p.strip() for p in parts if p.strip()]
|
||||
```
|
||||
|
||||
## Tuning thresholds
|
||||
|
||||
- **Y-gap threshold** (typically 20–30 pt): controls how aggressively lines are grouped into paragraphs.
|
||||
- **Style-change threshold**: break paragraphs on font change, size jump, or bold toggle when the difference exceeds this value.
|
||||
|
||||
See `references/troubleshooting.md` for threshold adjustment guidance.
|
||||
@@ -0,0 +1,59 @@
|
||||
# DOCX Construction Notes
|
||||
|
||||
Low-level notes for building the DOCX output with python-docx.
|
||||
|
||||
## East Asian fonts
|
||||
|
||||
Set CJK fonts properly on a run:
|
||||
|
||||
```python
|
||||
from docx.oxml.ns import qn
|
||||
from docx.oxml import OxmlElement
|
||||
|
||||
def set_east_asian_font(run, name: str):
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.find(qn('w:rFonts'))
|
||||
if rFonts is None:
|
||||
rFonts = OxmlElement('w:rFonts')
|
||||
rPr.insert(0, rFonts)
|
||||
rFonts.set(qn('w:eastAsia'), name)
|
||||
rFonts.set(qn('w:ascii'), name)
|
||||
rFonts.set(qn('w:hAnsi'), name)
|
||||
```
|
||||
|
||||
## Native bullets
|
||||
|
||||
Use Word's `List Bullet` style, not Wingdings characters:
|
||||
|
||||
```python
|
||||
p = doc.add_paragraph(style='List Bullet')
|
||||
p.clear()
|
||||
run = p.add_run("Bullet text")
|
||||
set_east_asian_font(run, font_name)
|
||||
```
|
||||
|
||||
## Image embedding
|
||||
|
||||
Extract images from the PDF first, then embed them:
|
||||
|
||||
```python
|
||||
from docx.shared import Inches
|
||||
|
||||
# Extract
|
||||
doc = pymupdf.open("input.pdf")
|
||||
for pi in range(len(doc)):
|
||||
for idx, img in enumerate(doc[pi].get_images()):
|
||||
xref = img[0]
|
||||
base = doc.extract_image(xref)
|
||||
path = f"extracted_{pi}_{idx}.{base['ext']}"
|
||||
with open(path, 'wb') as f:
|
||||
f.write(base['image'])
|
||||
|
||||
# Embed
|
||||
p = doc.add_paragraph()
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
run = p.add_run()
|
||||
run.add_picture(image_path, width=Inches(3.5))
|
||||
```
|
||||
|
||||
See `scripts/convert_pdf_to_docx.py` for the production-ready implementation.
|
||||
@@ -0,0 +1,42 @@
|
||||
# PDF Inspection Guide
|
||||
|
||||
Before converting a PDF, dump its text spans to understand fonts, sizes, bullets, and structure.
|
||||
|
||||
## Dump spans
|
||||
|
||||
```bash
|
||||
python3 << 'PY'
|
||||
import pymupdf
|
||||
doc = pymupdf.open("input.pdf")
|
||||
for pi in range(len(doc)):
|
||||
page = doc[pi]
|
||||
blocks = page.get_text("dict")["blocks"]
|
||||
for block in blocks:
|
||||
if block["type"] != 0: continue
|
||||
for line in block["lines"]:
|
||||
for span in line["spans"]:
|
||||
bbox = span["bbox"]
|
||||
flags = span["flags"]
|
||||
attrs = []
|
||||
if flags & 2**1: attrs.append("I")
|
||||
if flags & 2**4: attrs.append("B")
|
||||
print(f" Y={bbox[1]:.0f} [{span['size']:.1f}pt {'+'.join(attrs) or '-'}] {span['font']} | {span['text']}")
|
||||
PY
|
||||
```
|
||||
|
||||
## What to identify
|
||||
|
||||
- **Fonts used** — map PDF font names to DOCX font names.
|
||||
- **Bullet mechanism** — Wingdings glyphs, Unicode bullets, or something else.
|
||||
- **Header hierarchy** — which font size marks section vs. sub-section headers.
|
||||
- **Numbered lists** — delimiter style: `1)`, `1.`, `1)`, `一、`.
|
||||
- **Images** — check `page.get_images()` on each page.
|
||||
- **Special sections** — tables, verses, attribution lines, page numbers, forms.
|
||||
|
||||
## Page numbers
|
||||
|
||||
Page-number fonts are usually small and repeated on every page. Add them to `skip_fonts` in the config so they do not leak into body text.
|
||||
|
||||
## Attribution lines
|
||||
|
||||
Lines like `——节选自...` often use a different font or indentation. The converter breaks paragraphs on these; verify the break point after conversion.
|
||||
@@ -0,0 +1,18 @@
|
||||
# PDF-to-DOCX Troubleshooting
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---------|-------|-----|
|
||||
| Text over-merged | Y-gap threshold too high | Lower gap threshold (e.g. 20 → 15) |
|
||||
| Missing sections | Skipped by font filter | Add font to `skip_fonts` or remove filter |
|
||||
| Over-split lines | Y-gap threshold too low | Raise gap threshold (e.g. 20 → 30) |
|
||||
| Wingdings boxes | Unicode bullet inserted | Use `style='List Bullet'` instead |
|
||||
| CJK font wrong | East Asian font not set | Use `set_east_asian_font()` helper |
|
||||
| Image missing | Not extracted before DOCX build | Run image extraction first |
|
||||
| Verse mangled | Regex too aggressive | Tune verse splitting pattern |
|
||||
|
||||
## General debugging steps
|
||||
|
||||
1. Re-inspect the PDF with the span dump from `references/inspection-guide.md`.
|
||||
2. Compare the config against the actual fonts and sizes in the dump.
|
||||
3. Run the verification script in `SKILL.md` and check paragraph styles.
|
||||
4. Adjust one threshold at a time and re-convert.
|
||||
Reference in New Issue
Block a user