Constructdocs
section · guides

Document I/O

Intake and export formats, engines, fidelity tiers, and optional tooling for Construct document pipelines.

7 min read·guides / reference / document-io

Construct treats document I/O as an optional capability with two independent halves (ADR-0024). Both degrade gracefully when tooling is absent.

Pipeline overview

┌─────────────────────────────────────────────────────────────────────────┐
│ IN (many formats)          AUTHORING (canonical)         OUT (many)     │
│ PDF, DOCX, PPTX, XLSX,  →  typed markdown artifacts  →  PDF, DOCX,    │
│ email, AV, plain text,       under templates/docs/        DOC, HTML,    │
│ transcripts, calendar…       + .construct/knowledge/ ingest       deck, PPTX,  │
│                                                            RTF, EPUB…   │
└─────────────────────────────────────────────────────────────────────────┘
StageCommandInput formatsOutput
Ingestconstruct ingestPDF, Office, email, AV, text, transcripts, calendar, …Normalized markdown + optional assets
Authorspecialists / workflowsTyped markdown artifacts (PRD, ADR, RFC, …)
Exportconstruct export / construct publishMarkdown artifact (.md / .mdx)PDF, DOCX, DOC, HTML, deck, PPTX, RTF, ODT, EPUB, TeX, TXT, MD copy

Export does not accept PDF or DOCX as input — those are ingest sources. Once ingested or authored as markdown, artifacts export to any supported distributable format.

Ingestion (construct ingest)

Strategies

StrategyConfigEngineWhen to use
adapter (default)ingest.strategyDocling sidecar (high) or unpdf/mammoth (fast)Local-first; default for most projects
provideringest.strategy=providerVision-capable LLMWhen provider routing is configured
docling-remoteingest.strategy=docling-remote + DOCLING_SERVE_URLDocling Serve HTTPZero local ML footprint; sends files off-box

Fidelity tiers (adapter)

TierFlagPDF/DOCX engineOffice (xlsx/pptx/odt)
high (default)--fidelity=highDocling Python sidecar via uvDocling
fast--fidelity=fastunpdf / mammoth (optional npm deps)Fail loud (OFFICE_REQUIRES_DOCLING)

--legacy-extractor is a deprecated alias for --fidelity=fast.

Docling provisions on first use into a machine-shared ~/.construct/runtime/docling/.venv (~1.5 GB) — one venv per machine, shared by every project, not one per project. Eager provision: construct install --with-docling.

On docling timeout or failure, Construct falls back to node-native extraction (unpdf/mammoth) for PDF/DOCX. Office formats without a Node backend require docling.

Supported intake formats

CategoryExtensionsPrimary engineNotes
Plain text / code.md, .txt, .json, .yaml, .csv, .html, .xml, source filesUTF-8 read
Transcripts.vtt, .srt, .lrcPure JS parserStructured cues
Calendar.icsPure JS RFC5545
Email.eml, .msgEML parserAttachments listed, not extracted
PDF.pdfdocling / unpdfImages externalized with docling
Word.docx, .docdocling / mammothLegacy .doc via docling only
Excel.xlsx, .xls, .odsdoclingFast tier requires docling
Presentation.pptx, .ppt, .odpdoclingFast tier requires docling
Rich text.rtfdocling
Images.png, .jpg, .jpeg, .tif, .tiff, .bmp, .webpdocling visionFast tier requires docling (IMAGE_REQUIRES_DOCLING)
Apple iWork.pages, .numbers, .keydocling
Audio/video.mp3, .wav, .mp4, .mov, …whisper.cppRequires whisper-cli; ASR_REQUIRED without backend

droppedInfo

Every extraction path returns droppedInfo: { kind, count, reason, recoverable }[] so silent loss is observable. Pass --strict to fail when any recoverable drop occurs.

MCP

ToolPipeline
ingest_documentFull ingest (docling / node-native / strategies)
extract_document_textAsync: node-native first; docling for office/PDF when needed

Export (construct export / construct publish)

Per ADR-0024. Input: a markdown artifact. Output: one of the formats below. Engines are discovered at runtime — never bundled in core npm install.

Output formats

FormatEngineBrandedRequired tooling
pdfPandoc → TypstYes — construct-brand.typ + type layoutspandoc, typst
htmlPandoc + construct-web.htmlYespandoc
deckPandoc + construct-deck.htmlYespandoc
pptxpptxgenjsYes — brand tokenspptxgenjs (optional npm dep)
docxPandoc + reference docPartial — construct-reference.docx when presentpandoc
docPandoc → DOCX → LibreOfficeSame as DOCX intermediatepandoc, LibreOffice (soffice)
odppptxgenjs → LibreOfficeSame as PPTX intermediatepptxgenjs, LibreOffice (soffice)
rtf, odt, epub, tex, txtPandoc defaultsNopandoc
htmlfragRichDocument HTML fragmentN/A — copy/paste target
md, mdxCopy sourceN/A

Legacy .doc export: Pandoc writes branded DOCX, then LibreOffice headless down-converts. Override binary with CONSTRUCT_LIBREOFFICE_BIN. Cross-platform (macOS, Linux, Windows) when LibreOffice is installed.

RichDocument export adapters (ADR-0073)

Per ADR-0073, lib/rich-document-export.mjs's exportRichDocument({ doc, format, outputPath }) exports the RichDocument IR to every format above. Its canonical serialization is HTML: for typeset and office targets the IR is serialized to HTML and handed to the same engines via exportMarkdown's inputFormat: 'html' path, rather than round-tripping through markdown. Two targets have no engine and are written directly — md/mdx from the RichDocument→markdown writer, and htmlfrag, the copy/paste fragment whose purpose is that merged table cells, figure/caption pairs, and callouts survive a clipboard paste that a markdown fragment would flatten. odp (OpenDocument Presentation) builds a pptxgenjs deck first, then LibreOffice down-converts. A missing engine returns the same actionable diagnostic detect() reports and is never counted as a certified pass; lib/export-validate.mjs validates each produced file — PDF page count, Office/OpenDocument/EPUB zip structure and slide counts, HTML DOM shape, and local reference integrity.

Rich-media asset manifest (ADR-0073)

lib/document-assets.mjs derives an asset manifest from the RichDocument IR — it is not a parallel structure. buildAssetManifest(doc, { baseDir }) walks every figure/media/diagram block (including those nested in lists and callouts) and records each asset's role (photo / screenshot / diagram), local-vs-remote ref, resolved absolute path, sha256 content hash, byte size, MIME type, dimensions, caption, alt text, source ref, and embed/link policy. It is generated on importconstruct ingest writes a \<name>.assets.json sidecar beside the markdown — and consumed on export: exportRichDocument validates the manifest before any engine runs (a broken local media ref fails closed and blocks the export, writing no output) and rewrites local refs to absolute paths so a Pandoc/LibreOffice pass in a temp working directory still finds and embeds them. Local raster assets inline as data URIs in self-contained HTML; captions and alt text survive to whatever the target format preserves.

Certified I/O matrix

construct certify document-io            # local mode: missing engines skip gracefully
construct certify document-io --certified   # release mode: a skipped format is a hard failure
npm run certify:document-io                 # the certified profile

lib/certification/document-io-matrix.mjs exports a realistic RichDocument fixture (table, figure with caption/alt over a real raster asset, code, callout, diagram) to every output format and validates each. Local mode honours the graceful-degradation contract — a format whose engine is absent is skipped, not failed. Certified mode is the release contract — every declared format must have its engine (Pandoc, Typst, LibreOffice headless, pptxgenjs) present and must produce a file its validator accepts; a format skipped for a missing tool fails the run. Each row names the exact format, engine set, and validation detail. Intake-format coverage is asserted separately by the fixture catalog (lib/certification/document-io-fixtures.mjs).

Branded deck preview (local only)

Regenerate into .tmp/distribution-examples/ for local review:

npm run examples:distribution
open .tmp/distribution-examples/index.html

Single deck (legacy):

npm run examples:deck
OutputPath after generate
Gallery index.tmp/distribution-examples/index.html
PRD / ADR / research / runbook / strategy PDFs.tmp/distribution-examples/\<id>.pdf
Platform PRD demo (cockpit + PDF scroll).tmp/distribution-examples/agentic-platforms-prd.mp4 (Playwright recording agentic-platforms-prd)
HTML slide deck.tmp/distribution-examples/deck-deck.html
PowerPoint.tmp/distribution-examples/deck.pptx
Sourcesexamples/distribution/sources/

Publish templates by artifact type

Typed artifacts map to PDF layouts (export only affects PDF/deck/HTML branding depth):

LayoutArtifact types
construct-prd.typprd, prd-platform, prd-business, meta-prd, strategy, runbook, memo, one-pager, …
construct-research.typresearch-brief, evidence-brief, signal-brief, research-finding
construct-decision.typadr, rfc, rfc-platform
construct-pdf.typFallback for all other manifest types

Project override: .construct/publish-theme.typ.

Diagrams at export

Fenced d2 and mermaid blocks render via vendored pandoc-ext/diagram.lua when --figures is set. Requires d2 or graphviz dot, and mmdc for Mermaid.

Tooling detection

construct tools detect [--json] [--figures]

Reports export (Pandoc, Typst, LibreOffice for .doc, pptxgenjs, deck template), ingest (docling venv, whisper, node-native deps), and figure binaries. See diagram-and-demo cookbook.

Related ADRs

  • ADR-0024 — optional external tooling
  • ADR-0036 — docling sidecar default; remote opt-in
  • ADR-0001 — no heavy deps in core install

Brand consistency

Distribution exports, dashboard, supported hosts, and wireframe scaffolds share the Construct brand contract documented in branding.md. Visual tokens live in lib/brand-tokens.mjs (monochrome ink ramp, Space Grotesk / JetBrains Mono). PPTX exports embed bundled TTF cuts via lib/brand-fonts.mjs when pptx-embed-fonts is installed.