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:
| M | llm-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)
+