Recover from empty verified edit calls
ober
40477bfd852cf03f8a6335bea17887b4da3560dd
--- a/src/jcode/core/verified-run.ss +++ b/src/jcode/core/verified-run.ss @@ -1609,6 +1609,16 @@ (def (missing-read-path-message) "read requires a path. Do not retry empty read(). Use list(path=\".\") to inspect the current directory, or read(path=\"Makefile\"), read(path=\"README.md\"), read(path=\"AGENTS.md\"), or read(path=\"test/run-tests.sh\") for a specific file that exists. If you already understand the repo, use edit/write with the first complete draft, then verify().") +(def (missing-file-tool-path-message tool) + (string-append + (symbol->string tool) + " requires a path. Do not retry empty " + (symbol->string tool) + "(). Use list(path=\".\") to inspect the current directory, or read/head/tail/wc with a concrete current-repo file path such as Makefile, README.md, AGENTS.md, or test/run-tests.sh. If you already understand the repo, use edit/write with the first complete draft, then verify().")) + +(def (missing-edit-args-message) + "edit/write requires concrete arguments. Do not retry empty edit() or write(). To create or replace a file, call edit(path=\"life.ss\", content=<complete file contents>). To patch existing code, call edit(path=\"life.ss\", old_str=<exact current text>, new_str=<replacement>) or use line_edit/replace_def/replace_range. Then call verify().") + (def (slice-content content args) (let* ((line-no (arg-int args "line" 0)) (start-line (arg-int args "start" 0)) @@ -3258,9 +3268,10 @@ (required-full-rewrite? (required-repair-full-rewrite? cwd path content old-str line-no))) (cond - ((not path) (error 'edit "missing path arg")) + ((not path) + (raise-recoverable-tool-error (missing-edit-args-message) 'edit)) ((and (not content) (not old-str)) - (error 'edit "missing content arg or old_str/new_str args")) + (raise-recoverable-tool-error (missing-edit-args-message) 'edit)) ((placeholder-only-content? (or new-str content)) (guard-placeholder-content! 'edit path (or new-str content))) ((write-scope-error "edit" (scope-path cwd path)) @@ -3619,7 +3630,9 @@ (let ((path (arg-path args #f))) (if path (read-lines-at path cwd 'tail (line-tool-count args 50)) - "tail: missing path")))) + (raise-recoverable-tool-error + (missing-file-tool-path-message 'tail) + 'tail))))) '())) (head-def (make-tool-def @@ -3630,7 +3643,9 @@ (let ((path (arg-path args #f))) (if path (read-lines-at path cwd 'head (line-tool-count args 50)) - "head: missing path")))) + (raise-recoverable-tool-error + (missing-file-tool-path-message 'head) + 'head))))) '())) (wc-def (make-tool-def @@ -3641,7 +3656,9 @@ (let ((path (arg-path args #f))) (if path (string-append (line-count-for path cwd) " " path) - "wc: missing path")))) + (raise-recoverable-tool-error + (missing-file-tool-path-message 'wc) + 'wc))))) '())) (balance-def (make-tool-def --- a/test/run.ss +++ b/test/run.ss @@ -2389,6 +2389,48 @@ (safe-delete-test-file! target-path)) (let* ([vr-dir "/tmp"] + [target "jcode-verified-empty-edit-recovery.txt"] + [target-path (string-append vr-dir "/" target)] + [tool-results '()] + [wf (coding-workflow (string-append "grep -q ok " target) vr-dir)] + [resp (scripted-responder + (list + (list (make-wtool-call "head" '() #f)) + (list (make-wtool-call "wc" '() #f)) + (list (make-wtool-call "write" '() #f)) + (list (make-wtool-call "edit" '() #f)) + (list (make-wtool-call "edit" (list (cons "path" target) + (cons "content" "ok\n")) #f)) + (list (make-wtool-call "verify" '() #f)) + (list (make-wtool-call "done" '(("summary" . "empty-edit-recovered")) #f))))]) + (safe-delete-test-file! target-path) + (let ([result (run-workflow wf "recover from empty write/edit calls" resp + (list (cons 'max-iterations 10) + (cons 'max-tool-errors 0) + (cons 'on-message + (lambda (m) + (when (equal? (message-role m) "tool") + (set! tool-results + (cons (message-content m) tool-results)))))))]) + (check! "verified-run: empty inspect and edit calls recover with zero hard errors" + result "empty-edit-recovered") + (check-pred! "verified-run: empty edit/write gives recoverable concrete args" + (reverse tool-results) + (lambda (xs) + (let loop ((ys xs) (saw-head #f) (saw-edit #f)) + (if (null? ys) + (and saw-head saw-edit) + (loop (cdr ys) + (or saw-head + (and (str-contains? (car ys) "[ToolRecoverableError]") + (str-contains? (car ys) "head requires a path"))) + (or saw-edit + (and (str-contains? (car ys) "[ToolRecoverableError]") + (str-contains? (car ys) "edit/write requires concrete arguments") + (str-contains? (car ys) "edit(path=\"life.ss\""))))))))) + (safe-delete-test-file! target-path)) + +(let* ([vr-dir "/tmp"] [fixture "jcode-verified-run-budget-fixture.txt"] [fixture-path (string-append vr-dir "/" fixture)] [target "jcode-verified-run-budget-out.txt"]