llm-tools.el

Opinionated tools for use by a (local) LLM
Log | Files | Refs | LICENSE

commit f0ecdc6a955b7d0f9020aba3a8e2c46c7e22f9d5
parent 45b881a7f0b02a00e3f54beb329c8f938830f6ba
Author: Vineet Kumar <git@vineetk.net>
Date:   Sun, 24 May 2026 20:21:33 -0400

add beginning of hashline verification

Diffstat:
Mllm-tools-hl.el | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 102 insertions(+), 5 deletions(-)

diff --git a/llm-tools-hl.el b/llm-tools-hl.el @@ -89,12 +89,9 @@ If INCLUDE-CONTENT is non-nil, append `|LINE' after each hash." "Return FILE contents in hashline format from lines BEG to END." (llm-tools--hl-format-lines (llm-tools--hl-file-lines file beg end) beg t)) -;; FIXME might be redundant with =llm-tools--hl-file-line-hash= if I'm only -;; verifying the hash of the anchors and telling LLM to re-read the -;; file when they don't match (defun llm-tools--hl-file-read-hashes (file) - "Return FILE contents in hashline format without content." - (llm-tools--hl-format-lines (llm-tools--hl-file-lines file))) + "TODO rewrite docstring to be less redundant. Return FILE contents in hashline format without content as a list of hashline-formatted hashes." + (string-lines (llm-tools--hl-format-lines (llm-tools--hl-file-lines file)))) (defun llm-tools--hl-file-line-hash (file n) "Return the hash of the Nth line in FILE." @@ -107,4 +104,104 @@ If INCLUDE-CONTENT is non-nil, append `|LINE' after each hash." ;; rather, the llm would not format it like the reads. it's just going to provide text according to what the anchors it gives say ;; so I just need to verify if the anchors didn't change +;;;; Diff Parsing +(defun llm-tools--split-patch-sections (patch) + "Return a list of (FILENAME . REST) cons cells from PATCH. +Each section starts with '@@ PATH' on the first line; the car is PATH +and the cdr is the remainder of that section." + (let ((pos (string-match "^@@" patch))) + (when pos + (let (sections + (start pos)) + (while (string-match "\n@@" patch (1+ pos)) + (let ((nl (match-beginning 0))) + (push (substring patch start (1+ nl)) sections) + (setq start (1+ nl))) + (setq pos (match-end 0))) + (push (substring patch start) sections) + (mapcar (lambda (sec) + (let ((end (string-match "\n" sec))) + (cons (substring sec 3 end) + (substring sec end)))) + (nreverse sections)))))) + +(defun llm-tools--hl-get-anchors (patch) + "Returns an alist of (FILENAME . ANCHORS) from PATCH. +ANCHORS is a sorted, deduplicated list of all line anchors referenced by +the operations in that file's sections. Each anchor is a string like +\"5ff\" or a list of two strings from a range like +(\"3gv\" \"6be\")." + (let ((sections (split-patch-sections patch))) + (delete-dups + (mapcar (lambda (patch) + (cons (car patch) + (delete-dups + (sort + (flatten-list + (mapcar (lambda (line) + (if (length> line 0) + (let ((op (char-to-string (elt line 0))) + (anchor (substring line 2))) + (pcase op + ((or "+" "<") anchor) + ((or "-" "=") (split-string anchor "\\.\\.")))))) + (string-lines (cdr patch)))))))) + sections)))) + +(defun llm-tools--hl-verify (patch) + "TODO rewrite docstring to be lses verbose? Verifies the anchors exist in PATCH." + (let ((patch-anchors (llm-tools--hl-get-anchors patch))) + (mapcar (lambda (section) + (let* ((file (car section)) + (anchors (delete "EOF" (cdr section))) + (hl (llm-tools--hl-file-read-hashes file))) + ;; cl-subsetp uses eql by default which compares by identity and not content + (cons file (cl-subsetp anchors hl :test #'equal)))) + patch-anchors))) + +(let* ((patch "\ +# Replace one line (the payload must re-emit the original indentation) +@@ mod.ts += 1vx..1vx +~const TITLE = \"Mrs\"; + +# Replace a full multiline statement (widen to a self-contained boundary) +@@ mod.ts += 3gv..6be +~ return [ +~ \"Mrs\", +~ name?.trim() || \"guest\", +~ ].join(\" \"); + +# Insert AFTER/BEFORE a line +@@ mod.ts ++ 4ei +~ \"Dr\", +< 5ff +~ \"Dr\", + +# Append to file +@@ mod.ts ++ EOF +~export const done = true; + +# Delete a line +@@ test.el +- 5ff..5ff + +# Blank a line (replace with LF) +@@ mod.ts += 5bb..5bb +")) + (string-join + (delete-dups + (remq nil + (mapcar (lambda (elem) + (unless (cdr elem) + (format "%s had incorrect anchors. Use the 'read_file' tool to get correct anchors." + (car elem)))) + (llm-tools--hl-verify patch)))) + "\n")) + (provide 'llm-tools-hl) +