Skip to content

feat: extend file content search to PDF and text files - #425

Merged
aliasunder merged 19 commits into
mainfrom
worktree-file-content-search-pr2
Aug 12, 2026
Merged

feat: extend file content search to PDF and text files#425
aliasunder merged 19 commits into
mainfrom
worktree-file-content-search-pr2

Conversation

@aliasunder

@aliasunder aliasunder commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Extends vault_search to index PDF and text file content (.txt, .csv, .json, .xml, .svg, .log, .yaml, .yml, .base) alongside canvas files from PR feat(search): canvas file content FTS + graph parity #424
  • Moves pdf-engine.ts from utils/ to obsidian-markdown/ — PDF domain logic that fails the utils/ admission bar of "generic with zero domain knowledge"
  • Creates obsidian-markdown/pdf.ts with extractPdfText(Uint8Array → string) — manages pdfjs proxy lifecycle internally; callers pass bytes, get text
  • Removes the .canvas-only guard in upsertFileContent, generalizing to all indexable file types with a 100KB content cap
  • File watcher and rebuild pipeline extended to index all INDEXABLE_TEXT_EXTENSIONS; PDF extraction uses mapWithConcurrency (4 slots) during rebuild

Test plan

  • npm test — 2450 tests pass (5 new: 2 PDF extraction, 3 file content indexing)
  • npm run lint — 0 errors
  • npm run build — clean
  • Search a vault containing PDF and text files — results include them with kind: "file" and correct extension
  • Canvas search and link extraction unchanged (regression check)
  • Metadata filters (tags, type) still exclude file results
  • Large file content is truncated at 100KB (debug log emitted)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added searchable content indexing for PDFs and supported text files, including TXT, XML, YAML, and YML.
    • Improved PDF text extraction with formatting, headings, code blocks, page separators, and preserved links.
    • Added YAML and YML support for readable file operations.
    • Added PDF rendering and extraction support for empty or scanned documents.
  • Bug Fixes

    • Improved cleanup when indexed files are deleted.
    • Added safeguards for oversized or unreadable file content.
  • Documentation

    • Updated file support and PDF behavior documentation.

aliasunder and others added 3 commits August 11, 2026 13:14
pdf-engine.ts is PDF domain logic (pdfjs build swaps, font proxy wiring,
CMap path resolution) — it fails the utils/ admission bar of "generic with
zero domain knowledge." Move it to obsidian-markdown/ where its dependency
profile (node:module, node:path, unpdf, @napi-rs/canvas) is compatible with
the leaf-layer lint rules.

Create obsidian-markdown/pdf.ts with extractPdfText(Uint8Array → string) —
manages the pdfjs proxy lifecycle internally so callers pass bytes, get text.
Moves reconstructPdfMarkdown, groupIntoLines, buildHeadingLevels, roundFontSize
from asset-operations.ts (~120 lines).

asset-operations.ts's non-raw PDF branch now calls extractPdfText directly;
the raw/page-rendering branch keeps its own proxy for renderPdfPages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the .canvas-only guard in upsertFileContent and generalize to all
INDEXABLE_TEXT_EXTENSIONS (.pdf, .txt, .csv, .json, .xml, .svg, .log,
.yaml, .yml, .base). Canvas files still get linearization + link
extraction; other types are indexed as pre-rendered text (PDF via
extractPdfText, text files as raw UTF-8).

File watcher: extended to read and index all indexable file types on
change events. PDF extraction is async with error isolation per file.
Delete handler extended to clean up content for all indexable types.

Rebuild pipeline: scans canvas, PDF, and text files separately. PDF
extraction runs with bounded concurrency (mapWithConcurrency, 4 slots).
All file content inserted in the transaction alongside canvas.

Content cap: MAX_INDEXED_CONTENT_BYTES (100KB) truncates large files
before FTS insertion to prevent pathological rows.

Tests: updated non-canvas test to verify FTS indexing + no link
extraction. Added plain text file indexing test and content cap test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- vault_search description: expand kind/extension examples to include
  PDF and text file types in both hybrid and non-hybrid variants
- ARCHITECTURE.md: update pdf-engine.ts path (now obsidian-markdown/),
  replace "not yet searchable" with the full indexed file type list
- AGENTS.md: move pdf-engine.ts from utils/ to obsidian-markdown/
  in the structure tree, add pdf.ts entry
- New pdf.test.ts: extractPdfText integration test with real PDF
  fixture + empty-stream PDF edge case

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts Outdated
Comment thread src/vault-mcp/search/__tests__/search-index.test.ts
Comment thread src/vault-mcp/search/file-watcher.ts
Comment thread src/vault-mcp/search/search-index.ts Outdated
@umm-actually

umm-actually Bot commented Aug 11, 2026

Copy link
Copy Markdown

umm-actually re-reviewed at 745bfbd

No new findings (20 tracked finding(s) across all runs).

Context notes
  • 2 related file(s) excluded by max_related_files cap: src/vault-mcp/search/__tests__/search-helpers.test.ts, src/vault-mcp/search/__tests__/task-queries.test.ts

umm-actually · deepseek/deepseek-v4-pro

aliasunder and others added 2 commits August 11, 2026 14:48
…rch gap

- .devin/wiki.json: update utils/pdf-engine.ts → obsidian-markdown/pdf-engine.ts
  and add pdf.ts + file_content tables to the Non-Markdown Files page purpose
- search-index.ts: compute visibleFilesOfKind("file") once in rebuild instead
  of calling it twice (stat pass + content indexing pass)
- README.md: mention PDF and text file full-text searchability in the Files
  section; add YAML to the text formats list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
INDEXABLE_TEXT_EXTENSIONS includes .yaml/.yml (searchable via
vault_search), but TEXT_PASSTHROUGH_EXTENSIONS did not (not readable
via vault_read_file). A user finding a YAML file in search results
couldn't follow up with a read — broken UX.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/search/file-watcher.ts Outdated
Comment thread src/vault-mcp/search/__tests__/search-index.test.ts
Comment thread src/vault-mcp/search/search-index.ts Outdated
Comment thread src/vault-mcp/vault-operations/asset-operations.ts
Comment thread src/vault-mcp/vault-operations/__tests__/asset-operations.test.ts
@umm-actually

umm-actually Bot commented Aug 11, 2026

Copy link
Copy Markdown

Missing test coverage for file-watcher PDF and text file indexing
Medium severity · tests · high confidence

src/vault-mcp/search/__tests__/file-watcher.test.ts:1 — beyond the diff's line ranges, in code the changes touch or depend on.

The file watcher's handleChange and handleDelete gained new branches for PDF extraction and text file indexing (via INDEXABLE_TEXT_EXTENSIONS). No file-watcher tests exercise these paths — existing tests only cover .md files and .canvas files. The .pdf read path (readFileUint8ArrayextractPdfText) and the text file read path (readFile('utf8')) are untested. The handleDelete cleanup for indexable text extensions is also untested.

Failure scenario: A future refactor drops the filePath.endsWith('.canvas') guard in handleDelete but the replacement condition uses INDEXABLE_TEXT_EXTENSIONS incorrectly — no test catches it because the watcher's non-canvas file content path is entirely untested.

Suggested fix
Add file-watcher integration tests for: (1) a `.txt` file is indexed via `upsertFileContent` and searchable, (2) deleting an indexable text file calls `removeFileContent`, (3) a `.pdf` file channels through `extractPdfText` (mock `extractPdfText` to verify the call).

aliasunder and others added 2 commits August 11, 2026 14:57
…t assertion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… indexing

3 test quality fixes: exact `toBe` on deterministic extractPdfText output
(was 3x toContain), consolidated decomposed assertions on search result
(was toBeDefined + .kind + .extension), and truncation test now proves
content was actually truncated via start/end phrase strategy
(mutation-verified). 3 coverage gaps filled: INDEXABLE_TEXT_EXTENSIONS
drift guard (mutation-verified), removeFileContent for non-canvas files,
and the truncation test's two-bar fix doubles as a coverage improvement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/obsidian-markdown/pdf.ts Outdated
The Text formats entry listed .svg/.json/.txt/.csv/.xml/.log/.base but
omitted .yaml and .yml, which are supported by both TEXT_PASSTHROUGH_EXTENSIONS
(asset-operations.ts) and INDEXABLE_TEXT_EXTENSIONS (search-index.ts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@umm-actually

umm-actually Bot commented Aug 11, 2026

Copy link
Copy Markdown

vault_read_file tool description omits .yaml/.yml from readable text formats list
Medium severity · correctness · high confidence

src/vault-mcp/mcp-core/tools/asset-tools.ts:15 — beyond the diff's line ranges, in code the changes touch or depend on.

The TEXT_PASSTHROUGH_EXTENSIONS set in asset-operations.ts adds .yaml and .yml — this is correct and matches INDEXABLE_TEXT_EXTENSIONS. However, the vault_read_file tool description in asset-tools.ts lists readable text formats as .svg/.json/.txt/.csv/.xml/.log/.base — it does NOT list .yaml or .yml. An agent reading the tool description won't know YAML files are readable, and will only discover it by trial or by reading the unsupported-type error message. The error message WAS updated to include .yaml/.yml (visible in tool-definitions.test.ts and asset-operations.ts), but the tool description wasn't.

Failure scenario: An agent calls vault_list_files, sees some .yaml files, checks vault_read_file's description for supported types, sees .yaml not listed, and incorrectly concludes YAML files are unsupported — avoiding reading them or using a workaround.

Suggested fix
Update the vault_read_file tool description's text formats list to include `.yaml` and `.yml`: `(.svg/.json/.txt/.csv/.xml/.log/.yaml/.yml/.base)`

@umm-actually

umm-actually Bot commented Aug 11, 2026

Copy link
Copy Markdown

Pre-existing: DOCKERHUB.md Files section is stale — needs regeneration after README.md update
Low severity · correctness · high confidence

DOCKERHUB.md:1 — beyond the diff's line ranges, in code the changes touch or depend on.

DOCKERHUB.md's 'Files' section (auto-generated from README.md) contains a stale version of the text formats list. The README.md was updated to include YAML and YML extensions and to mention FTS searchability for text files, but DOCKERHUB.md still references the older 'SVG, JSON, CSV, logs, and Bases files' wording without .yaml/.yml in the explicit list. Since DOCKERHUB.md is auto-generated, this is a pre-existing regeneration gap — it reflects the README from before this PR's README changes.

Failure scenario: Pre-existing: A user reading DOCKERHUB.md sees 'Text and data files — SVG, JSON, CSV, logs, and Bases files return exactly as written' without the YAML/YML extensions or the FTS searchability claim that README.md now states. The generated content is stale until the next regeneration.

…-8 truncation

- extractPdfText now returns { text, totalPages } so the scanned-PDF
  error message in asset-operations.ts can include page count again
  (restored parity with the raw branch's error format)
- File-watcher integration tests: text file indexing + delete cleanup
  (coverage gap flagged by umm-actually)
- UTF-8 truncation: character-level string.slice instead of byte-level
  Buffer.subarray to avoid splitting multi-byte characters at the cap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aliasunder

Copy link
Copy Markdown
Owner Author

Addressing non-thread bot finding: file-watcher coverage gap

The umm-actually PR-level comment flagged missing file-watcher tests for PDF and text file indexing paths. This was also raised as an inline thread. Fixed — two file-watcher integration tests added:

  1. .txt file indexed into file content FTS (verifies kind: "file" and extension: ".txt")
  2. .csv file content removed on delete (verifies removeFileContent called and hybrid search returns empty)

🔍 ship-check · pr-monitor · claude-opus-4-6

Comment thread README.md Outdated
@aliasunder

Copy link
Copy Markdown
Owner Author

Addressing bot findings from the latest review round:

  1. vault_read_file description omits .yaml/.yml — Fixed. Updated the text formats list in asset-tools.ts to include .yaml/.yml.
  2. DOCKERHUB.md stale after README changes — Fixed. Regenerated via npm run generate:dockerhub-readme.
  3. README text formats list — Fixed. Expanded to include TXT and XML.

🔍 ship-check · pr-monitor · claude-opus-4-6

…RHUB.md

- vault_read_file tool description: text formats list now includes
  .yaml/.yml (matches TEXT_PASSTHROUGH_EXTENSIONS)
- README: expanded text formats list to include TXT and XML
- DOCKERHUB.md: regenerated to reflect README changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/search/__tests__/file-watcher.test.ts
Writes a real buildMinimalPdf() fixture to the temp vault, starts the
watcher, and asserts extracted text appears in hybridSearch with
kind: "file" and extension: ".pdf". Exercises the binary readFile →
Uint8Array → extractPdfText path that the .txt tests don't cover.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/mcp-core/tools/asset-tools.ts
@aliasunder

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds shared Node PDF processing, reconstructs PDF text as Markdown, extends asset support to YAML, and indexes PDFs and supported text files in file-content search. Watchers, rebuilds, search descriptions, tests, and documentation now reflect these formats.

Changes

Non-Markdown processing

Layer / File(s) Summary
PDF engine and Markdown extraction
src/vault-mcp/obsidian-markdown/*, AGENTS.md
Adds shared PDFJS initialization, canvas support, PDF document proxy creation, PDF-to-Markdown extraction, link handling, page counts, and valid/empty PDF tests.
Asset PDF and text handling
src/vault-mcp/vault-operations/*, README.md, DOCKERHUB.md
Routes asset PDF extraction through the shared modules, preserves raw PDF rendering, and adds YAML and YML passthrough support.
File-content indexing and watching
src/vault-mcp/search/*, src/vault-mcp/mcp-core/*, ARCHITECTURE.md, .devin/wiki.json
Indexes PDFs and supported text files, applies a 100 KiB content limit, handles deletion cleanup, and updates search descriptions and indexing documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: Review effort 2/5

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: extending file-content search to PDF and text files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-file-content-search-pr2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/vault-mcp/search/search-index.ts (1)

1615-1728: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Skip PDF and text extraction when file-content indexing is disabled.

When fileToolsEnabled is false, file_content and file_content_fts do not exist. These paths still read every supported text file and run PDF extraction. This adds rebuild and watcher I/O, CPU use, and warning logs for content that cannot be searched. Keep Canvas link extraction active.

  • src/vault-mcp/search/search-index.ts#L1615-L1728: Build PDF and text file lists, then read and extract them, only when fileToolsEnabled is true.
  • src/vault-mcp/search/file-watcher.ts#L88-L118: Pass the file-content capability into startFileWatcher, then skip non-Canvas content reads when it is false.
#!/bin/bash
set -euo pipefail

rg -n -C 4 --type=ts '\bstartFileWatcher\s*\(' src
rg -n -C 4 --type=ts '\bfileToolsEnabled\b' src/vault-mcp
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vault-mcp/search/search-index.ts` around lines 1615 - 1728, In
src/vault-mcp/search/search-index.ts lines 1615-1728, keep Canvas reading and
link extraction unconditional, but only build PDF/text lists and perform PDF
extraction or text reads when fileToolsEnabled is true. In
src/vault-mcp/search/file-watcher.ts lines 88-118, pass the file-content
capability into startFileWatcher and skip non-Canvas content reads when
disabled; preserve Canvas processing in all cases.
🧹 Nitpick comments (6)
src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts (3)

20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Declare buildEmptyStreamPdf before its call site.

Line 20 calls buildEmptyStreamPdf, which is a const arrow function initialized at line 34. This works today because Vitest defers the it callback until after the module body evaluates. It breaks with a TDZ ReferenceError if the call ever moves into the describe body or into collection-phase code.

Move the helper above the describe block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts` at line 20, Move the
buildEmptyStreamPdf helper declaration above the describe block so it is
initialized before any call site, while preserving its existing behavior and
implementation.

5-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add coverage for the Markdown reconstruction rules.

Both tests exercise only the trivial path. reconstructPdfMarkdown in pdf.ts implements four non-trivial rules that no test asserts:

  • Heading levels from relative font sizes (buildHeadingLevels, H1–H3 cap, smallest size stays body text).
  • Fenced code blocks from fontFamily === "monospace", including the fence closed at page end.
  • --- Page N --- separators for multi-page documents.
  • The deduplicated links footer and the Links: N header field.

A regression in any of these rules passes the current suite. Add fixtures with two font sizes, a monospace run, and two pages.

Do you want me to generate these fixtures and tests?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts` around lines 5 - 30,
Extend the extractPdfText tests around reconstructPdfMarkdown with fixtures
covering two relative font sizes, a monospace run, and a two-page document.
Assert H1–H3 heading assignment with the smallest font remaining body text,
fenced code closure at page end, page separators, and the deduplicated links
footer plus Links: N header field.

34-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

PDF test fixtures have no shared, layer-neutral home. The shared root cause is that buildMinimalPdf lives under mcp-core/__tests__/, so leaf-layer tests reach upward into the protocol layer to use it, and a second fixture was hand-copied rather than derived from it. One shared fixture module fixes both sites.

  • src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts#L34-L86: buildEmptyStreamPdf duplicates almost all of buildMinimalPdf — the header, the catalog, pages, page, and stream objects, the xref table, and the trailer. Only the content stream and the /Resources entry differ. Parameterize a single builder over the content stream and the font resource, then express both fixtures through it.
  • src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts#L8-L8: this import points at ../../mcp-core/__tests__/pdf-fixture.js. Re-point it at the shared fixture module once the builder moves to a neutral location, such as a test-support folder that no production layer owns.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts` around lines 34 - 86,
Move buildMinimalPdf into a shared, layer-neutral test-support fixture module
and parameterize it by content stream and font resource so buildEmptyStreamPdf
in src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts uses the shared builder
instead of duplicating PDF structure; preserve the differing /Resources and
stream behavior. Update the import in
src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts#L8 to reference the
new shared module rather than the mcp-core fixture, with no direct change needed
beyond that import.
src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts (1)

8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The fixture import path resolves correctly.

buildMinimalPdf is defined at src/vault-mcp/mcp-core/__tests__/pdf-fixture.ts. The relative specifier and the .js extension are correct.

One placement note: this leaf-layer test now depends on a fixture inside mcp-core/__tests__/. pdf.test.ts does the same. See the consolidated comment on fixture placement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts` at line 8, Keep
the buildMinimalPdf import unchanged because its relative path and .js extension
resolve correctly; no code change is required for this comment.
src/vault-mcp/obsidian-markdown/pdf-engine.ts (1)

62-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

pdf-engine.ts breaks the obsidian-markdown/ folder contract. The shared root cause is one placement decision: a module that resolves package paths from disk and mutates globalThis now lives in a folder that both the coding guidelines and AGENTS.md define as pure, filesystem-free parsers. Resolve the contradiction in one direction — either move the module or amend the stated contract.

  • src/vault-mcp/obsidian-markdown/pdf-engine.ts#L62-L89: resolvePdfjsAssetPaths reads the pdfjs-dist package root through createRequire(...).resolve, and initializePdfEngine installs process-wide DOMMatrix, Path2D, and ImageData globals. If the module stays here, confirm the ESLint layering rule permits it; otherwise move it to utils/ under the "complete, standalone primitive" bar.
  • AGENTS.md#L129-L129: this line lists pdf-engine.ts under obsidian-markdown/, while lines 180-182 of the same file state the folder holds "pure parsers/transforms" with "No fs, no SQLite, no MCP". If the placement stands, add an explicit carve-out for the PDF bootstrap in that paragraph, matching how the paragraph already carves out canvas.ts and links.ts.

As per coding guidelines: "Keep Obsidian/Markdown parsers pure: no filesystem, SQLite, or MCP dependencies."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vault-mcp/obsidian-markdown/pdf-engine.ts` around lines 62 - 89, Resolve
the placement/contract mismatch for pdf-engine.ts: either move
resolvePdfjsAssetPaths and initializePdfEngine to utils/ as a standalone
primitive and update all references, or retain the module under
obsidian-markdown only after confirming the layering rule permits its filesystem
resolution and globalThis mutations. If retaining it, update AGENTS.md at the
listed entry and pure-parser contract to explicitly carve out the PDF bootstrap
alongside canvas.ts and links.ts; otherwise state that the AGENTS.md site
requires no direct change beyond updating the file listing.

Source: Coding guidelines

AGENTS.md (1)

129-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The pdf-engine.ts entry conflicts with the folder contract stated later in this file.

Line 129 places pdf-engine.ts in obsidian-markdown/. Lines 180-182 of this same file define that folder as "pure parsers/transforms" with "No fs, no SQLite, no MCP". pdf-engine.ts resolves the pdfjs-dist package root from disk and mutates globalThis.

If the placement is intended, amend the obsidian-markdown/ layering paragraph to carve out the PDF bootstrap, the way the paragraph already carves out canvas.ts for JSON and links.ts for namespace exports. See the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 129, Update the obsidian-markdown layering paragraph to
explicitly carve out pdf-engine.ts as an allowed PDF bootstrap exception,
alongside the existing canvas.ts and links.ts exceptions. Document that it may
resolve pdfjs-dist from disk and mutate globalThis, while preserving the
folder’s pure-parser/transform contract for all other modules.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Line 130: Update the pdf.ts entry in AGENTS.md to document extractPdfText as
returning PdfTextResult, including its text and totalPages fields, instead of
describing a direct Uint8Array-to-string conversion.

In `@DOCKERHUB.md`:
- Line 69: Update the corresponding text/data-file description in README.md to
state that full-text indexing/search is limited to the first 100KB, while
preserving the separate claim that large files and logs can be read by line
range; then regenerate DOCKERHUB.md so it reflects the README change.

In `@src/vault-mcp/mcp-core/tools/asset-tools.ts`:
- Line 110: Update the search-coverage statement in the asset-tools description
to reflect that vault_search indexes PDFs and all supported text formats, while
retaining the existing markdown-note coverage and file-browsing/note-link
guidance where applicable.

In `@src/vault-mcp/search/search-index.ts`:
- Around line 989-995: Update the truncation logic near contentBytes and
truncatedContent to enforce MAX_INDEXED_CONTENT_BYTES by iterating Unicode code
points and accumulating each code point’s UTF-8 byte length, stopping before the
cap is exceeded. Preserve the full content when already within the limit, avoid
splitting surrogate pairs, and add a boundary test using non-ASCII characters.

---

Outside diff comments:
In `@src/vault-mcp/search/search-index.ts`:
- Around line 1615-1728: In src/vault-mcp/search/search-index.ts lines
1615-1728, keep Canvas reading and link extraction unconditional, but only build
PDF/text lists and perform PDF extraction or text reads when fileToolsEnabled is
true. In src/vault-mcp/search/file-watcher.ts lines 88-118, pass the
file-content capability into startFileWatcher and skip non-Canvas content reads
when disabled; preserve Canvas processing in all cases.

---

Nitpick comments:
In `@AGENTS.md`:
- Line 129: Update the obsidian-markdown layering paragraph to explicitly carve
out pdf-engine.ts as an allowed PDF bootstrap exception, alongside the existing
canvas.ts and links.ts exceptions. Document that it may resolve pdfjs-dist from
disk and mutate globalThis, while preserving the folder’s pure-parser/transform
contract for all other modules.

In `@src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts`:
- Line 8: Keep the buildMinimalPdf import unchanged because its relative path
and .js extension resolve correctly; no code change is required for this
comment.

In `@src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts`:
- Line 20: Move the buildEmptyStreamPdf helper declaration above the describe
block so it is initialized before any call site, while preserving its existing
behavior and implementation.
- Around line 5-30: Extend the extractPdfText tests around
reconstructPdfMarkdown with fixtures covering two relative font sizes, a
monospace run, and a two-page document. Assert H1–H3 heading assignment with the
smallest font remaining body text, fenced code closure at page end, page
separators, and the deduplicated links footer plus Links: N header field.
- Around line 34-86: Move buildMinimalPdf into a shared, layer-neutral
test-support fixture module and parameterize it by content stream and font
resource so buildEmptyStreamPdf in
src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts uses the shared builder
instead of duplicating PDF structure; preserve the differing /Resources and
stream behavior. Update the import in
src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts#L8 to reference the
new shared module rather than the mcp-core fixture, with no direct change needed
beyond that import.

In `@src/vault-mcp/obsidian-markdown/pdf-engine.ts`:
- Around line 62-89: Resolve the placement/contract mismatch for pdf-engine.ts:
either move resolvePdfjsAssetPaths and initializePdfEngine to utils/ as a
standalone primitive and update all references, or retain the module under
obsidian-markdown only after confirming the layering rule permits its filesystem
resolution and globalThis mutations. If retaining it, update AGENTS.md at the
listed entry and pure-parser contract to explicitly carve out the PDF bootstrap
alongside canvas.ts and links.ts; otherwise state that the AGENTS.md site
requires no direct change beyond updating the file listing.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4aeaf4ef-515d-45cf-9659-259f7162898f

📥 Commits

Reviewing files that changed from the base of the PR and between 8191136 and 0fd9ac6.

📒 Files selected for processing (18)
  • .devin/wiki.json
  • AGENTS.md
  • ARCHITECTURE.md
  • DOCKERHUB.md
  • README.md
  • src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts
  • src/vault-mcp/mcp-core/tools/asset-tools.ts
  • src/vault-mcp/mcp-core/tools/search-tools.ts
  • src/vault-mcp/obsidian-markdown/__tests__/pdf-engine.test.ts
  • src/vault-mcp/obsidian-markdown/__tests__/pdf.test.ts
  • src/vault-mcp/obsidian-markdown/pdf-engine.ts
  • src/vault-mcp/obsidian-markdown/pdf.ts
  • src/vault-mcp/search/__tests__/file-watcher.test.ts
  • src/vault-mcp/search/__tests__/search-index.test.ts
  • src/vault-mcp/search/file-watcher.ts
  • src/vault-mcp/search/search-index.ts
  • src/vault-mcp/vault-operations/__tests__/asset-operations.test.ts
  • src/vault-mcp/vault-operations/asset-operations.ts

Comment thread AGENTS.md Outdated
Comment thread DOCKERHUB.md Outdated
Comment thread src/vault-mcp/mcp-core/tools/asset-tools.ts
Comment thread src/vault-mcp/search/search-index.ts Outdated
aliasunder and others added 2 commits August 11, 2026 17:08
…h coverage

- truncateToUtf8ByteLimit: iterates Unicode code points to enforce the
  100KB cap in UTF-8 bytes (string.slice truncated by UTF-16 code units,
  so multi-byte content could exceed the byte limit)
- AGENTS.md: extractPdfText signature corrected to { text, totalPages }
- README + DOCKERHUB.md: 100KB indexing cap stated alongside
  searchability claim
- asset-tools.ts: search coverage line updated to include canvas, PDF,
  and text formats

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…it nitpicks

- Rebuild pipeline: gate PDF and text file list building behind
  fileToolsEnabled (canvas stays unconditional for link extraction)
- File watcher: check search.fileContentIndexingEnabled before reading
  non-canvas files for FTS — avoids wasted I/O when tables don't exist
- AGENTS.md: document pdf-engine.ts as a noted exception to the
  obsidian-markdown/ pure-parser contract (createRequire + globalThis
  mutation, compatible lint profile)
- pdf.test.ts: move buildEmptyStreamPdf above describe block (TDZ safety)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aliasunder

Copy link
Copy Markdown
Owner Author

Addressing CodeRabbit review findings (review 4910580977):

Actionable (4 inline threads — already resolved in prior passes):
All 4 inline findings were posted as threads and handled: AGENTS.md signature, DOCKERHUB.md 100KB caveat, asset-tools search coverage, UTF-8 byte truncation.

Outside-diff finding (1):

  • Skip PDF/text extraction when fileToolsEnabled is false — Fixed. Rebuild pipeline gates PDF and text file list building behind fileToolsEnabled (empty arrays when disabled). File watcher checks search.fileContentIndexingEnabled before reading non-canvas files. Canvas link extraction stays unconditional.

Nitpick findings (6 in review body):

  1. buildEmptyStreamPdf before call site — Fixed. Moved helper above describe block.
  2. Add coverage for Markdown reconstruction rules — Skipped. Reconstruction functions are internal to pdf.ts (not exported). They're exercised via extractPdfText with real PDF fixtures. Building PDFs with specific font properties for heading/code block tests is medium lift for low value — the functions are already tested indirectly.
  3. PDF test fixtures have no shared home — Valid but out of scope for this PR. buildMinimalPdf predates this PR and is used across 3 test files. Moving it is a cross-cutting refactor.
  4. Fixture import path resolves correctly — Bot says "no code change required." Skipped.
  5. pdf-engine.ts breaks obsidian-markdown/ folder contract — Fixed. AGENTS.md now documents the exception explicitly: pdf-engine.ts is PDF domain logic with a compatible lint profile (no node:fs); it uses createRequire and globalThis mutation for pdfjs bootstrapping.
  6. pdf-engine.ts entry conflicts with folder contract — Same as chore: add version 0.1.0 to package.json #5, addressed by the AGENTS.md amendment.

🔍 ship-check · pr-monitor · claude-opus-4-6

Comment thread src/vault-mcp/search/search-index.ts
aliasunder and others added 2 commits August 11, 2026 20:42
Move pdf-fixture.ts from mcp-core/__tests__/ to
obsidian-markdown/__tests__/ — the PDF parsers live in
obsidian-markdown/, so the fixtures belong alongside them. Consolidate
buildEmptyStreamPdf (was inlined in pdf.test.ts) into the shared
fixture module. Update all 4 import sites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split the single-paragraph PDF description into structured subsections:
bulleted list for text rendition features, paragraph for raw mode, and
a labeled "Font independence" paragraph for the pdfjs engine explanation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/search/search-index.ts
- AGENTS.md pdf-engine.ts rationale: explain why it belongs with the
  PDF parsers (domain logic, same unit as pdf.ts) instead of citing
  lint rule compatibility
- ARCHITECTURE.md: rename "Font independence" heading to "Why text
  works without system fonts" — plain language over jargon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@umm-actually

umm-actually Bot commented Aug 12, 2026

Copy link
Copy Markdown

Pre-existing: vault_list_files tool description omits .yaml/.yml from supported extensions
Low severity · correctness · high confidence

src/vault-mcp/mcp-core/tools/asset-tools.ts:160 — beyond the diff's line ranges, in code the changes touch or depend on.

The vault_list_files tool description's extensions parameter says extensions filter applies. It does not list which extensions are readable, so users must infer from vault_read_file's description. Since vault_read_file also omits .yaml/.yml, users have no discoverable way to know YAML files are supported from tool descriptions alone — they only learn from the unsupported-type error message.

Failure scenario: A user discovers .yaml files via vault_list_files but reads vault_read_file's description, doesn't see .yaml listed, and assumes they are unsupported.

Suggested fix
No code change needed for `vault_list_files` itself — fixing `vault_read_file`'s description (see previous finding) is the root fix, as `vault_list_files` cross-references `vault_read_file`.

aliasunder and others added 2 commits August 12, 2026 00:46
Scanned or image-only PDFs produce no indexable text and are silently
skipped — note the limitation in ARCHITECTURE.md so readers don't
assume all PDFs are searchable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hoist canvasLinks extraction before the transaction so the debug log
retains the link count diagnostic for canvas files (lost during the
generalization of upsertFileContent). Extract the PDF mapper callback
into a named extractPdfContent function for readability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aliasunder

Copy link
Copy Markdown
Owner Author

Addressing umm-actually PR-level finding: vault_list_files omits .yaml/.yml

The bot's own analysis concludes "No code change needed for vault_list_files itself — fixing vault_read_file's description is the root fix." That fix was already applied (vault_read_file description now lists .yaml/.yml). vault_list_files is a discovery tool that cross-references vault_read_file for supported types, so no separate update needed.

Also fixed in this push:

  • Canvas link count restored in debug logcanvasLinks hoisted before the transaction so upsertFileContent's debug log retains links: canvasLinks.length for canvas files (regression from the generalization).
  • Extracted PDF mapper — the inline pdfContents mapper callback is now a named extractPdfContent function for readability.

🔍 ship-check · pr-monitor · claude-opus-4-6

@umm-actually

umm-actually Bot commented Aug 12, 2026

Copy link
Copy Markdown

Restore canvas link count in upsertFileContent debug log after generalization
Low severity · correctness · high confidence

src/vault-mcp/search/search-index.ts:1062 — beyond the diff's line ranges, in code the changes touch or depend on.

The upsertFileContent debug log at line 1062 in search-index.ts previously emitted links: canvasLinks.length — a diagnostic that told operators how many file-node links a canvas produced. After the generalization to PDF and text files, the link count is no longer emitted for ANY file type. For canvas files this is a silent regression of an observable that previously existed and had diagnostic value for link graph issues. The log entry now only reports fts: ....

Failure scenario: A canvas board with file-type nodes is indexed. Previously the debug log showed the link count (e.g. links: 3). After this PR the same log entry shows fts: true with no link count. An operator investigating a link graph discrepancy cannot tell from logs whether link extraction succeeded or silently produced zero links.

Suggested fix
Capture `canvasLinks.length` before the transaction and include it in the debug log conditionally (only for canvas files), or log it separately inside the canvas branch after the transaction: `logger.debug('indexed file content', { path: params.filePath, links: isCanvas ? canvasLinks.length : undefined, fts: Boolean(upsertFileContentStmt) })`.

@aliasunder

Copy link
Copy Markdown
Owner Author

Addressing umm-actually finding: canvas link count in debug log

Already fixed in d43d701canvasLinks extraction was hoisted before the transaction, and the debug log now includes links: canvasLinks.length again. This is a duplicate of the inline thread finding on the same commit, which was resolved in the previous pr-monitor pass.


🔍 ship-check · pr-monitor · claude-opus-4-6

Remove wrapping parentheses around await mapWithConcurrency by
splitting the result into a named intermediate (pdfResults) before
filtering nulls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aliasunder
aliasunder merged commit 85e9550 into main Aug 12, 2026
17 of 18 checks passed
@aliasunder
aliasunder deleted the worktree-file-content-search-pr2 branch August 12, 2026 17:45
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