format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: docx
|
||||
description: "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of \"Word doc\", \"word document\", \".docx\", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a \"report\", \"memo\", \"letter\", \"template\", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation."
|
||||
description: 'Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of "Word doc", "word document", ".docx", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a "report", "memo", "letter", "template", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.'
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
---
|
||||
|
||||
@@ -12,10 +12,10 @@ 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 |
|
||||
| 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 |
|
||||
|
||||
### Converting .doc to .docx
|
||||
@@ -58,18 +58,49 @@ python scripts/accept_changes.py input.docx output.docx
|
||||
Generate .docx files with JavaScript, then validate. 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));
|
||||
```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));
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
After creating the file, validate it. If validation fails, unpack, fix the XML, and repack.
|
||||
|
||||
```bash
|
||||
python scripts/office/validate.py doc.docx
|
||||
```
|
||||
@@ -79,28 +110,33 @@ python scripts/office/validate.py doc.docx
|
||||
```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
|
||||
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
|
||||
},
|
||||
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins
|
||||
}
|
||||
},
|
||||
children: [
|
||||
/* content */
|
||||
],
|
||||
},
|
||||
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 |
|
||||
| Paper | Width | Height | Content Width (1" margins) |
|
||||
| ------------ | ------ | ------ | -------------------------- |
|
||||
| US Letter | 12,240 | 15,840 | 9,360 |
|
||||
| A4 (default) | 11,906 | 16,838 | 9,026 |
|
||||
|
||||
**Landscape orientation:** docx-js swaps width/height internally, so pass portrait dimensions and let it handle the swap:
|
||||
|
||||
```javascript
|
||||
size: {
|
||||
width: 12240, // Pass SHORT edge as width
|
||||
@@ -117,22 +153,36 @@ Use Arial as the default font (universally supported). Keep titles black for rea
|
||||
```javascript
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default
|
||||
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 } },
|
||||
]
|
||||
{
|
||||
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")] }),
|
||||
]
|
||||
}]
|
||||
sections: [
|
||||
{
|
||||
children: [
|
||||
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun('Title')] }),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -140,29 +190,53 @@ const doc = new Document({
|
||||
|
||||
```javascript
|
||||
// ❌ WRONG - never manually insert bullet characters
|
||||
new Paragraph({ children: [new TextRun("• Item")] }) // BAD
|
||||
new Paragraph({ children: [new TextRun("\u2022 Item")] }) // BAD
|
||||
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 } } } }] },
|
||||
]
|
||||
{
|
||||
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")] }),
|
||||
]
|
||||
}]
|
||||
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
|
||||
@@ -177,7 +251,7 @@ const doc = new Document({
|
||||
```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 border = { style: BorderStyle.SINGLE, size: 1, color: 'CCCCCC' };
|
||||
const borders = { top: border, bottom: border, left: border, right: border };
|
||||
|
||||
new Table({
|
||||
@@ -189,14 +263,14 @@ new Table({
|
||||
new TableCell({
|
||||
borders,
|
||||
width: { size: 4680, type: WidthType.DXA }, // Also set on each cell
|
||||
shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID
|
||||
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")] })]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
children: [new Paragraph({ children: [new TextRun('Cell')] })],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
**Table width calculation:**
|
||||
@@ -211,6 +285,7 @@ columnWidths: [7000, 2360] // Must sum to table width
|
||||
```
|
||||
|
||||
**Width rules:**
|
||||
|
||||
- **Always use `WidthType.DXA`** — never `WidthType.PERCENTAGE` (incompatible with Google Docs)
|
||||
- Table width must equal the sum of `columnWidths`
|
||||
- Cell `width` must match corresponding `columnWidth`
|
||||
@@ -222,49 +297,59 @@ columnWidths: [7000, 2360] // Must sum to table width
|
||||
```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
|
||||
})]
|
||||
})
|
||||
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()] })
|
||||
new Paragraph({ children: [new PageBreak()] });
|
||||
|
||||
// Or use pageBreakBefore
|
||||
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
|
||||
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" })
|
||||
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
|
||||
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 */
|
||||
],
|
||||
},
|
||||
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
|
||||
@@ -291,9 +376,11 @@ sections: [{
|
||||
**Follow all 3 steps in order.**
|
||||
|
||||
### Step 1: Unpack
|
||||
|
||||
```bash
|
||||
python scripts/office/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
|
||||
@@ -305,36 +392,44 @@ Edit files in `unpacked/word/`. See XML Reference below for patterns.
|
||||
**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.
|
||||
|
||||
**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’s a quote: “Hello”</w:t>
|
||||
```
|
||||
| Entity | Character |
|
||||
|--------|-----------|
|
||||
| `‘` | ‘ (left single) |
|
||||
|
||||
| Entity | Character |
|
||||
| ---------- | ----------------------------- |
|
||||
| `‘` | ‘ (left single) |
|
||||
| `’` | ’ (right single / apostrophe) |
|
||||
| `“` | “ (left double) |
|
||||
| `”` | ” (right double) |
|
||||
| `“` | “ (left double) |
|
||||
| `”` | ” (right double) |
|
||||
|
||||
**Adding comments:** Use `comment.py` to handle boilerplate across multiple XML files (text must be pre-escaped XML):
|
||||
|
||||
```bash
|
||||
python scripts/comment.py unpacked/ 0 "Comment text with & and ’"
|
||||
python scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0
|
||||
python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author" # custom author name
|
||||
```
|
||||
|
||||
Then add markers to document.xml (see Comments in XML Reference).
|
||||
|
||||
### Step 3: Pack
|
||||
|
||||
```bash
|
||||
python scripts/office/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
|
||||
@@ -355,6 +450,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
|
||||
### 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>
|
||||
@@ -362,6 +458,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
|
||||
```
|
||||
|
||||
**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>
|
||||
@@ -371,6 +468,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
|
||||
**Inside `<w:del>`**: Use `<w:delText>` instead of `<w:t>`, and `<w:delInstrText>` instead of `<w:instrText>`.
|
||||
|
||||
**Minimal edits** - only mark what changes:
|
||||
|
||||
```xml
|
||||
<!-- Change "30 days" to "60 days" -->
|
||||
<w:r><w:t>The term is </w:t></w:r>
|
||||
@@ -384,6 +482,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
|
||||
```
|
||||
|
||||
**Deleting entire paragraphs/list items** - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add `<w:del/>` inside `<w:pPr><w:rPr>`:
|
||||
|
||||
```xml
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
@@ -397,9 +496,11 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
|
||||
</w:del>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
Without the `<w:del/>` in `<w:pPr><w:rPr>`, accepting changes leaves an empty paragraph/list item.
|
||||
|
||||
**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">
|
||||
@@ -409,6 +510,7 @@ Without the `<w:del/>` in `<w:pPr><w:rPr>`, accepting changes leaves an empty pa
|
||||
```
|
||||
|
||||
**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>
|
||||
@@ -448,14 +550,19 @@ After running `comment.py` (see Step 2), add markers to document.xml. For replie
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user