Skip to content

feat(files): line paging for vault_read_file text results - #397

Merged
aliasunder merged 10 commits into
mainfrom
claude/vault-bootstrap-setup-u0fyq2
Aug 3, 2026
Merged

feat(files): line paging for vault_read_file text results#397
aliasunder merged 10 commits into
mainfrom
claude/vault-bootstrap-setup-u0fyq2

Conversation

@aliasunder

@aliasunder aliasunder commented Aug 3, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Large text files were unreadable: anything past the fixed 100 KiB text-output cap returned only text output too large, with no way to read a slice. The cap's own comment has said since it landed that "text past this size needs paging" — this adds that paging.

start_line + limit on vault_read_file. Two optional inputs page any text result — the seven passthrough formats, canvas outlines and raw JSON source, and PDF-extracted text — as a 1-based line window. A paged read prepends a metadata block so the agent always knows where it is and how much file exists:

data.csv — lines 51–100 of 400 (continue with start_line: 101)
data.csv — lines 351–400 of 400 (end of file)

A read without paging inputs stays byte-identical to today's output (the existing verbatim it.each doubles as the regression guard), so nothing changes for existing callers. start_line: 1, limit: 1 doubles as a cheap "how many lines is this?" probe, documented in the tool description.

Design decisions (user-ratified during planning):

  • 1-based start_line over a 0-based offset — matches vault_update_task's line, editors, and wc -l thinking; the metadata block hands the agent the exact next start_line, so nobody does arithmetic.
  • Scope: every text rendition, not just passthrough formats. All three text paths share assertTextWithinCap, so paging is implemented at one interception point (buildPagedTextResult) replacing the three assert call sites — and the cap's documented remediation is true everywhere it fires. Images and PDF raw: true reject paging inputs.
  • Error layering: runtime messages stay fact-only per the repo's error-surface convention — start line past the end: "data.csv" renders to 342 lines carries the self-correction datum but no wire input names or prescribed actions; all remediation ("page it with start_line and limit") lives in the tool description's Errors: bullets. The unpaged cap message is unchanged, so its exact-match tests are untouched.
  • Line semantics: splitIntoLines (the canonical CRLF normalizer) with wc -l counting — a trailing newline's empty final element is not a line. Paged windows come back LF-joined with no trailing newline, a documented divergence from "exactly as written" that applies to paged reads only. The cap applies per window, so a single 100 KiB line still errors (…lines 1–1 render to 102401 bytes…). A start line past the end errors rather than returning an empty window an agent could misread as "file is empty"; an empty file returns the zero-line window.

The text arm of AssetReadResult now carries path (matching the image and pages arms) so the formatter can build the metadata line; 7 existing PDF whole-object assertions gained the field.

Docs: ARCHITECTURE.md Files section (input row + text-formats item), README Files bullet (user-facing phrasing), DOCKERHUB.md regenerated. server.json, wiki.json, and env surfaces verified unaffected — MAX_TEXT_OUTPUT_BYTES deliberately stays a non-env constant.

Validation. 18 new tests across both layers: 13 mocked data-layer units (window geometry, CRLF→LF, trailing-newline counting, empty file, past-EOF, per-window cap, PDF text, raw canvas, image + raw-PDF rejections) and 5 real-temp-vault wire-shape tests (both content blocks asserted whole, end-of-file window, [Error]: exact messages) plus a description-contract test for the new Errors bullets. Mutation-tested against committed state: an off-by-one in the window slice fails 8 window tests, disabling the per-window cap fails exactly the cap test, disabling the past-EOF guard fails exactly the two past-EOF tests. Live verification on a Test Deploy to follow before merge.

Type of change

  • New MCP tool
  • Bug fix
  • Refactoring
  • Documentation
  • CI / workflow change
  • Infrastructure (SST, Docker)
  • Other: feature — new inputs on an existing MCP tool

Checklist

🤖 Generated with Claude Code

https://claude.ai/code/session_01ST7bwmeMkH7ETvyytfNfJB

Summary by CodeRabbit

  • New Features

    • Added line-based pagination for large text, canvas, and extracted PDF content.
    • Responses include the selected range, total line count, and continuation information.
    • Unpaged reads retain their existing behavior and format.
  • Bug Fixes

    • Added validation for invalid ranges and unsupported paging of images or rendered PDFs.
  • Documentation

    • Updated documentation with line-range reading and pagination guidance.

start_line/limit page any text rendition — passthrough formats, canvas
outlines and raw JSON, PDF-extracted text — as a 1-based line window,
preceded by a metadata block stating the window, total line count, and
next start_line. A read without paging inputs stays byte-identical.

The 100 KiB output cap now applies per window, so files past the cap
are readable in pages; the cap's tool-description entry points at
paging while runtime messages stay fact-only per the error-surface
convention. A start line past the end errors with the total; images
and raw PDF page rendering reject paging inputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ST7bwmeMkH7ETvyytfNfJB
@umm-actually

umm-actually Bot commented Aug 3, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Clarify byte cap applies to delivered form

The cap check uses Buffer.byteLength(windowText, "utf8"), but windowText was built
by joining windowLines with "\n". If the original file used CRLF line endings, the
lines were already normalized to LF by splitIntoLines, so the rejoined text is
LF-only. This means the byte count of the window can differ from the byte count of
the same line range in the original file — the cap is enforced against the
normalized form, not the original. This is acceptable since the tool description
already states paged windows use \n line endings, but the error message should
clarify that the byte count is for the delivered (LF-normalized) window, not the
original file bytes, to avoid confusion when a user compares against ls -l.

src/vault-mcp/vault-operations/asset-operations.ts [158-164]

 const windowBytes = Buffer.byteLength(windowText, "utf8")
 if (windowBytes > MAX_TEXT_OUTPUT_BYTES) {
   throw new Error(
     `text output too large: "${path}" lines ${firstLine}–${endLine} ` +
-      `render to ${windowBytes} bytes (cap ${MAX_TEXT_OUTPUT_BYTES} bytes)`,
+      `render to ${windowBytes} bytes as delivered (cap ${MAX_TEXT_OUTPUT_BYTES} bytes)`,
   )
 }
Suggestion importance[1-10]: 2

__

Why: The suggestion adds a minor clarification to the error message, but it is not necessary. The tool description already states that paged windows use \n line endings, and the error message is internal. The change offers negligible improvement to correctness or user experience.

Low

Comment thread src/vault-mcp/vault-operations/asset-operations.ts
@aliasunder

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 3, 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 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d4d646f1-da8f-498c-b4b1-c453783331e2

📥 Commits

Reviewing files that changed from the base of the PR and between 92d89cd and 109a7d4.

📒 Files selected for processing (5)
  • AGENTS.md
  • README.md
  • src/vault-mcp/mcp-core/tools/asset-tools.ts
  • src/vault-mcp/vault-operations/__tests__/asset-operations.test.ts
  • src/vault-mcp/vault-operations/asset-operations.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/vault-mcp/mcp-core/tools/asset-tools.ts
  • src/vault-mcp/vault-operations/asset-operations.ts
  • README.md
  • src/vault-mcp/vault-operations/tests/asset-operations.test.ts

📝 Walkthrough

Walkthrough

Changes

Line-range asset reading

Layer / File(s) Summary
Asset paging core
src/vault-mcp/vault-operations/asset-operations.ts
readAssetContent now accepts 1-based line windows. Text results include asset paths and range metadata. Paging handles normalization, validation, size limits, canvas content, and extracted PDF text.
MCP paging integration
src/vault-mcp/mcp-core/tools/asset-tools.ts, ARCHITECTURE.md, README.md, DOCKERHUB.md
vault_read_file validates and forwards start_line and limit, formats continuation metadata, logs paging details, and documents supported outputs and errors.
Paging validation coverage
src/vault-mcp/vault-operations/__tests__/asset-operations.test.ts, src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts
Tests cover line windows, metadata, empty and final windows, normalization, size limits, PDF and canvas content, invalid starts, and unsupported paging targets.

Authoring guidance

Layer / File(s) Summary
Code style guidance
AGENTS.md
The Code style preamble now distinguishes authoring guidance from review-checklist language.

Estimated code review effort: 3 (Moderate) | ~25 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 describes the main change: adding line paging to vault_read_file text results.
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 claude/vault-bootstrap-setup-u0fyq2

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: 1

🤖 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 `@src/vault-mcp/vault-operations/asset-operations.ts`:
- Around line 119-172: Update buildPagedTextResult to validate the paging inputs
before calling contentLines.slice: reject any startLine or limit value below 1
with an error instead of allowing negative indices or invalid windows. Preserve
the existing default of 1 for an omitted startLine and all valid paging
behavior.
🪄 Autofix (Beta)

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: 9427d0ac-ce02-481f-8cec-c78d0b76674c

📥 Commits

Reviewing files that changed from the base of the PR and between 210d5cf and 92d89cd.

📒 Files selected for processing (7)
  • 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/vault-operations/__tests__/asset-operations.test.ts
  • src/vault-mcp/vault-operations/asset-operations.ts

Comment thread src/vault-mcp/vault-operations/asset-operations.ts
claude and others added 4 commits August 3, 2026 03:17
CodeRabbit review: the tool schema's .min(1) made a sub-1 startLine
unreachable, but a bypassed negative slice start would silently serve
lines from the END of the rendition — the data layer enforces the
bound itself per the repo's validation convention, with a fact-only
message in the module's own naming.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ST7bwmeMkH7ETvyytfNfJB
The hasTrailingNewlineArtifact false path was untested — all existing
paging tests used content ending in "\n". This test exercises paging
a file without a trailing newline to prove the branch counts lines
correctly. Mutation-verified: removing the conditional and always
slicing off the last element fails this test.

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

umm-actually Bot commented Aug 3, 2026

Copy link
Copy Markdown

umm-actually re-reviewed at e91553e

1 new finding(s) posted (1 tracked finding(s) across all runs).


umm-actually · deepseek/deepseek-v4-pro

aliasunder and others added 2 commits August 3, 2026 15:09
…NTS.md as write-time guidance

Destructure lineWindow, name the end-of-file boolean, flatten the return.
Reframe the code style preamble: these are authoring rules applied while
writing, not a review checklist applied after.

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

Copy link
Copy Markdown
Owner Author

@umm review

@aliasunder

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 3, 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.

aliasunder and others added 2 commits August 3, 2026 15:49
Extract hasInvalidLineRange, isStartPastEnd, exceedsByteCap so each
guard reads as intent, not arithmetic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
metadataBlock and contentBlock make the two text blocks' roles visible
without reading describeTextWindow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/vault-mcp/vault-operations/asset-operations.ts
@aliasunder
aliasunder merged commit 2905166 into main Aug 3, 2026
20 checks passed
@aliasunder
aliasunder deleted the claude/vault-bootstrap-setup-u0fyq2 branch August 3, 2026 20:51
aliasunder added a commit that referenced this pull request Aug 4, 2026
## Summary

- Mirrors PR #397's `vault_read_file` paging on `vault_read_note` — one
paging idiom across both read tools
- Pages the delivered rendition (full body or a `heading:` section) by
1-based line range; JSON modes (`outline`, `properties_only`) reject
paging with a clear error
- Fixes the oversized-section gap: this project's own Done lane holds
250+ cards — `heading: "Done", start_line: 1, limit: 20` now reads a
window instead of the entire lane

### Implementation

- **Extracted** `pageTextByLines` + `LineWindow` type to
`obsidian-markdown/lines.ts` as a shared primitive (split, validate,
slice — no byte cap; byte cap enforcement stays in asset-operations)
- **Refactored** `buildPagedTextResult` in asset-operations.ts to
delegate to `pageTextByLines`
- **Moved** `describeTextWindow` to `tool-helpers.ts` for shared use by
both read tools
- **Added** `start_line`/`limit` schema params + paging validation +
two-block result formatting to `vault_read_note` handler
- **Updated** ARCHITECTURE.md input table and `vault_read_note`
description (examples, errors, returns)

## Test plan

- [x] 12 unit tests for `pageTextByLines` (window, clamp, CRLF, trailing
newline, empty, past-EOF, invalid range)
- [x] 9 integration tests for `vault_read_note` paging (full note,
heading section, end-of-file, empty note, past-EOF error,
outline/properties_only rejection, byte-identical unpaged, description
assertions)
- [x] 2342 tests pass (21 new, 2321 existing)
- [x] Mutation-tested: removing paging from the section branch kills the
heading-paging test
- [x] Build + lint clean
- [ ] CI green

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added optional line-based pagination for reading full notes and
heading sections.
* Paged responses include selected line ranges, total line counts, and
continuation details.
* Added validation for invalid, out-of-range, and incompatible
pagination requests.

* **Bug Fixes**
* Improved handling of line endings, trailing newlines, empty content,
and paging limits.
  * Preserved existing output formats for unpaged note reads.

* **Documentation**
* Updated tool documentation with pagination parameters, behavior, and
limitations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

2 participants