Skip to content

Add docx-to-markdown reverse conversion (Tier-2)#16

Merged
llbbl merged 2 commits into
mainfrom
feat/docx-to-markdown
Jul 26, 2026
Merged

Add docx-to-markdown reverse conversion (Tier-2)#16
llbbl merged 2 commits into
mainfrom
feat/docx-to-markdown

Conversation

@llbbl

@llbbl llbbl commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add convertDocxToMarkdown(), converting Word documents to clean GitHub-flavored Markdown via pandoc, with a DocxImportReport that surfaces content pandoc silently drops.
  • Harden docx archive inspection against hostile input and resolve the main document part correctly instead of assuming a fixed path.
  • Move PandocError into its own module and shift fflate to a runtime dependency.

Changes

Docx-to-Markdown Conversion

  • src/docx.ts: New convertDocxToMarkdown() runs pandoc --from docx --to gfm with an explicit --track-changes mode, reusing the existing Markdown postprocessor hook.
  • src/docx.ts: New DocxImportReport surfaces content pandoc drops silently during import — comments, tracked changes and moves, text boxes, headers/footers, footnote-scoped revisions, SmartArt, charts, embedded media, OLE objects, and altChunk references — so unmappable items are never silently dropped.
  • src/docx.ts: The main document part is resolved from _rels/.rels rather than assumed to be word/document.xml.
  • src/docx.ts: The archive reader is bounded against hostile input with per-part, total-bytes, entry-count, and header/footer-count caps, plus CRC-32 verification of every inspected part.

Refactoring

  • src/errors.ts: PandocError moved out of src/pandoc.ts into a new module and re-exported, so the public API surface is unchanged.
  • src/index.ts: Re-export the new docx conversion API and error types.

Dependencies

  • package.json / pnpm-lock.yaml: fflate moved from devDependencies to dependencies, since archive inspection now runs at runtime.

Tests

  • tests/index.test.ts: Coverage for convertDocxToMarkdown(), DocxImportReport, part resolution, and archive-reader bounds/CRC checks.
  • tests/fixtures/golden/roundtrip/: New golden round-trip fixture(s).
  • tests/fixtures/golden/README.md: Documentation for the round-trip fixtures.

Documentation

  • README.md: Document the new docx-to-markdown conversion API.

Test plan

  • pnpm check (biome + tsc + 224 vitest tests + build) passes
  • js-ts-code-reviewer PASS after three review rounds

Closes #6

Add convertDocxToMarkdown(), converting Word documents to clean GitHub-
flavored Markdown via pandoc's `--from docx --to gfm`, with an explicit
`--track-changes` mode and reuse of the existing Markdown postprocessor
hook.

Because the acceptance criterion for this feature is that unmappable
content is never silently dropped, the conversion also produces a
DocxImportReport surfacing anything pandoc discards during import:
comments, tracked changes and moves, text boxes, headers/footers,
footnote-scoped revisions, SmartArt, charts, embedded media, OLE
objects, and altChunk references.

The main document part is resolved by reading `_rels/.rels` rather
than assuming the conventional `word/document.xml` path, so documents
with non-standard part layouts are still handled correctly.

The docx archive reader is hardened against hostile input: per-part
size caps, a total-bytes budget, an entry-count cap, and a
header/footer-count cap, plus CRC-32 verification of every inspected
part.

PandocError has moved from src/pandoc.ts to a new src/errors.ts module
and is re-exported from there, so the public API surface is unchanged.

fflate has moved from devDependencies to dependencies, since archive
inspection now happens at runtime rather than only in tests.

Closes #6
The aggregate-budget test deflated ~572MB of fixture data and took
~4.2s against vitest's 5s default, sitting at 84% of the timeout and
flaking as "Test timed out in 5000ms" on the Node 20.x CI job under
any slightly slower runner. This was a slow fixture, not a code defect.

Both heavy fixtures now derive their size from the constants they
probe (DOCX_MAX_PART_BYTES, DOCX_MAX_TOTAL_BYTES, etc.) via a shared
createRepetitiveXmlPart helper, so each uses the minimum data needed
to trip its limit (572MB -> 135MB). They also compress at level 1
instead of 9 since the archive is discarded immediately, and the two
legitimately slow tests get explicit 30s timeouts instead of relying
on the default.

The aggregate-budget test now asserts all four preconditions that
matter for it to prove anything (per-part under cap, part count under
the header/footer cap, entry count under the archive cap, aggregate
over budget), where it previously asserted only two.

Suite runtime drops from 6.7s to 4.0s. No assertions were weakened.
@llbbl
llbbl merged commit 37fa259 into main Jul 26, 2026
4 checks passed
@llbbl
llbbl deleted the feat/docx-to-markdown branch July 26, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reverse conversion: docx → clean md (Tier 2)

1 participant