Skip to content

Comments

feat: Add hashline-based file read and edit tools#167

Merged
gadenbuie merged 23 commits intomainfrom
feat/read-edit-tool
Feb 17, 2026
Merged

feat: Add hashline-based file read and edit tools#167
gadenbuie merged 23 commits intomainfrom
feat/read-edit-tool

Conversation

@gadenbuie
Copy link
Collaborator

@gadenbuie gadenbuie commented Feb 12, 2026

Implements the hashline approach described in The Harness Problem — content-hash line annotations for reliable, validated file editing.

Summary

  • Hashline annotations in btw_tool_files_read: Each line returned to the model is now prefixed with line_number:hash| (e.g. 2:f1a| return("world")). The 3-character hash is derived from rlang::hash() over trimmed, 80-char-truncated content. The display.markdown shown to users remains a clean code block.
  • New btw_tool_files_edit tool: Uses hashline references from the read output to make targeted edits — replace, insert_after, and replace_range actions. Hashes are validated against the current file state, so stale edits are rejected atomically. Multiple edits in a single call are applied bottom-to-top to preserve line numbering.
  • include_hashline parameter: Defaults to FALSE in the _impl() function so internal callers like btw_this() get clean output. Only the model-facing tool wrapper passes TRUE.
  • Smart edit responses: The edit tool returns updated hashline references for edited regions and surrounding context, so the model can make follow-up edits without re-reading the entire file. Nearby edits (within 10 lines) are merged into a single contiguous region. When edits change the line count, a shift hint tells the model how to adjust cached line numbers (e.g. "update line numbers by +2 (old line 14 → new line 16)").

Edit response format

The response is tiered by complexity:

Tier Condition Response
1 Single region, no line count change Hashlines for region with 1 line of context on each side. No shift hint.
2 Single region, line count changed Hashlines for region. Shift hint with offset and one concrete old → new example.
3 Multiple disjoint regions Hashlines per region. Per-region shift hint with cumulative offset.

Example tier 2 response (insert 2 lines after line 2):

Applied 1 edit(s) to helpers.R (now 7 lines, previously 5).

2:bbb|line before edit
3:f1a|inserted line A
4:c3d|inserted line B
5:e5f|line after edit

Content below line 5 was not modified.
Cached hashes are still valid — update line numbers by +2 (old line 3 → new line 5).

Verification

library(btw)

# Read a file — model sees hashline-annotated output
result <- btw_tool_files_read("R/tool-files.R", line_start = 1, line_end = 5)
cat(result@value)
# 1:a3f|#' @include tool-result.R
# 2:b1c|NULL
# 3:d4e|
# 4:f2a|hashline <- function(line) {
# 5:c8b|  substr(rlang::hash(substr(trimws(line), 1, 80)), 1, 3)

# Edit using hash references from the read
btw_tool_files_edit("test.R", edits = list(
  list(action = "replace", line = "4:f2a", content = list("new_hashline <- function(line) {"))
))

Add hashline() and format_hashlines() helpers that annotate file
content with line_number:hash| prefixes. Modify btw_tool_files_read
to return hashline-annotated output in @value for the model while
keeping clean code blocks in display.markdown for users.
Update existing tests that checked @value for code-fenced output to
use expect_match with hashline regex patterns. Add new tests verifying
hashline-annotated output format and correct line numbering in ranges.
Implement the edit tool with replace, insert_after, and replace_range
actions. Edits are validated against hashline references from the read
tool to detect concurrent modifications. Includes S7 class with
diffviewer integration and full tool registration.
Cover replace, delete, insert_after, insert at top, replace_range,
stale hash rejection, overlapping edit detection, multiple edits,
line expansion, range deletion, and path security validation.
Run devtools::document() to create Rd file for the new edit tool
and update NAMESPACE with the new export.
Add include_hashline parameter to btw_tool_files_read_impl() that
defaults to TRUE. btw_this() passes include_hashline = FALSE so it
returns the original code-block format instead of hashline annotations.
Invert the default so only the model-facing tool wrapper opts in to
hashlines. Internal callers like btw_this() get clean output without
needing to explicitly pass include_hashline = FALSE.
The edit tool response now includes hashline-annotated context around
each edited region, so the model can make follow-up edits without
re-reading the entire file. Nearby edits (within 10 lines) are merged
into a single region. When edits change the line count, a shift hint
tells the model how to adjust its cached line numbers.
- Extract validate_one_hash() to deduplicate hash validation logic
- Extract splice_lines() to DRY before/after slicing in apply_edits
- Extract sort_edits_ascending/descending shared sort helpers
- Pre-filter insert_after edits in check_edit_overlaps
- Add diffviewer warning to edit tool's contents_shinychat method
- Fix sprintf no-op in edit tool shinychat path construction
- Rename duplicate test name for multi-edit response test
- Add tests for last-line edit and parser error paths
The edit response now returns hashlines and shift hints, so the model
can chain follow-up edits without re-reading the file. Update the tool
description, argument docs, and guidance to teach this workflow.
- btw_tool_files_read: Add R code examples, USAGE NOTES section,
  clarify argument descriptions with concrete examples
- btw_tool_files_edit: Reorganize into WHEN TO USE, EDIT ACTIONS,
  RESPONSE FORMAT, WORKFLOW, and NOTES sections; add hashline
  reference example; clarify atomic behavior
- btw_tool_files_replace: Add HOW IT WORKS section, use case examples,
  TIPS FOR SUCCESS guidance; improve argument clarity
@gadenbuie gadenbuie merged commit 48623cb into main Feb 17, 2026
9 checks passed
@gadenbuie gadenbuie deleted the feat/read-edit-tool branch February 17, 2026 20:25
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