Skip to content

feat: add OpenCode rich document reader - #1

Merged
ddbaron merged 7 commits into
mainfrom
fm/opencode-reader-restart-m3
Aug 14, 2026
Merged

feat: add OpenCode rich document reader#1
ddbaron merged 7 commits into
mainfrom
fm/opencode-reader-restart-m3

Conversation

@ddbaron

@ddbaron ddbaron commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Intent

Continue delivery of the public OpenCode rich-document reader at https://github.com/ddbaron/opencode-rich-document-reader as a continuation of the existing user-facing DOCX, ODT, and PPTX reader work. Preserve the implementation and committed fixes already present on fm/opencode-reader-restart-m3 at dcd8a29; do not reimplement the reader, discard the six approved pipeline fixes, retain the cancelled 3dbdfa5 fix, add ancestor-symlink security machinery, or create duplicate work. Validate and deliver this non-default branch through the configured no-mistakes pipeline with yolo off, including tests, documentation, lint, publication, PR, and CI; handle all findings through no-mistakes and never hand-edit the project during an active run. Keep origin at git@github.com:ddbaron/opencode-rich-document-reader.git, do not push or merge the default branch, and finish at a green CI-ready PR. Captain clarification: approve RDR-001 as-is if it appears; RDR-006 and RDR-007 fixes are already included. Escalate any genuinely new ask-user decision rather than choosing a new product or security scope.

What Changed

  • Adds the read_rich_document OpenCode tool for converting DOCX, ODT, and PPTX files into structure-preserving Markdown with document context, warnings, and metadata.
  • Extracts embedded media into isolated temporary directories, exposes a media index, and attaches only explicitly selected supported images.
  • Adds project-boundary and symlink checks, archive/table limits, executable DOCX/ODT/PPTX fixtures and tests, package metadata, and GitHub Actions typecheck/test coverage.

Risk Assessment

✅ Low: The reviewed changes are bounded to additive WebP support and a semantics-preserving linear media-selector lookup, with no material source risks found.

Testing

Targeted executable tests and direct public-tool verification exercised structure-preserving Markdown, media indexing and selection, format/error handling, and concurrent ancestor-symlink rejection; all completed successfully with no transient worktree artifacts. The reviewer-visible evidence is a CLI transcript because this plugin has no rendered UI surface, so no screenshot applies.

Evidence: Public read_rich_document end-to-end transcript
=== structure.docx ===
title: Read structure.docx
format: docx
markdown:
---
---

## Section 1 {#section-1}

# Project Overview {#project-overview}

DOCX introduction.

- First bullet

[Open reference](https://example.com/reference)


| Header | Value |
|  ---  |  ---  |
| Row | 42 |



Section boundary.

## Section 2 {#section-2}

# Second Section {#second-section}

## Embedded media

| Label | Type | Original attachment | MIME type | Temporary path | Location |
| --- | --- | --- | --- | --- | --- |
| `media-1` | image | image1.png | image/png | /var/folders/p9/clm2b7rd78d_3y7gydm7l7gc0000gn/T/opencode-rich-document-LovGut/media-1.png | Section 1 - Section: Project Overview |

## Conversion warnings

- None reported.
media index:
[
  {
    "label": "media-1",
    "originalName": "image1.png",
    "mimeType": "image/png",
    "location": "Section 1 - Section: Project Overview"
  }
]
=== structure.odt ===
title: Read structure.odt
format: odt
markdown:
---
---

# ODT Section {#odt-section}

ODT introduction.

ODT bullet

<a id="summary"></a>

| ODT Header | ODT Value |
|  ---  |  ---  |

## Embedded media

| Label | Type | Original attachment | MIME type | Temporary path | Location |
| --- | --- | --- | --- | --- | --- |
| `media-1` | image | image1.png | image/png | /var/folders/p9/clm2b7rd78d_3y7gydm7l7gc0000gn/T/opencode-rich-document-2sb1MZ/media-1.png | Section: ODT Section |

## Conversion warnings

- None reported.
media index:
[
  {
    "label": "media-1",
    "originalName": "image1.png",
    "mimeType": "image/png",
    "location": "Section: ODT Section"
  }
]
=== slides.pptx ===
title: Read slides.pptx
format: pptx
markdown:
---
---

## Slide 1 {#slide-1}


---

# PPTX Slide One {#pptx-slide-one}

- Slide bullet


> **Note:** Speaker notes

## Embedded media

| Label | Type | Original attachment | MIME type | Temporary path | Location |
| --- | --- | --- | --- | --- | --- |
| `media-1` | image | image1.png | image/png | /var/folders/p9/clm2b7rd78d_3y7gydm7l7gc0000gn/T/opencode-rich-document-LpI8gc/media-1.png | Slide 1 - Section: PPTX Slide One |
| `media-2` | chart | chart1.xml | application/vnd.openxmlformats-officedocument.drawingml.chart+xml | /var/folders/p9/clm2b7rd78d_3y7gydm7l7gc0000gn/T/opencode-rich-document-LpI8gc/media-2.xml | Slide 1 - Section: PPTX Slide One |

## Conversion warnings

- None reported.
media index:
[
  {
    "label": "media-1",
    "originalName": "image1.png",
    "mimeType": "image/png",
    "location": "Slide 1 - Section: PPTX Slide One"
  },
  {
    "label": "media-2",
    "originalName": "chart1.xml",
    "mimeType": "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
    "location": "Slide 1 - Section: PPTX Slide One"
  }
]
=== structure.docx media=media-1 ===
title: Read structure.docx
format: docx
markdown:
---
---

## Section 1 {#section-1}

# Project Overview {#project-overview}

DOCX introduction.

- First bullet

[Open reference](https://example.com/reference)


| Header | Value |
|  ---  |  ---  |
| Row | 42 |



Section boundary.

## Section 2 {#section-2}

# Second Section {#second-section}

## Embedded media

| Label | Type | Original attachment | MIME type | Temporary path | Location |
| --- | --- | --- | --- | --- | --- |
| `media-1` | image | image1.png | image/png | /var/folders/p9/clm2b7rd78d_3y7gydm7l7gc0000gn/T/opencode-rich-document-DsdtAD/media-1.png | Section 1 - Section: Project Overview |

## Conversion warnings

- None reported.
media index:
[
  {
    "label": "media-1",
    "originalName": "image1.png",
    "mimeType": "image/png",
    "location": "Section 1 - Section: Project Overview"
  }
]
attachments: [{"type":"file","mime":"image/png","filename":"media-1.png"}] 
Evidence: Concurrent symlink safety result
Both concurrent focused processes reported `rejects lexical path escapes and symlinks that resolve outside the project` as ok and exited with `A=0 B=0`.
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (6m38s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ src/media.ts:16 - The new selector allowlist omits image/webp, even though officeparser 7.5.1 emits valid WebP attachments with that MIME; selecting the listed media label therefore throws MEDIA_SELECTOR_INVALID for an otherwise supported image. Confirm that WebP is intentionally unsupported or include/document the supported MIME set.
  • ⚠️ src/media.ts:203 - The public media array is unbounded, while each selector performs a linear records.find followed by a linear selected.some, allowing a large caller-supplied selector list to force quadratic work against up to 4096 attachments. Resolve selectors through a map and track selected labels with a Set, or bound the array at the schema boundary.

🔧 Fix: Support WebP and linearize media selector resolution
✅ Re-checked - no issues remain.

🔧 **Test** - 1 issue found → auto-fixed ✅
  • ⚠️ test/read-rich-document.test.ts:269 - Running two copies of the symlink-escape test concurrently caused a fixture collision because both use the fixed sibling path outside.docx; one process reported Document not found instead of the expected symlink escape. The same test passed serially, so this is test-fixture flakiness rather than a product failure. Use a unique outside fixture path if concurrent test execution is expected.
  • node --experimental-strip-types --test --test-name-pattern='extracts DOCX structure|extracts ODT structure|retains PPTX slide|attaches only explicitly|attaches selected WebP|leaves the source' test/read-rich-document.test.ts
  • node --experimental-strip-types --test --test-name-pattern='rejects lexical path escapes and symlinks that resolve outside the project' test/read-rich-document.test.ts
  • node --experimental-strip-types --test --test-name-pattern='associates media after a DOCX section boundary|labels media from headers, footers|rejects unsupported, missing|rejects an unknown media selector|rejects image-labeled|rejects parser-truncated|rejects oversized tables' test/read-rich-document.test.ts
  • Registered read_rich_document through the plugin, read generated DOCX/ODT/PPTX fixtures, selected an image attachment, and exercised escaping and in-project ancestor symlinks; transcript captured at the evidence artifact path.
  • git status --short --untracked-files=all

🔧 Fix: Made symlink fixture paths unique; concurrent focused tests pass
✅ Re-checked - no issues remain.

  • node --experimental-strip-types --test --test-name-pattern='extracts DOCX structure|extracts ODT structure|retains PPTX slide' test/read-rich-document.test.ts
  • node --experimental-strip-types --test --test-name-pattern='attaches only explicitly selected image media|attaches selected WebP image media' test/read-rich-document.test.ts
  • Two concurrent invocations of node --experimental-strip-types --test --test-name-pattern=&#39;rejects lexical path escapes and symlinks that resolve outside the project&#39; test/read-rich-document.test.ts
  • node --experimental-strip-types --test --test-name-pattern='rejects unsupported, missing, and malformed documents with useful errors|rejects an unknown media selector instead of attaching every image' test/read-rich-document.test.ts
  • Direct invocation of the registered RichDocumentReaderPlugin read_rich_document.execute against generated DOCX, ODT, and PPTX fixtures, including selected media-1 attachment output.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@ddbaron
ddbaron merged commit 9ff813d into main Aug 14, 2026
2 checks passed
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.

1 participant