create-docx — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited create-docx (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Word documents are generated by writing a JavaScript file that uses the docx npm package, running it with Node, then cleaning up the temp files. The docx package must be installed locally in the same folder as the script - global install does not work with require('docx').
The full docx-js API reference is in the example-skills:docx skill. Load it for syntax on tables, images, headers/footers, tracked changes, and anything not covered here.
Write the JS file to a working location (e.g. Desktop). Output path should be absolute.
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
HeadingLevel, AlignmentType, LevelFormat, BorderStyle, WidthType,
ShadingType, VerticalAlign, PageBreak } = require('docx');
const fs = require('fs');
// ... build doc ...
Packer.toBuffer(doc).then(buf => {
fs.writeFileSync("C:/Users/rosem/Desktop/output.docx", buf);
console.log("Done");
}).catch(err => { console.error(err); process.exit(1); });cd C:/Users/rosem/Desktop
npm init -y
npm install docx
node build-doc.jsRemove-Item -Recurse -Force build-doc.js, package.json, package-lock.json, node_modulesThe validate.py script from example-skills:docx throws a UnicodeEncodeError on Windows consoles (cp1252 can't encode the arrow character it prints). This is a validator bug, not a document problem. Skip validation - open the file in Word to verify instead.
docx-js defaults to A4. Always override:
sections: [{
properties: {
page: {
size: { width: 12240, height: 15840 }, // US Letter
margin: { top: 1080, right: 1080, bottom: 1080, left: 1080 } // 0.75" margins
}
},
children: [...]
}]
// Content width = 12240 - (2 x 1080) = 10080 DXA// Define once in Document
numbering: {
config: [
{ reference: "bullets", levels: [{ level: 0, format: LevelFormat.BULLET, text: "•",
alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
{ reference: "steps", levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.",
alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
]
}
// Use on paragraphs
new Paragraph({ numbering: { reference: "bullets", level: 0 }, children: [...] })Use a separate reference for bullets inside table cells (avoids numbering continuation issues):
{ reference: "cell-bullets", levels: [{ level: 0, format: LevelFormat.BULLET, text: "•",
alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 360, hanging: 180 } } } }] }Both columnWidths on the table AND width on each cell. They must match. Always use WidthType.DXA - never WidthType.PERCENTAGE (breaks in Google Docs). Always use ShadingType.CLEAR - never SOLID (causes black cell backgrounds).
const border = { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC" };
const borders = { top: border, bottom: border, left: border, right: border };
const col1 = 5040, col2 = 5040; // must sum to content width (10080)
new Table({
width: { size: 10080, type: WidthType.DXA },
columnWidths: [col1, col2],
rows: [new TableRow({ children: [
new TableCell({
borders,
width: { size: col1, type: WidthType.DXA },
shading: { fill: "F0F0F0", type: ShadingType.CLEAR },
margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun("Cell content")] })]
}),
]})]
})For documents that will have screenshots inserted manually:
function screenshotBox(description) {
const br = { style: BorderStyle.SINGLE, size: 8, color: "999999" };
const borders = { top: br, bottom: br, left: br, right: br };
return new Table({
width: { size: 10080, type: WidthType.DXA },
columnWidths: [10080],
rows: [new TableRow({ children: [new TableCell({
borders,
width: { size: 10080, type: WidthType.DXA },
shading: { fill: "DEDEDE", type: ShadingType.CLEAR },
margins: { top: 280, bottom: 280, left: 280, right: 280 },
verticalAlign: VerticalAlign.CENTER,
children: [
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 40, after: 60 },
children: [new TextRun({ text: "[ INSERT SCREENSHOT ]", font: "Arial",
size: 20, bold: true, color: "666666" })] }),
new Paragraph({ alignment: AlignmentType.CENTER,
children: [new TextRun({ text: description, font: "Arial",
size: 18, italics: true, color: "666666" })] })
]
})})]
});
}Coloured border box with a bold title and bulleted lines:
function callout(title, lines, fill, borderColor) {
const br = { style: BorderStyle.SINGLE, size: 14, color: borderColor };
const borders = { top: br, bottom: br, left: br, right: br };
return new Table({
width: { size: 10080, type: WidthType.DXA },
columnWidths: [10080],
rows: [new TableRow({ children: [new TableCell({
borders,
width: { size: 10080, type: WidthType.DXA },
shading: { fill, type: ShadingType.CLEAR },
margins: { top: 120, bottom: 120, left: 240, right: 240 },
children: [
new Paragraph({ spacing: { before: 40, after: 80 },
children: [new TextRun({ text: title, font: "Arial", size: 22, bold: true, color: "404040" })] }),
...lines.map(line => new Paragraph({
numbering: { reference: "cell-bullets", level: 0 },
spacing: { before: 40, after: 40 },
children: [new TextRun({ text: line, font: "Arial", size: 22, color: "404040" })]
}))
]
})})]
});
}
// Usage: callout("What to ask:", ["Question one", "Question two"], "FFF2CC", "FFC000")Single-cell tables (code blocks, callout boxes, prompt boxes) follow this structure. The closing sequence is the most common source of syntax errors — get it wrong and Node throws Unexpected token '}'.
return new Table({
width: { size: CW, type: WidthType.DXA }, columnWidths: [CW],
rows: [new TableRow({ children: [new TableCell({ // opens: rows[, TableRow{, children[, TableCell{
borders, width: { size: CW, type: WidthType.DXA },
shading: { fill: "F4F4F4", type: ShadingType.CLEAR },
margins: { top: 120, bottom: 120, left: 200, right: 200 },
children: [/* paragraphs */]
})]}) // closes: TableCell{, TableCell(, children[, TableRow{, TableRow(
] // closes: rows[
}); // closes: Table{, Table(
}Closing sequence: } ) ] } ) then ] on next line, then });. Never write `})})]` — it skips the ] that closes children:[ and crashes at parse time.
| Mistake | Fix |
|---|---|
require('docx') fails after npm install -g | Install locally: cd Desktop && npm init -y && npm install docx |
| Black table cell backgrounds | Use ShadingType.CLEAR not SOLID |
| Table renders incorrectly | Set columnWidths on table AND width on each cell, both in DXA |
WidthType.PERCENTAGE used | Switch to WidthType.DXA - percentage breaks in Google Docs |
| PageBreak causes invalid XML | Wrap it: new Paragraph({ children: [new PageBreak()] }) |
| Bullet shows as literal text | Never new TextRun("• item") - use numbering config |
| Em dashes (--) appear in Word | Do not use -- or — in content - rephrase the sentence instead |
| Validator crashes on Windows | Known encoding bug in validate.py - open in Word instead |
| node_modules left on Desktop | Always run cleanup step after generating |
SyntaxError: Unexpected token } in table helper | Missing ] to close children:[ of TableRow before closing } of TableRow args — use the single-cell table pattern above |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.