docx · diff

git:20260607.412f6db to git:20260616.904f11f

46 added, 413 removed. Audit A to A.

---
name: docx
- description: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
- license: Proprietary. LICENSE.txt has complete terms
- # nativeEngine.triggers feed Code Buddy's keyword skill matcher (scoreSkill);
- # without them realistic requests score below the 0.3 auto-activation threshold.
+ description: "Create and edit Word documents (.docx): headings, styled paragraphs, tables, images, headers/footers, and page formatting. Use whenever the user asks to write, format, or modify a Word/.docx document."
+ license: MIT (Code Buddy original skill)
nativeEngine:
triggers:
- word document
- - word doc
- docx
- - microsoft word
- ---
-
- # DOCX creation, editing, and analysis
-
- ## Overview
-
- A .docx file is a ZIP archive containing XML files.
-
- ## Quick Reference
-
- | Task | Approach |
- |------|----------|
- | Read/analyze content | `pandoc` or unpack for raw XML |
- | Create new document | Use `docx-js` - see Creating New Documents below |
- | Edit existing document | Unpack → edit XML → repack - see Editing Existing Documents below |
-
- ### Reading Content
-
- ```bash
- # Text extraction with tracked changes
- pandoc --track-changes=all document.docx -o output.md
-
- # Raw XML access
- python3 scripts/unpack.py document.docx unpacked/
- ```
-
- ### Converting to Images
-
- ```bash
- soffice --headless --convert-to pdf document.docx
- pdftoppm -jpeg -r 150 document.pdf page
- ```
-
- ---
-
- ## Creating New Documents
-
- Generate .docx files with JavaScript. Install: `npm install -g docx`
-
- ### Setup
- ```javascript
- const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
- Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
- TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
- VerticalAlign, PageNumber, PageBreak } = require('docx');
-
- const doc = new Document({ sections: [{ children: [/* content */] }] });
- Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));
- ```
-
- ### Page Size
-
- ```javascript
- // CRITICAL: docx-js defaults to A4, not US Letter
- // Always set page size explicitly for consistent results
- sections: [{
- properties: {
- page: {
- size: {
- width: 12240, // 8.5 inches in DXA
- height: 15840 // 11 inches in DXA
- },
- margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins
- }
- },
- children: [/* content */]
- }]
- ```
-
- **Common page sizes (DXA units, 1440 DXA = 1 inch):**
-
- | Paper | Width | Height | Content Width (1" margins) |
- |-------|-------|--------|---------------------------|
- | US Letter | 12,240 | 15,840 | 9,360 |
- | A4 (default) | 11,906 | 16,838 | 9,026 |
-
- ### Styles (Override Built-in Headings)
-
- Use Arial as the default font (universally supported). Keep titles black for readability.
-
- ```javascript
- const doc = new Document({
- styles: {
- default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default
- paragraphStyles: [
- // IMPORTANT: Use exact IDs to override built-in styles
- { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
- run: { size: 32, bold: true, font: "Arial" },
- paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel required for TOC
- { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
- run: { size: 28, bold: true, font: "Arial" },
- paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
- ]
- },
- sections: [{
- children: [
- new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }),
- ]
- }]
- });
- ```
-
- ### Lists (NEVER use unicode bullets)
-
- ```javascript
- // ❌ WRONG - never manually insert bullet characters
- new Paragraph({ children: [new TextRun("• Item")] }) // BAD
- new Paragraph({ children: [new TextRun("\u2022 Item")] }) // BAD
-
- // ✅ CORRECT - use numbering config with LevelFormat.BULLET
- const doc = new Document({
- numbering: {
- config: [
- { reference: "bullets",
- levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
- style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
- { reference: "numbers",
- levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
- style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
- ]
- },
- sections: [{
- children: [
- new Paragraph({ numbering: { reference: "bullets", level: 0 },
- children: [new TextRun("Bullet item")] }),
- new Paragraph({ numbering: { reference: "numbers", level: 0 },
- children: [new TextRun("Numbered item")] }),
- ]
- }]
- });
-
- // ⚠️ Each reference creates INDEPENDENT numbering
- // Same reference = continues (1,2,3 then 4,5,6)
- // Different reference = restarts (1,2,3 then 1,2,3)
- ```
-
- ### Tables
-
- **CRITICAL: Tables need dual widths** - set both `columnWidths` on the table AND `width` on each cell. Without both, tables render incorrectly on some platforms.
-
- ```javascript
- // CRITICAL: Always set table width for consistent rendering
- // CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds
- const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
- const borders = { top: border, bottom: border, left: border, right: border };
-
- new Table({
- width: { size: 100, type: WidthType.PERCENTAGE }, // Always set table width
- columnWidths: [4680, 4680], // Set at table level (DXA: 1440 = 1 inch)
- rows: [
- new TableRow({
- children: [
- new TableCell({
- borders,
- width: { size: 4680, type: WidthType.DXA }, // Also set on each cell
- shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID
- margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width)
- children: [new Paragraph({ children: [new TextRun("Cell")] })]
- })
- ]
- })
- ]
- })
- ```
-
- **Table width calculation:**
-
- Use `WidthType.PERCENTAGE` for simplicity, or `WidthType.DXA` for precise control:
-
- ```javascript
- // Option 1: Percentage (recommended - automatically fits content area)
- width: { size: 100, type: WidthType.PERCENTAGE }
-
- // Option 2: DXA (precise control)
- // Table width = sum of columnWidths = content width
- // US Letter with 1" margins: 12240 - 2880 = 9360 DXA
- width: { size: 9360, type: WidthType.DXA },
- columnWidths: [7000, 2360] // Must sum to table width
- ```
-
- **Width rules:**
- - Table width must equal the sum of `columnWidths`
- - Cell `width` must match corresponding `columnWidth`
- - Cell `margins` are internal padding - they reduce content area, not add to cell width
- - For full-width tables: use content width (page width minus left and right margins)
-
- ### Images
-
- ```javascript
- // CRITICAL: type parameter is REQUIRED
- new Paragraph({
- children: [new ImageRun({
- type: "png", // Required: png, jpg, jpeg, gif, bmp, svg
- data: fs.readFileSync("image.png"),
- transformation: { width: 200, height: 150 },
- altText: { title: "Title", description: "Desc", name: "Name" } // All three required
- })]
- })
- ```
-
- ### Page Breaks
-
- ```javascript
- // CRITICAL: PageBreak must be inside a Paragraph
- new Paragraph({ children: [new PageBreak()] })
-
- // Or use pageBreakBefore
- new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
- ```
-
- ### Table of Contents
-
- ```javascript
- // CRITICAL: Headings must use HeadingLevel ONLY - no custom styles
- new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" })
- ```
-
- ### Headers/Footers
-
- ```javascript
- sections: [{
- properties: {
- page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 inch
- },
- headers: {
- default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] })
- },
- footers: {
- default: new Footer({ children: [new Paragraph({
- children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
- })] })
- },
- children: [/* content */]
- }]
- ```
-
- ### Critical Rules for docx-js
-
- - **Set page size explicitly** - docx-js defaults to A4; use US Letter (12240 x 15840 DXA) for US documents
- - **Never use `\n`** - use separate Paragraph elements
- - **Never use unicode bullets** - use `LevelFormat.BULLET` with numbering config
- - **PageBreak must be in Paragraph** - standalone creates invalid XML
- - **ImageRun requires `type`** - always specify png/jpg/etc
- - **Always set table `width`** - use `{ size: 100, type: WidthType.PERCENTAGE }` for full width
- - **Tables need dual widths** - `columnWidths` array AND cell `width`, both must match
- - **Table width = sum of columnWidths** - for DXA, ensure they add up exactly
- - **Always add cell margins** - use `margins: { top: 80, bottom: 80, left: 120, right: 120 }` for readable padding
- - **Use `ShadingType.CLEAR`** - never SOLID for table shading
- - **TOC requires HeadingLevel only** - no custom styles on heading paragraphs
- - **Override built-in styles** - use exact IDs: "Heading1", "Heading2", etc.
- - **Include `outlineLevel`** - required for TOC (0 for H1, 1 for H2, etc.)
-
+ - word doc
+ - .docx
+ - write a document
+ - report document
---
- ## Editing Existing Documents
-
- **Follow all 3 steps in order.**
-
- ### Step 1: Unpack
- ```bash
- python3 scripts/unpack.py document.docx unpacked/
- ```
- Extracts XML, pretty-prints, merges adjacent runs, and converts smart quotes to XML entities (`“` etc.) so they survive editing. Use `--merge-runs false` to skip run merging.
-
- ### Step 2: Edit XML
-
- Edit files in `unpacked/word/`. See XML Reference below for patterns.
-
- **Always use "Claude" as the author** for tracked changes and comments.
+ # Create & edit Word documents with python-docx
- **Use the Edit tool directly for string replacement. Do not write Python scripts.** Scripts introduce unnecessary complexity. The Edit tool shows exactly what is being replaced.
+ Build real `.docx` files with the open-source **python-docx** library
+ (`python3 -c "import docx"`). Work in visible steps; verify before reporting done.
- **CRITICAL: Use smart quotes for new content.** When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
- ```xml
- <!-- Use these entities for professional typography -->
- <w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t>
- ```
- | Entity | Character |
- |--------|-----------|
- | `&#x2018;` | ‘ (left single) |
- | `&#x2019;` | ’ (right single / apostrophe) |
- | `&#x201C;` | “ (left double) |
- | `&#x201D;` | ” (right double) |
+ ## Workflow
+ 1. **Outline** the document: title, sections (headings), body, tables, images.
+ 2. **Write a Python script** (heredoc via the `bash` tool) that assembles the
+ document with python-docx and saves it.
+ 3. **Verify**: reopen with `Document(path)` and assert key paragraphs/headings
+ exist; `print(path)`.
+ 4. **Report** the saved path + a one-line summary.
- **Adding comments:** Use `comment.py` to handle boilerplate across multiple XML files (text must be pre-escaped XML):
- ```bash
- python3 scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;"
- python3 scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0
- ```
- Then add markers to document.xml (see Comments in XML Reference).
+ ## Quality conventions
+ - Use real heading styles (`doc.add_heading('Title', level=0)`, `level=1/2`) so the
+ document has a proper outline — not just bold text.
+ - Tables: `doc.add_table(rows, cols, style='Light Grid Accent 1')`; bold the header row.
+ - Embed images with `doc.add_picture(path, width=Inches(5))`; add a caption paragraph.
+ - Set page size/margins via `doc.sections[0]` when the user specifies them.
+ - Keep styling consistent; when editing an existing file, match its conventions.
- ### Step 3: Pack
+ ## Example
```bash
- python3 scripts/pack.py unpacked/ output.docx --original document.docx
- ```
- Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate false` to skip.
-
- **Auto-repair will fix:**
- - `durableId` >= 0x7FFFFFFF (regenerates valid ID)
- - Missing `xml:space="preserve"` on `<w:t>` with whitespace
-
- **Auto-repair won't fix:**
- - Malformed XML, invalid element nesting, missing relationships, schema violations
-
- ### Common Pitfalls
-
- - **Replace entire `<w:r>` elements**: When adding tracked changes, replace the whole `<w:r>...</w:r>` block with `<w:del>...<w:ins>...` as siblings. Don't inject tracked change tags inside a run.
- - **Preserve `<w:rPr>` formatting**: Copy the original run's `<w:rPr>` block into your tracked change runs to maintain bold, font size, etc.
-
- ---
-
- ## XML Reference
-
- ### Schema Compliance
-
- - **Element order in `<w:pPr>`**: `<w:pStyle>`, `<w:numPr>`, `<w:spacing>`, `<w:ind>`, `<w:jc>`, `<w:rPr>` last
- - **Whitespace**: Add `xml:space="preserve"` to `<w:t>` with leading/trailing spaces
- - **RSIDs**: Must be 8-digit hex (e.g., `00AB1234`)
-
- ### Tracked Changes
-
- **Insertion:**
- ```xml
- <w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
- <w:r><w:t>inserted text</w:t></w:r>
- </w:ins>
- ```
-
- **Deletion:**
- ```xml
- <w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
- <w:r><w:delText>deleted text</w:delText></w:r>
- </w:del>
- ```
-
- **Minimal edits** - only mark what changes:
- ```xml
- <!-- Change "30 days" to "60 days" -->
- <w:r><w:t>The term is </w:t></w:r>
- <w:del w:id="1" w:author="Claude" w:date="...">
- <w:r><w:delText>30</w:delText></w:r>
- </w:del>
- <w:ins w:id="2" w:author="Claude" w:date="...">
- <w:r><w:t>60</w:t></w:r>
- </w:ins>
- <w:r><w:t> days.</w:t></w:r>
- ```
-
- **Rejecting another author's insertion** - nest deletion inside their insertion:
- ```xml
- <w:ins w:author="Jane" w:id="5">
- <w:del w:author="Claude" w:id="10">
- <w:r><w:delText>their inserted text</w:delText></w:r>
- </w:del>
- </w:ins>
- ```
-
- **Restoring another author's deletion** - add insertion after (don't modify their deletion):
- ```xml
- <w:del w:author="Jane" w:id="5">
- <w:r><w:delText>deleted text</w:delText></w:r>
- </w:del>
- <w:ins w:author="Claude" w:id="10">
- <w:r><w:t>deleted text</w:t></w:r>
- </w:ins>
- ```
-
- ### Comments
-
- After running `comment.py` (see Step 2), add markers to document.xml. For replies, use `--parent` flag and nest markers inside the parent's:
-
- ```xml
- <!-- Comment 0 with reply 1 nested inside -->
- <w:commentRangeStart w:id="0"/>
- <w:commentRangeStart w:id="1"/>
- <w:r><w:t>text</w:t></w:r>
- <w:commentRangeEnd w:id="1"/>
- <w:commentRangeEnd w:id="0"/>
- <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
- <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="1"/></w:r>
- ```
-
- ### Images
+ python3 - <<'PY'
+ from docx import Document
+ from docx.shared import Pt, Inches
+ doc = Document()
+ doc.add_heading('Q2 Robotics Budget', level=0)
+ doc.add_heading('Overview', level=1)
+ doc.add_paragraph('Funding target: 120k EUR — status: on track.')
+ doc.add_heading('Line items', level=1)
+ t = doc.add_table(rows=1, cols=3); t.style = 'Light Grid Accent 1'
+ hdr = t.rows[0].cells
+ for i, h in enumerate(['Item', 'Cost', 'Status']):
+ hdr[i].text = h; hdr[i].paragraphs[0].runs[0].font.bold = True
+ for item, cost, status in [('Actuators', '8000', 'ordered'), ('Compute', '3500', 'done')]:
+ c = t.add_row().cells; c[0].text = item; c[1].text = cost; c[2].text = status
+ doc.save('report.docx')
- 1. Add image file to `word/media/`
- 2. Add relationship to `word/_rels/document.xml.rels`:
- ```xml
- <Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
- ```
- 3. Add content type to `[Content_Types].xml`:
- ```xml
- <Default Extension="png" ContentType="image/png"/>
- ```
- 4. Reference in document.xml:
- ```xml
- <w:drawing>
- <wp:inline>
- <wp:extent cx="914400" cy="914400"/> <!-- EMUs: 914400 = 1 inch -->
- <a:graphic>
- <a:graphicData uri=".../picture">
- <pic:pic>
- <pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill>
- </pic:pic>
- </a:graphicData>
- </a:graphic>
- </wp:inline>
- </w:drawing>
+ assert Document('report.docx').paragraphs[0].text == 'Q2 Robotics Budget'
+ print('report.docx')
+ PY
```
- ---
-
- ## Dependencies
-
- - **pandoc**: Text extraction
- - **docx**: `npm install -g docx` (new documents)
- - **LibreOffice**: PDF conversion
- - **Poppler**: `pdftoppm` for images
+ ## Reading / editing existing files
+ - Read text: iterate `Document(path).paragraphs` / `.tables`, or `python3 -m markitdown file.docx` if available.
+ - Edit in place: load, modify runs/paragraphs/tables, `save()` back; preserve tracked
+ changes and comments where present.