fix(docx): avoid numbering generated letter paragraphs

✅ merged · #337 · open-legal-products/mike ← duncanmcqueen/mike · opened 2mo ago by duncanmcqueen · merged 1mo ago by willchen96 · +220-29 across 5 files · ↗ on GitHub

From the PR description

Summary

  • make generated DOCX section numbering opt-in through an optional numberSections tool argument
  • leave letters and other prose documents unnumbered by default
  • stop assigning inferred legal numbers to ordinary body paragraphs
  • preserve explicit bullets and manually typed numbering
  • add DOCX XML regression tests for default and opt-in numbering

Problem

The DOCX generator always configured legal numbering and inferred a numbering level for every paragraph beneath a section heading. This caused ordinary demand-letter prose to render approximately as:

1. DEMAND FOR PAYMENT
1.1 We represent the claimant.
(a) Payment is required within ten days.

A prompt-only fix would be insufficient because the renderer itself applied numbering even when the model did not request it.

Behavior after this change

Documents are unnumbered by default. numberSections: true numbers section headings when explicitly requested or required by a workflow/template, while ordinary prose remains unnumbered. Explicit bullets use Word bullet formatting. Manually typed prefixes such as 1. This reference is intentional. remain literal text when automatic numbering is off.

The dispatcher and renderer both use === true, so missing, null, string, or otherwise malformed values cannot enable numbering accidentally.

Tests

New regression tests inspect word/document.xml (and word/numbering.xml for bullets) inside generated DOCX bytes and cover:

  • demand-letter headings and body prose unnumbered by default
  • opt-in legal numbering on headings only
  • preservation of manually typed numbering when numbering is off
  • explicit bullets rendered as bullets rather than legal clauses

Validation completed (rebased onto current main):

  • tsc --noEmit in backend - clean
  • npm test in backend - 711 passed, 24 skipped
  • The four new documentGeneration.test.ts cases all pass

Rebased onto current main

Re-verified the bug has not been addressed upstream in the meantime: documentOps.ts on main still calls legalNumbering(inferredLevel) for inferred paragraphs and still configures the numbering set unconditionally, and numberSections appears nowhere in backend/src.

The rebase was clean. Worth noting explicitly because it touches neighbouring code: this does not conflict with the docx text-value work merged in #328 - that PR's preserves numeric-looking text through an unrelated edit regression test still passes alongside the new numbering tests.

No database, frontend, provider, PDF reconstruction, or product-module changes are included.

Our analysis

Make DOCX section numbering opt-in — read the full analysis →

Think the analysis missed something the PR description covers?

Commits in this PR (1)

SHA Subject Author Date
8baa5529 fix(docx): avoid numbering generated letter paragraphs duncanmcqueen 2026-08-21 ↗ GitHub
commit body
The DOCX generator configured legal numbering unconditionally and
inferred a numbering level for every paragraph under a section heading,
so ordinary demand-letter prose came out as "1. DEMAND FOR PAYMENT /
1.1 We represent the claimant. / (a) Payment is required...". Prompting
alone could not fix it: the renderer numbered the output whether or not
the model asked for numbering.

- Numbering is now opt-in via an optional numberSections tool argument.
  Letters and other prose render unnumbered by default.
- When requested, only section headings are numbered; body prose is not.
- Explicit bullets keep Word bullet formatting, and manually typed
  prefixes like "1." stay literal text when automatic numbering is off.
- Both the dispatcher and the renderer test `=== true`, so a missing,
  null, or malformed value cannot switch numbering on by accident.

Adds DOCX XML regression tests that read word/document.xml (and
word/numbering.xml for bullets) out of the generated bytes.

Rebased onto main; no conflicts with the docx text-value work in #328,
whose numeric-text regression test still passes alongside these.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-337.md from inside the repo you want the changes in.

⬇ Download capture-pull-337.md