Skip to content
This document describes intent at the time it was written. It is part of the design record, not user documentation, and may not reflect the current behaviour of the code.

003 - convert command

Purpose: mdd convert recursively converts .docx files to Markdown (.docx.md) using Docling for body content and python-docx for metadata extraction.

Status: Implemented (2026-05-07)

Ported from lsimons-auto spec-019 (md action). See that spec for full algorithm detail.

  • mdd convert <path> walks <path>, writes <original>.docx.md in-place
  • --dest-dir <dir> mirrors source tree into <dir>
  • Incremental: skip files whose .md is newer than the .docx (unless --force)
  • --dry-run prints what would be converted without writing
  • --file <path> converts a single file regardless of extension
  • Progress printed per file: [N/M] Converting: <relpath>
  • Non-zero exit if any conversion fails; continues and reports all failures
  • Registered in src/mdd/commands/convert.py via the register(subparsers, parents) convention defined in S35; the pre-S35 cmd_convert(args) shape has been superseded.
  • extract_title, extract_metadata use python-docx; convert_body uses Docling
  • Docling DocumentConverter instantiated once per invocation (singleton)
  • Atomic write: write to .md.tmp, then rename. The writer refuses to write through a symlink at the destination, at the .md.tmp sibling, or at the <name>-attachments/ directory; the conversion fails instead of following the link.
  • The output base the operator names (--dest-dir, or the source directory for in-place output) is trusted and may itself be a symlink. Every directory mdd creates or writes into below it (mirrored sub-directories, <name>-attachments/) must be a real directory; a symlink there fails that file’s conversion. Converters take the base as a root argument for this.
  • The directory walk skips symlinked entries with a warning, so a symlinked .docx/.pptx/.pdf in the tree is not read. A file named directly (positional file argument or --file) is operator-chosen: it is followed even when it is a symlink, with a warning.
  • Dependencies: docling>=2, python-docx>=1
  • Multi-person fields (Participants, Invitees, Absent): nested bullet list
  • Single-value meeting fields (Location): - **Key:** Value
  • Document-meta fields (For, Date, Author, Status): - Key: Value (no bold)
  1. html.unescape()
  2. Strip Word-generated TOC
  3. Strip empty/artifact headings
  4. Strip boilerplate sections (Meeting Logistics)
  5. Strip boilerplate lines (JIRA board bullet)

Site built 2026-10-26.