summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVineet Kumar <git@vineetk.net>2026-05-24 20:21:33 -0400
committerVineet Kumar <git@vineetk.net>2026-05-24 20:36:53 -0400
commitf0ecdc6a955b7d0f9020aba3a8e2c46c7e22f9d5 (patch)
tree3b92b893543ca9cbc5540d55aa749660e1a53ee0
parent45b881a7f0b02a00e3f54beb329c8f938830f6ba (diff)
add beginning of hashline verification
-rw-r--r--llm-tools-hl.el107
1 files changed, 102 insertions, 5 deletions
diff --git a/llm-tools-hl.el b/llm-tools-hl.el
index 5a984e8..1ec13ef 100644
--- 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."
89 "Return FILE contents in hashline format from lines BEG to END." 89 "Return FILE contents in hashline format from lines BEG to END."
90 (llm-tools--hl-format-lines (llm-tools--hl-file-lines file beg end) beg t)) 90 (llm-tools--hl-format-lines (llm-tools--hl-file-lines file beg end) beg t))
91 91
92;; FIXME might be redundant with =llm-tools--hl-file-line-hash= if I'm only
93;; verifying the hash of the anchors and telling LLM to re-read the
94;; file when they don't match
95(defun llm-tools--hl-file-read-hashes (file) 92(defun llm-tools--hl-file-read-hashes (file)
96 "Return FILE contents in hashline format without content." 93 "TODO rewrite docstring to be less redundant. Return FILE contents in hashline format without content as a list of hashline-formatted hashes."
97 (llm-tools--hl-format-lines (llm-tools--hl-file-lines file))) 94 (string-lines (llm-tools--hl-format-lines (llm-tools--hl-file-lines file))))
98 95
99(defun llm-tools--hl-file-line-hash (file n) 96(defun llm-tools--hl-file-line-hash (file n)
100 "Return the hash of the Nth line in FILE." 97 "Return the hash of the Nth line in FILE."
@@ -107,4 +104,104 @@ If INCLUDE-CONTENT is non-nil, append `|LINE' after each hash."
107;; 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 104;; 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
108;; so I just need to verify if the anchors didn't change 105;; so I just need to verify if the anchors didn't change
109 106
107;;;; Diff Parsing
108(defun llm-tools--split-patch-sections (patch)
109 "Return a list of (FILENAME . REST) cons cells from PATCH.
110Each section starts with '@@ PATH' on the first line; the car is PATH
111and the cdr is the remainder of that section."
112 (let ((pos (string-match "^@@" patch)))
113 (when pos
114 (let (sections
115 (start pos))
116 (while (string-match "\n@@" patch (1+ pos))
117 (let ((nl (match-beginning 0)))
118 (push (substring patch start (1+ nl)) sections)
119 (setq start (1+ nl)))
120 (setq pos (match-end 0)))
121 (push (substring patch start) sections)
122 (mapcar (lambda (sec)
123 (let ((end (string-match "\n" sec)))
124 (cons (substring sec 3 end)
125 (substring sec end))))
126 (nreverse sections))))))
127
128(defun llm-tools--hl-get-anchors (patch)
129 "Returns an alist of (FILENAME . ANCHORS) from PATCH.
130ANCHORS is a sorted, deduplicated list of all line anchors referenced by
131the operations in that file's sections. Each anchor is a string like
132\"5ff\" or a list of two strings from a range like
133(\"3gv\" \"6be\")."
134 (let ((sections (split-patch-sections patch)))
135 (delete-dups
136 (mapcar (lambda (patch)
137 (cons (car patch)
138 (delete-dups
139 (sort
140 (flatten-list
141 (mapcar (lambda (line)
142 (if (length> line 0)
143 (let ((op (char-to-string (elt line 0)))
144 (anchor (substring line 2)))
145 (pcase op
146 ((or "+" "<") anchor)
147 ((or "-" "=") (split-string anchor "\\.\\."))))))
148 (string-lines (cdr patch))))))))
149 sections))))
150
151(defun llm-tools--hl-verify (patch)
152 "TODO rewrite docstring to be lses verbose? Verifies the anchors exist in PATCH."
153 (let ((patch-anchors (llm-tools--hl-get-anchors patch)))
154 (mapcar (lambda (section)
155 (let* ((file (car section))
156 (anchors (delete "EOF" (cdr section)))
157 (hl (llm-tools--hl-file-read-hashes file)))
158 ;; cl-subsetp uses eql by default which compares by identity and not content
159 (cons file (cl-subsetp anchors hl :test #'equal))))
160 patch-anchors)))
161
162(let* ((patch "\
163# Replace one line (the payload must re-emit the original indentation)
164@@ mod.ts
165= 1vx..1vx
166~const TITLE = \"Mrs\";
167
168# Replace a full multiline statement (widen to a self-contained boundary)
169@@ mod.ts
170= 3gv..6be
171~ return [
172~ \"Mrs\",
173~ name?.trim() || \"guest\",
174~ ].join(\" \");
175
176# Insert AFTER/BEFORE a line
177@@ mod.ts
178+ 4ei
179~ \"Dr\",
180< 5ff
181~ \"Dr\",
182
183# Append to file
184@@ mod.ts
185+ EOF
186~export const done = true;
187
188# Delete a line
189@@ test.el
190- 5ff..5ff
191
192# Blank a line (replace with LF)
193@@ mod.ts
194= 5bb..5bb
195"))
196 (string-join
197 (delete-dups
198 (remq nil
199 (mapcar (lambda (elem)
200 (unless (cdr elem)
201 (format "%s had incorrect anchors. Use the 'read_file' tool to get correct anchors."
202 (car elem))))
203 (llm-tools--hl-verify patch))))
204 "\n"))
205
110(provide 'llm-tools-hl) 206(provide 'llm-tools-hl)
207