Word Documents
.docx create, edit, read, and visual verification
docx skill is available by default and triggers automatically when you mention Word documents, .docx files, reports, memos, letters, or templates. No manual loading required.Overview
The docx skill handles the full lifecycle of Word documents: creation from scratch, editing existing files, reading and analysis, template-based generation, tracked-change management, and mandatory visual QA verification. It is scoped exclusively to .docx files — not PDFs, spreadsheets, HWPX, or Google Docs.
The primary tool is officecli, a CLI on PATH that covers ~80% of tasks (add, set, remove, validate, query, track-change accept/reject). For tasks officecli cannot handle — tracked-change creation, OMML equations, bulk pattern matching — the skill falls back to Python OOXML scripts in scripts/.
Triggers
| Pattern | Examples |
|---|---|
| File extension | .docx, Word document |
| Document types | Reports, memos, letters, proposals, contracts |
| Keywords | "Word doc", "template", "tracked changes", "table of contents" |
| Korean triggers | "워드 문서", "보고서 만들어줘", "문서 작성해줘" |
Quick Reference
| Task | Tool | Command Pattern | Notes |
|---|---|---|---|
| Format like existing doc | shell + officecli | cp source.docx target.docx && officecli open target.docx | Inherit styles/headers/footers |
| Create blank DOCX | officecli | officecli create report.docx | Start from real Office file |
| Add paragraph | officecli | officecli add FILE /body --type paragraph --prop text="..." | Primary write path |
| Edit paragraph/run | officecli | officecli set FILE /body/p[N] --prop ... | Exact path targeting |
| Read text/outline/stats | officecli | officecli view FILE text | Modes: text, annotated, outline, stats, issues, html |
| Query document | officecli | officecli query FILE "p[style=Heading1]" | CSS-like selectors |
| Template replacement | officecli | officecli set FILE / --prop find="{{X}}" --prop replace="Y" | Preserves template structure |
| Validation / issue scan | officecli | officecli validate FILE | Pair with view FILE issues |
| Accept/reject tracked changes | officecli | officecli set FILE / --prop accept-changes=all | Also reject-changes=all |
| Create tracked changes | Python (L3/L4) | scripts/docx_cli.py + ooxml/redline_diff.py | officecli cannot create — only accept/reject |
| OMML equations | Python (L4) | Unpack → inject <m:oMath> → repack | officecli cannot generate OMML |
| Complex anchored comments | Python (L3) | python3 scripts/comment.py IN OUT --text "..." --anchor "..." | For comments beyond officecli |
| PDF conversion / visual QA | soffice | soffice --headless --convert-to pdf FILE | Screenshot-based QA |
"~해줘" Usage Examples
Creates a quarterly report document from scratch with proper heading hierarchy, table of contents, tables, and professional formatting. Uses
officecli create → add structure → validate → PDF verification.
Reference-based editing: copies the source file to inherit all styles, headers, and footers, then replaces body content while preserving the document's visual identity. See the Reference-Based Editing workflow below.
Reads and analyzes the document using
officecli view FILE text and view FILE outline, then provides a structured summary of headings, key content, and document statistics.
Creates tracked changes (redlines) via the Python OOXML scripts, since officecli can only accept/reject — not create — tracked changes. Escalates to L3/L4 automatically.
Template-safe replacement using
officecli set FILE / --prop find="{{X}}" --prop replace="Y" to fill placeholders while preserving the template's formatting and structure.
Reference-Based Editing
When a user says "format like X.docx", "match existing style", "based on template", or provides a source file — always start from the source file. Never rebuild from scratch.
Workflow
- Copy the source:
cp source.docx target.docx— inherits all styles, margins, numbering, headers, footers - Open:
officecli open target.docx— daemon starts; command returns immediately - Remove body content only — keep
/styles,/numbering,/header,/footer - Add new paragraphs using existing style names (e.g.
--prop style=Heading1) — they auto-apply
Why This Matters
Pandoc-generated and Word-generated documents have specific style IDs (e.g. Heading1, BodyText, FirstParagraph) unique to that document. Adding a new style with the same name causes:
officecli validateerrors about duplicate style IDs- Word/LibreOffice rendering falls back to defaults
- The task takes 10x longer than necessary
Template Priority
- User-provided source file — first-class template
tests/fixtures/*.docx— pre-built working examples shipped with the skillofficecli createblank — only when nothing else applies
# CORRECT: inherit Pandoc styles
cp Assignment1.docx Assignment2.docx
officecli open Assignment2.docx
officecli remove Assignment2.docx "/body/p[1]" # remove old body (keep styles/headers/footers)
# ... remove more paragraphs ...
officecli add Assignment2.docx /body --type paragraph --prop text="New title" --prop style=Heading1
officecli close Assignment2.docx
# WRONG: recreate styles that already exist -- validate fails
officecli add doc.docx /styles --type style --prop name=Heading1 --prop size=20pt ...
Escalation Ladder
When officecli cannot do the job, escalate in this order:
| Level | When | Tool |
|---|---|---|
| L1 officecli high-level | Typical add/set/remove operations | officecli add/set/remove/query/view |
| L2 officecli raw-set | XML injection — PAGE field, fldChar, hyperlink anchor, custom attributes | officecli raw-set FILE PATH --xpath X --action A --xml ... |
| L3 Python script | Bulk tracked-change ops, comment add, merge runs, redline validation | python3 scripts/*.py |
| L4 Unpack → edit XML → repack | OMML equations, custom style injection, pattern-match editing | scripts/docx_cli.py open FILE work/ → edit XML → scripts/docx_cli.py save work/ OUT.docx |
Escalation Signals
- officecli shows "silently ignored" → L2 (raw-set)
- Need OMML/MathML equations → L4 (inject
<m:oMath>XML) - Need to CREATE tracked changes (officecli only accepts/rejects) → L3 or L4
- Bulk find/replace across 100+ targets → L3 (
docx_cli.py search/replace) - Pandoc-generated doc with custom style IDs → Reference-Based Editing first
- Task still fails after L1+L2 → Read relevant
references/*.mdbefore giving up
Core Workflows
Execution Model
Run commands one at a time. Do not write all commands into a shell script and execute as a single block. OfficeCLI is incremental: every add, set, and remove immediately modifies the file and returns output.
- One command at a time, then read the output. Check the exit code before proceeding.
- Non-zero exit = stop and fix immediately. Do not continue building on a broken state.
- Verify after structural operations. After adding a style, table, chart, or section, run
getorvalidatebefore building on top of it.
Reading and Analyzing
officecli view doc.docx text # Full text extraction
officecli view doc.docx text --max-lines 200 # Truncated extraction
officecli view doc.docx text --start 1 --end 50 # Range extraction
officecli view doc.docx outline # Structure: stats, headings, headers/footers
officecli view doc.docx annotated # Style/font/size per run, equations as LaTeX
officecli view doc.docx stats # Paragraph count, style/font distribution
Element Inspection
officecli get doc.docx / # Document root (metadata, page setup)
officecli get doc.docx /body --depth 1 # List body children
officecli get doc.docx "/body/p[1]" # Specific paragraph
officecli get doc.docx "/body/p[1]/r[1]" # Specific run
officecli get doc.docx "/body/tbl[1]" --depth 3 # Table structure
officecli get doc.docx /styles # Style definitions
officecli get doc.docx "/styles/Heading1" # Specific style
officecli get doc.docx "/header[1]" # Header/footer
officecli get doc.docx /numbering # Numbering definitions
officecli get doc.docx "/body/p[1]" --json # JSON output for scripting
CSS-like Queries
officecli query doc.docx 'paragraph[style=Heading1]' # By style
officecli query doc.docx 'p:contains("quarterly")' # By text content
officecli query doc.docx 'p:empty' # Empty paragraphs
officecli query doc.docx 'image:no-alt' # Images without alt text
officecli query doc.docx 'p[align=center] > r[bold=true]' # Compound selectors
officecli query doc.docx 'paragraph[size>=24pt]' # By size
officecli query doc.docx 'field[fieldType!=page]' # Fields by type
Headers and Footers
--prop field=page is silently ignored in add --type footer commands. The footer is created with static text only. You must use raw-set to inject the PAGE field after creating the footer.# Step 1. Empty footer for cover page (auto-enables differentFirstPage)
officecli add doc.docx / --type footer --prop type=first --prop text=""
# Step 2. Default footer with static "Page " text
officecli add doc.docx / --type footer --prop text="Page " \
--prop type=default --prop align=center --prop size=9pt --prop font=Calibri
# Step 3. Inject PAGE field via raw-set
# (footer[2] = default when first-page footer also exists)
officecli raw-set doc.docx "/footer[2]" \
--xpath "//w:p" \
--action append \
--xml '<w:r ...><w:fldChar w:fldCharType="begin"/></w:r>
<w:r ...><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
<w:r ...><w:fldChar w:fldCharType="end"/></w:r>'
Footer index rule: When both a first-page footer and a default footer are added, the default footer is /footer[2]. If there is no first-page footer, the default footer is /footer[1]. Always verify with officecli get doc.docx "/footer[2]".
Resident Mode (Performance)
Always use open/close — it is the smart default. Every command benefits from no repeated file I/O.
officecli open doc.docx # Load once into memory (returns IMMEDIATELY)
officecli add doc.docx ... # All commands run in memory -- fast
officecli set doc.docx ...
officecli close doc.docx # Write once to disk
officecli open as a background shell job (e.g. via run_in_background). It returns immediately and the daemon lives in the background automatically. Running it as a monitored shell creates zombies and file locks.Batch Mode
Execute multiple operations in a single open/save cycle:
cat <<'EOF' | officecli batch doc.docx
[
{"command":"add","parent":"/body","type":"paragraph",
"props":{"text":"Introduction","style":"Heading1"}},
{"command":"add","parent":"/body","type":"paragraph",
"props":{"text":"This report covers Q4 results.","font":"Calibri","size":"11pt"}}
]
EOF
Batch supports: add, set, get, query, remove, move, swap, view, raw, raw-set, validate.
Subskill References
Additional detail lives in companion files loaded on demand:
| Subskill | When to Use |
|---|---|
| officecli-academic-paper | Academic papers, citations, bibliography, TOC for papers |
| creating.md | Detailed creation recipes (new documents from scratch) |
| editing.md | Detailed editing guides (modify existing documents) |
Decision Flow
Is the document an academic paper (thesis, journal, conference)?
YES --> read officecli-academic-paper/SKILL.md
NO --> continue with main skill
User provided a source file to match?
YES --> Reference-Based Editing + editing.md
NO --> creating.md
Python OOXML Scripts
When officecli reaches its limits, the skill falls back to these Python scripts:
| Script | Purpose | Command |
|---|---|---|
scripts/docx_cli.py | Unified Python CLI — unpack, save, validate, repair, search, TOC, chunk, comment, accept-changes, merge-runs | python3 scripts/docx_cli.py {open|save|validate|repair|text|search|...} |
scripts/accept_changes.py | Accept all tracked changes | python3 scripts/accept_changes.py IN.docx OUT.docx |
scripts/comment.py | Add W3C-compliant OOXML comments anchored to text | python3 scripts/comment.py IN.docx OUT.docx --text "..." --anchor "..." |
scripts/ooxml/merge_runs.py | Merge adjacent runs with identical formatting | python3 scripts/ooxml/merge_runs.py unpacked/ |
scripts/ooxml/redline_diff.py | Validate tracked-change correctness vs original | python3 scripts/ooxml/redline_diff.py unpacked/ original.docx |
scripts/ooxml/simplify_tracked.py | Simplify same-author adjacent tracked changes | python3 scripts/ooxml/simplify_tracked.py unpacked/ |
Reference Materials
| File | Read When | Contains |
|---|---|---|
references/cjk-handling.md | Korean text / East Asian font / wrapping issues | rFonts East Asian fonts, lang tags, accessibility |
references/tracked-changes.md | Track changes / comments / redline work | w:ins, w:del, comments XML, script usage examples |
Design Principles for Business Documents
Heading Hierarchy
- H1: Document title (one per document)
- H2: Major sections
- H3: Subsections under H2
- Never skip levels (H1 → H3 is invalid). Table of Contents depends on correct heading structure.
# Verify heading hierarchy
officecli view report.docx outline
Color Palette
| Purpose | Allowed Colors | Hex Examples |
|---|---|---|
| Headings, emphasis | Navy | #003366, #1B2A4A |
| Body accents, borders | Charcoal | #333333, #4A4A4A |
| Highlights, callouts | Forest green | #2E5E3F, #1A4731 |
Never use rainbow colors, bright primary colors, or more than 3 accent colors in a single document.
Font Selection
| Script | Primary Font | Fallback |
|---|---|---|
| Korean | Malgun Gothic | Pretendard |
| English / Latin | Calibri | Aptos |
Typography
Body text: 11-12pt. Headings step up: H1 = 18pt minimum (20pt preferred for long documents), H2 = 14pt bold, H3 = 12pt bold. Line spacing of 1.15x-1.5x for body text.
Content-to-Element Mapping
| Content Type | Recommended Element | Why |
|---|---|---|
| Sequential items | Bulleted list (listStyle=bullet) | Scanning is faster than inline commas |
| Step-by-step process | Numbered list (listStyle=numbered) | Numbers communicate order |
| Comparative data | Table with header row | Columns enable side-by-side comparison |
| Trend data | Embedded chart (chartType=line/column) | Visual pattern recognition |
| Key definition | Hanging indent paragraph | Offset term from definition |
| Legal/contract clause | Numbered list with bookmarks | Cross-referencing via bookmarks |
| Mathematical content | Equation element (formula=LaTeX) | Proper OMML rendering |
| Citation/reference | Footnote or endnote | Keeps body text clean |
| Pull quote / callout | Paragraph with border + shading | Visual distinction from body |
| Multi-section layout | Section breaks with columns | Column control per section |
Mandatory Verification
# Step 1: Structural validation
officecli validate output.docx
# Step 2: Visual PDF verification
soffice --headless --convert-to pdf --outdir /tmp output.docx
# Open/inspect PDF to confirm: formatting, tables, images, headers/footers
- Skipping PDF verification = unverified output
- If
validatereports errors, fix them before delivering the file
QA Checklist
Assume there are problems. Your job is to find them.
Issue Detection
officecli view doc.docx issues
officecli view doc.docx issues --type format
officecli view doc.docx issues --type content
officecli view doc.docx issues --type structure
Content QA
officecli view doc.docx text
officecli view doc.docx outline
officecli query doc.docx 'p:empty'
officecli query doc.docx 'image:no-alt'
# Check for leftover placeholders
officecli query doc.docx 'p:contains("lorem")'
officecli query doc.docx 'p:contains("placeholder")'
Pre-Delivery Checklist
- Metadata set (title, author)
- PAGE field injected in footer via
raw-set— verify withofficecli get doc.docx "/footer[2]" --depth 3 - First-page footer added if document has a cover page
- Cover page content fills at least 60% of the page
- TOC present when document has 3+ headings
- Last page content fills at least 40% of the page
- Heading hierarchy correct (no skipped levels)
- No empty paragraphs used as spacing
- All images have alt text
- Tables have header rows
- Document validates with
officecli validate - No placeholder text remaining
Verification Loop
- Generate document
- Run
view issues+view outline+view text+validate - List issues found (if none, look again more critically)
- Fix issues
- Re-verify — one fix often creates another problem
- Repeat until a full pass reveals no new issues
Do not declare success until you have completed at least one fix-and-verify cycle.
Common Pitfalls
| Pitfall | Correct Approach |
|---|---|
--name "foo" | Use --prop name="foo" — all attributes go through --prop |
| Guessing property names | Run officecli help docx set paragraph --json for exact names |
\n in shell strings | Use \\n for newlines in --prop text="line1\\nline2" |
Hex colors with # | Use FF0000 not #FF0000 — no hash prefix |
| Paths are 1-based | /body/p[1], /body/tbl[1] — XPath convention |
--index is 0-based | --index 0 = first position — array convention |
Unquoted [N] in zsh/bash | Shell glob-expands /body/p[1] — always quote paths: "/body/p[1]" |
| Spacing via empty paragraphs | Use spaceBefore/spaceAfter properties on paragraphs |
$ in --prop text= | Use single quotes: --prop text='$50M' |
--prop field=page in footer | Silently ignored. Must use raw-set to inject <w:fldChar> |
| Recreating styles from template | cp source.docx target.docx first — do not add styles with existing IDs |
officecli open as background shell | Run foreground — open returns immediately, daemon runs in bg automatically |
| Batch JSON parse error | Use heredoc: cat <<'EOF' | officecli batch FILE.docx |
| OMML equation creation | officecli cannot generate OMML — escalate to L4 |
listStyle on run | listStyle is a paragraph property, not a run property |
| Row-level bold/color/shd | Row set only supports height, header, and c1/c2/c3 text shortcuts — use cell-level set |
| Section vs root property names | Section uses pagewidth/pageheight (lowercase). Document root uses pageWidth/pageHeight (camelCase) |
| Wrong border format | Use style;size;color;space format: single;4;FF0000;1 |
| Code block indentation via spaces | Use ind.left paragraph property (e.g. --prop ind.left=720) |
chartType=pie/doughnut in LibreOffice | Slices are invisible in PDF — use column or bar instead |
Known Issues
| Issue | Workaround |
|---|---|
| No visual preview | Unlike pptx (SVG/HTML), docx has no built-in rendering. Use view text/outline/annotated/issues for verification. Open in Word for visual check. |
| Track changes creation requires raw XML | OfficeCLI can accept/reject but cannot create tracked changes. Use raw-set or Python scripts (L3). |
| Tab stops may require raw XML | Tab stop creation is not exposed in high-level commands. Use raw-set. |
| Chart series cannot be added after creation | set --prop data= can only update existing series. Delete and recreate the chart. |
| Complex numbering definitions | listStyle=bullet/numbered covers simple cases. For multi-level lists, use numId/numLevel. |
| Batch intermittent failure | ~1-in-15 batch operations may fail. Retry or close/reopen file. Split large batches into 10-15 chunks. |
| Table-level padding produces invalid XML | Do not use set tbl[N] --prop padding=N. Use cell-level padding.top/padding.bottom. |
| Internal hyperlinks not supported | hyperlink only accepts absolute URIs. Use raw-set with <w:hyperlink w:anchor="...">. |
Table --index positioning unreliable | --index N on add /body --type table may be ignored. Add content in desired order. |
view text shows "1." for all numbered items | Display-only limitation. Rendered output in Word/LibreOffice shows correct numbers. |
Anti-Patterns (Must Avoid)
- Placeholder data: Never leave "Acme Corp", "Alice Chen", "Lorem ipsum" in output. If the user has not provided data, ask.
- Footer PAGE fields: When setting page numbers via
raw-set, the XML structure must be exact (fldChar/instrText/fldChar sequence). - Empty paragraphs for spacing: Use
spacing-afterproperties. - Manual bullet characters (-, *): Use
listStyle=bulletorlistStyle=number. - Manual font XML injection: Use
--prop font=...when it suffices. - Ignoring reference materials: If a complex task fails with officecli, read
references/*.mdand checkscripts/*.pybefore giving up. - Recreating existing styles: When user provides a source file, copy it and modify — do not rebuild styles from scratch.
Tool Discovery
Always confirm syntax from help before guessing:
officecli --help
officecli help docx
officecli help docx add
officecli help docx set
officecli help docx query
officecli help docx add paragraph --json
officecli help docx set run --json
Dependencies
| Tool | Purpose | Status |
|---|---|---|
officecli (PATH) | Primary DOCX CLI — global install includes CJK fork | Required |
dotnet | Runtime/build for officecli | Required for fork builds |
python3 | Fallback scripts (scripts/*.py) | Required for L3/L4 |
lxml | Python XML processing for scripts/ooxml/* | Required for L3/L4 |
soffice | PDF conversion / .doc migration / macro workflows | Optional fallback |
pdftoppm | Image-based QA after PDF render | Optional fallback |
Prerequisite Check
# Required
python3 -c "import docx, lxml" || echo "MISSING: pip install python-docx lxml"
# On-demand: LibreOffice
which soffice >/dev/null 2>&1 || echo "INFO: LibreOffice not installed"
# On-demand: OfficeCLI
which officecli >/dev/null 2>&1 || echo "INFO: OfficeCLI not installed"