skills/law-review-docx/SKILL.md
Use this skill to BUILD a formatted Word document from law review / legal MARKDOWN drafts via the law_review_template + pandoc (footnotes, TOC, styled tables) — NOT the generic 'docx' skill (which edits docx content) and NOT 'docx-render' (which only converts an existing .docx to PDF). Triggers: 'generate a docx', 'create the Word file', 'export to docx', 'build the document', 'compile/finalize the draft', 'build the law review document', 'make a Word version', 'turn my markdown draft into Word', 'make the submission docx', 'apply the law review template'.
npx skillsauth add edwinhu/workflows law-review-docxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Convert markdown drafts into a properly formatted Word document using the law review template via pandoc.
This is the ONLY correct way to build a law-review .docx — never hand-roll
pandoc/soffice; the template, footnote handling, TOC, and table styling all
live in build_docx.py. Agents without the Skill tool (most workflow
subagents) can't invoke this skill — run the script below directly:
uv run python3 ${CLAUDE_SKILL_DIR}/scripts/build_docx.py PROJECT_DIR [--output PATH] [--fix-footnotes]
The script:
.planning/ACTIVE_WORKFLOW.md or PRECIS.mddrafts/*Draft*.md files in section order (Introduction → Parts → Conclusion → Appendix)<!-- include: PATH --> sentinels by inlining file contents (paths must be absolute or ~-expanded)--reference-doc pointing to the law review template--fix-footnotes)To embed externally generated tables or fragments at build time, place a sentinel in the draft:
<!-- include: ~/projects/mirror/data/tables/paper/table2_body.md -->
The preprocessor expands ~, reads the file, and splices its contents inline before pandoc runs. Missing or non-absolute paths emit a visible <!-- MISSING: ... --> placeholder instead of failing silently. For images, use plain pandoc markdown () — no sentinel needed.
If the user doesn't specify a path, detect it from context:
drafts/ and .planning/.planning/ACTIVE_WORKFLOW.md exists and read project_dir from itpaper → actual project directory)The reference template lives at:
${CLAUDE_SKILL_DIR}/../writing-legal/templates/law_review_template.docx
This template defines all styles that pandoc applies:
| Style | Use | Formatting | |-------|-----|------------| | Title | Article title | Bold, small caps, centered | | Heading 1 | Part titles (I., II., III.) | Bold, left-aligned | | Heading 2 | Sections (A., B., C.) | Bold, left-aligned | | Heading 3 | Subsections (1., 2., 3.) | Italic, left-aligned | | Body Text | All body paragraphs | First-line indent | | First Paragraph | After headings | No indent | | Footnote Text | Footnotes | 10pt, single-spaced |
Report the output path, section count, footnote count, and approximate word count. If the user needs further formatting (NOTEREF cross-references, footnote repair from cloud editing), suggest --fix-footnotes or the docx-repair skill.
build_docx.py sets Word's paragraph-level widowControl, which prevents a
paragraph's last line from landing alone on the next page — it does nothing
about a last line holding one or two stray words. Two companion scripts
handle that, after compiling to PDF:
# Report widows (paragraph last lines of 1-2 short words, main column only)
uv run python3 ${CLAUDE_SKILL_DIR}/scripts/check_widows.py OUTPUT.pdf [--max-words N] [--verbose]
# Same detection, then bind the last two words in the offending drafts/*.md
# paragraph with a pandoc non-breaking space. Recompile afterwards.
uv run python3 ${CLAUDE_SKILL_DIR}/scripts/fix_widows.py OUTPUT.pdf PROJECT_DIR [--dry-run]
Run --dry-run first: fix_widows.py edits source markdown, and the fix is
only meaningful against the PDF it was measured on. Iterate compile → check →
fix → recompile.
build_docx.convert_to_pdf() delegates to doc_render.convert(renderer="word"),
which uses Microsoft Word's engine for line-exact layout (best for widow detection)
and faithful tables.
Note on table fidelity: for tables this skill builds, the wrap_cell pass (see
style_tables) already pre-breaks cells with explicit <w:br/> so soffice and
x2t render the grid too (commit ec349c5), and x2t kerning is corrected by
doc_render's GPOS/kern injection + EB-Garamond substitution. So all three
engines are grid-faithful for build-generated tables; Word is preferred for
polish, not required for table integrity. Word matters most for hand-authored
docx whose tables never pass through wrap_cell — LibreOffice collapses such a
table to a single stacked column whenever a cell must auto-wrap (Word/x2t keep the
grid).
Word is GUI-driven, so a detached Claude background job can't drive it directly
(AppleEvents fail with -600 — it's in a non-console GUI session without Word's TCC
grant). doc_render handles this transparently by dispatching the render into a
cmux pane (which lives in the console GUI session and is TCC-granted). One-time
host prerequisites:
automation.socketControlMode ≠ "cmuxOnly" in
~/.config/cmux/cmux.json, then cmux reload-config.Set $DOC_RENDER_NO_CMUX=1 to disable the cmux path (then background jobs fall
back to x2t/LibreOffice). See docs/investigations/2026-06-22_word-render-cmux-dispatch.md.
Symptom. In the compiled DOCX, some footnotes read with a doubled space and wrapping parens around a citation:
see (Griffin, supra note 12; Macey, supra note 12). For proponents...
(note the two spaces before ().
Root cause. Pandoc-citeproc wraps any bracketed parenthetical citation
[@key] or [signal @key] in parens with a leading space when it appears
mid-paragraph inside a footnote body. At the paragraph start the wrap is
suppressed; mid-paragraph it is not. This is native pandoc behavior for
note-style CSLs and cannot be fixed at the CSL level.
Why the natural-looking fix doesn't work. Rewriting source to bare
textual form (@key without brackets) renders cleanly only if every
citation has a locator. For bib entries without locators (books, misc,
many articles), pandoc-citeproc with a note-style CSL emits just a stray
number (1.) because the full cite is supposed to go into a footnote and
there is no footnote to host it (we're already inside one).
Fix. The docx-repair skill's fix_footnotes.py detects and strips
these wraps post-compile. The detector keys on the distinctive XML
signature:
<w:r><w:t xml:space="preserve"> </w:t></w:r> <!-- natural space -->
<w:r><w:t xml:space="preserve"> </w:t></w:r> <!-- EXTRA space -->
<w:r>…<w:t>(Author,</w:t></w:r> <!-- open paren run -->
… citation content …
<w:r>…<w:t>)</w:t></w:r> <!-- close paren (standalone or attached) -->
Author-written explanatory parentheticals ((describing X), (documenting Y))
appear as a single <w:t> (…)</w:t> run and lack the double-whitespace
signature, so they are preserved. build_docx.py runs fix_footnotes.py
automatically when --fix-footnotes is set (the default).
| Action | Why Wrong | Do Instead |
|--------|-----------|------------|
| Running pandoc -o output.docx without --reference-doc | Produces default Calibri formatting that violates journal requirements | Always use the template |
| Manually constructing the DOCX with python-docx or docx-js | Reinvents what the template + pandoc already handle | Run the script |
| Combining markdown without prefixing footnote labels | Causes footnote collisions when multiple sections use [^1] | The script handles this automatically |
development
Build the meeting-level proxy-voting × ownership panel on the WRDS SGE grid — ISS N-PX fund votes reduced to (item × block) direction cells, joined to institutional and mutual-fund ownership. Use when working with risk.voteanalysis_npx, N-PX fund-level votes, ISS→CRSP fund linking, index/passive/active voting blocks, or a proxy-voting panel that needs ownership attached.
development
Use when "CRSP CIZ", "CRSP v2", "CRSP flat file format 2.0", "crsp.dsf_v2 / msf_v2", "StkDlySecurityData", "StkMthSecurityData", "StkSecurityInfoHist", "stocknames_v2", "DlyRet / MthRet / DlyPrc / MthPrc", "SHRCD or EXCHCD equivalent in new CRSP", "SIZ to CIZ migration", "CRSP data after 2024", "CRSP delisting returns", "CRSP cumulative adjustment factors", "CRSP index INDNO / INDFAM", or any CRSP stock/index query where the legacy SIZ column names no longer exist.
development
Use when linking or deduping datasets by entity name rather than a shared key — 'fuzzy match', 'fuzzy name matching', 'entity resolution', 'record linkage', 'match company/person names', 'dedupe entity names', 'name-based join', 'bridge identifiers' (CIK ↔ permno ↔ gvkey ↔ wficn ↔ EIN ↔ personid), or any use of char n-gram TF-IDF, cosine similarity on names, `sparse_dot_topn`, or RapidFuzz at scale.
development
Use when building a publication-quality table in Python — 'regression table', 'results table', 'summary statistics table', 'etable', 'coefplot', 'great_tables', 'GT', 'gt table', 'format a table for the paper', 'export table to LaTeX/HTML', significance stars, spanners, or column formatting for a table headed into a paper, slide deck, or notebook.