Skip to content

feat: add outline_document tool projecting a document's table of contents - #198

Merged
Mearman merged 4 commits into
mainfrom
feat/outline-tool
Aug 19, 2026
Merged

feat: add outline_document tool projecting a document's table of contents#198
Mearman merged 4 commits into
mainfrom
feat/outline-tool

Conversation

@Mearman

@Mearman Mearman commented Aug 18, 2026

Copy link
Copy Markdown
Member

Adds a tenth tool module, outline_document: reads any source document through documents.js's DocumentConverter port, takes the tree-form DocumentPackage the conversion reports on ConversionResult.package, and runs document-outline.js's buildOutline over it. Groups carry { text, level, children } for headings, list items, slides, sheets, and draw pages; leaves carry { kind, text }. The internal conversion target is chosen per source format to preserve the source's own content kind and avoid a PDF layout pass wherever the matrix allows one.

Follows the existing per-tool module convention under src/tools/, registered from src/server.ts alongside the other nine tools, with unit tests and a smoke-test update.

document-outline.js 1.0.0 is on npm now, so this declares it as a real dependency (pnpm add document-outline.js@^1.0.0) in place of the hand-copied node_modules/document-outline.js the tool was originally developed against. That copy was invisible to package.json/pnpm-lock.yaml, and an import rolldown can't resolve through dependencies isn't external — so tsdown was inlining the whole of document-outline.js, and through it document-schema.js, straight into dist/index.js and dist/bin.js, duplicating the document-schema.js copy documents.js already brings in as a proper external dependency. Same duplicate-install hazard from-package.ts's error handling already narrows by error.name to survive.

The build log is the proof it's actually fixed rather than just building. Before, every one of the three outputs printed:

Detected dependencies in bundle:
- document-schema.js
- document-outline.js

Now that section is gone entirely, and the outputs shrink accordingly — dist/index.js 71.95 kB → 42.12 kB, dist/bin.js 72.08 kB → 42.25 kB, dist/index.cjs 74.51 kB → 43.24 kB. dist/index.js and dist/bin.js carry a bare import { buildOutline, isOutlineNode, outlineLeafText } from "document-outline.js", and dist/index.cjs the matching require.

pnpm-workspace.yaml gains document-outline.js in minimumReleaseAgeExclude, as a bare package name like the family's other sibling packages already listed there. minimumReleaseAge stays at 60 — that's the one CI's --frozen-lockfile install actually respects (it ignores the exclude list), so a CI run started inside the hour after document-outline.js's publish fails the age check until that window elapses, and just needs rerunning afterwards.

Rebased onto current main. Typecheck, lint, unit tests, workers tests, smoke tests, and the build all pass locally.

Refs ExaDev/document-schema.js#20, ExaDev/document-outline.js#2.

Generated by Claude Code

…ents

The server gains a tenth tool module: outline_document reads any source
document through documents.js's DocumentConverter port, takes the
tree-form DocumentPackage the conversion reports on
ConversionResult.package, and runs document-outline.js's buildOutline
over it, answering the TOC as structured JSON an MCP client renders
directly -- groups carry { text, level, children } for headings, list
items, slides, sheets, and draw pages; leaves carry { kind, text }.

The internal conversion target is chosen per source format to preserve
the source's own content kind (a projection conversion such as
docx-to-csv would outline a spreadsheet projection of a wordprocessing
document, not the document), and to avoid a PDF layout pass wherever
the matrix allows one. odf is the one format whose only conversion is
to pdf, and a pdf source's content view is itself a read of the layout
it already records, not a new layout pass.

A leaf's kind label comes from a leafKind helper rather than a kind
property read: two leaf classes in document-schema.js carry no kind
literal at all (a formula, identified structurally by its mathml
payload, and the sheet-anchored standalone ContentEmbeddedObject), so
the label falls back to the same structural discrimination idiom
document-outline.js's own outlineLeafText uses.
…he bundle

outline_document imported buildOutline/outlineLeafText from a copy of
document-outline.js placed in node_modules by hand, invisible to
package.json and pnpm-lock.yaml.
An import rolldown cannot resolve through `dependencies` is not external,
so tsdown inlined the whole of document-outline.js into dist/index.js and
dist/bin.js, and with it document-schema.js, which document-outline.js
depends on -- a second copy of the schema types in a build that already
reaches document-schema.js through documents.js as a real external
dependency, the same duplicate-install hazard from-package.ts narrows by
error.name to survive.

Declaring document-outline.js as a dependency makes both resolve external
again: the ESM builds now carry a bare
`import { ... } from "document-outline.js"` and the CJS build a matching
require, with document-schema.js reached transitively through the single
installed copy documents.js already resolves.

The bare package name in minimumReleaseAgeExclude follows the entries
already there for this family's other sibling packages, each published
only by its own semantic-release CI through npm OIDC trusted publishing.
minimumReleaseAge is unchanged, and still gates CI's frozen-lockfile
install, which ignores the exclude list.
@Mearman
Mearman force-pushed the feat/outline-tool branch from 2d205a0 to d217ff4 Compare August 19, 2026 01:37
@Mearman
Mearman marked this pull request as ready for review August 19, 2026 01:39
documents.js's SVG writer refuses to build a multi-page document
(buildSvgText throws SvgMultiPageNotSpecifiedError past one page), so
any odg source with more than one page failed outline_document
entirely. pdf is the only remaining target in the conversion matrix
that keeps the same content kind ('drawing') without a one-page
ceiling, matching odf's existing pdf-only routing for the same reason.
Two format-specific tests (markdown, ods) plus an unreadable-docx
error case exercised only 2 of the then-12 OUTLINE_PROBE_TARGETS
entries -- exactly how the odg target shipped broken, since nothing
in the suite ever drove a multi-page odg source through the tool.

Adds a dedicated regression pinning the multi-page odg case exactly
(three pages, each an empty 'Page N' group, kind 'drawing'), plus a
table-driven case per remaining source format (csv, docx, odf, odp,
odt, pdf, pptx, svg, xlsx) asserting the tool answers without error
and reports the source's own content kind. A companion assertion
checks the fixture table's keys plus the three deep-asserted formats
cover every DocumentFormat DOCUMENT_FORMATS lists, so a future format
addition with no fixture fails loudly instead of silently going
untested.

odf-formula-fixture.ts hand-authors a real .odf package byte-for-byte
(mimetype-first-and-stored zip, office:body > office:math > math:math
under the real "math:" namespace prefix), mirroring this repo's own
odm-fixture.ts convention -- neither odf.js nor documents.js exposes
an odf writer of any kind.
@Mearman
Mearman merged commit d726205 into main Aug 19, 2026
9 checks passed
@Mearman
Mearman deleted the feat/outline-tool branch August 19, 2026 02:08
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.1.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant