Expose concrete verified tool schemas
ober
edf95f837cec13920c4850d2454597cebf0b76b0
--- a/src/jcode/core/verified-run.ss +++ b/src/jcode/core/verified-run.ss @@ -3412,9 +3412,148 @@ ""))))))))) ) -;; Minimal JSON-Schema object; the per-arg contract rides in each tool's -;; description (nested schema alists don't serialize cleanly, descriptions do). -(def *obj-schema* '(("type" . "object"))) +(def (schema-object pairs) + (let ((ht (make-hash-table))) + (for-each + (lambda (kv) (hash-put! ht (car kv) (cdr kv))) + pairs) + ht)) + +(def *obj-schema* (schema-object (list (cons "type" "object")))) +(def *empty-obj-schema* (schema-object (list (cons "type" "object")))) + +(def (string-schema desc) + (schema-object + (list (cons "type" "string") + (cons "description" desc)))) + +(def (number-schema desc) + (schema-object + (list (cons "type" "number") + (cons "description" desc)))) + +(def (script-kind-schema) + (schema-object + (list (cons "type" "string") + (cons "enum" (list "minimal-pass" "cli-two-args" "vector-grid")) + (cons "description" "Generic starter kind")))) + +(def (path-schema required?) + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" + (string-schema "Path inside the current repository"))))) + (cons "required" (if required? (list "path") '()))))) + +(def *read-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to read")) + (cons "line" (number-schema "1-based line number to start at")) + (cons "start" (number-schema "1-based start line")) + (cons "end" (number-schema "1-based end line")) + (cons "offset" (number-schema "Character offset")) + (cons "limit" (number-schema "Maximum characters to return"))))) + (cons "required" (list "path"))))) + +(def *line-read-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to inspect")) + (cons "file" (string-schema "Alias for path")) + (cons "lines" (number-schema "Number of lines"))))) + (cons "required" (list "path"))))) + +(def *script-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" + (string-schema "New .ss script path, for example life.ss")) + (cons "kind" (script-kind-schema))))) + (cons "required" (list "path"))))) + +(def *run-alias-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "command" + (string-schema "Simple ls/cat/head/tail/wc/grep/rg inspection command"))))) + (cons "required" (list "command"))))) + +(def *edit-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to edit, for example life.ss")) + (cons "file" (string-schema "Alias for path")) + (cons "content" (string-schema "Complete new file contents, or replacement text with old_str")) + (cons "contents" (string-schema "Alias for content")) + (cons "old_str" (string-schema "Exact existing text to replace")) + (cons "new_str" (string-schema "Replacement text")) + (cons "line" (number-schema "1-based line number for line replacement"))))) + (cons "required" (list "path"))))) + +(def *write-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to write, for example life.ss")) + (cons "file" (string-schema "Alias for path")) + (cons "content" (string-schema "Complete file contents")) + (cons "contents" (string-schema "Alias for content"))))) + (cons "required" (list "path" "content"))))) + +(def *line-edit-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to edit")) + (cons "file" (string-schema "Alias for path")) + (cons "line" (number-schema "1-based line number")) + (cons "content" (string-schema "Replacement line content"))))) + (cons "required" (list "path" "line" "content"))))) + +(def *replace-def-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to edit")) + (cons "name" (string-schema "Top-level function name")) + (cons "content" (string-schema "Complete replacement definition"))))) + (cons "required" (list "path" "name" "content"))))) + +(def *replace-range-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "path" (string-schema "File path to edit")) + (cons "start" (number-schema "1-based inclusive start line")) + (cons "end" (number-schema "1-based inclusive end line")) + (cons "content" (string-schema "Replacement content for the range")) + (cons "new_str" (string-schema "Alias for content"))))) + (cons "required" (list "path" "start" "end" "content"))))) + +(def *done-schema* + (schema-object + (list (cons "type" "object") + (cons "properties" + (schema-object + (list (cons "summary" (string-schema "Short completion summary"))))) + (cons "required" (list "summary"))))) (def (scope-instruction scope) (if scope @@ -3602,31 +3741,31 @@ (make-tool-def (make-tool-spec "read" "Read a file's current contents. args: {\"path\": string, optional \"line\"/\"start\" and \"end\" line numbers, or \"offset\"/\"limit\"}." - *obj-schema*) + *read-schema*) (scoped-tool (lambda (args) (do-read args cwd))) '())) (list-def (make-tool-def (make-tool-spec "list" "List a directory under the verified working directory. args: {\"path\": string, optional; default \".\"}. Read-only." - *obj-schema*) + (path-schema #f)) (scoped-tool (lambda (args) (do-list args cwd))) '())) (ls-def (make-tool-def (make-tool-spec "ls" "Alias for list. args: {\"path\": string, optional; default \".\"}. Read-only." - *obj-schema*) + (path-schema #f)) (scoped-tool (lambda (args) (do-list args cwd))) '())) (cat-def (make-tool-def (make-tool-spec "cat" "Alias for read. args: {\"path\": string}." - *obj-schema*) + *read-schema*) (scoped-tool (lambda (args) (do-read args cwd))) '())) (tail-def (make-tool-def (make-tool-spec "tail" "Return the last lines of a file. args: {\"path\": string, \"lines\": number optional}. \"file\" is accepted as a path alias." - *obj-schema*) + *line-read-schema*) (scoped-tool (lambda (args) (let ((path (arg-path args #f))) (if path @@ -3639,7 +3778,7 @@ (make-tool-def (make-tool-spec "head" "Return the first lines of a file. args: {\"path\": string, \"lines\": number optional}. \"file\" is accepted as a path alias." - *obj-schema*) + *line-read-schema*) (scoped-tool (lambda (args) (let ((path (arg-path args #f))) (if path @@ -3652,7 +3791,7 @@ (make-tool-def (make-tool-spec "wc" "Return a file line count. args: {\"path\": string}. \"file\" is accepted as a path alias." - *obj-schema*) + (path-schema #t)) (scoped-tool (lambda (args) (let ((path (arg-path args #f))) (if path @@ -3665,13 +3804,13 @@ (make-tool-def (make-tool-spec "balance" "Check delimiter balance for a file and report unmatched paren/bracket/brace line and column. args: {\"path\": string}. Use after syntax errors such as unexpected close parenthesis." - *obj-schema*) + (path-schema #t)) (scoped-tool (lambda (args) (balance-file args cwd))) '())) (create-script-def (make-tool-def (make-tool-spec "create_verified_jerboa_script" "Create a small executable Jerboa .ss starter through the verified edit path. args: {\"path\": string ending .ss, \"kind\": \"minimal-pass\" | \"cli-two-args\" | \"vector-grid\"}. Use only when a generic template clearly matches: minimal-pass for trivial scripts, cli-two-args for simple numeric two-argument CLIs, vector-grid for 2-D vector/grid examples. For nontrivial algorithms, you may skip scaffolding and write a small complete first version, then verify and expand deterministically." - *obj-schema*) + *script-schema*) (scoped-tool (lambda (args) (create-verified-jerboa-script args cwd))) '())) @@ -3681,7 +3820,7 @@ (if run-aliases? "Safe alias for simple shell habits: supports ls/cat/head/tail/wc/grep/rg, plain file read, plain directory list, and mkdir -p PATH under write scope. Arbitrary shell is unavailable. Use verify for the configured build/test command." "Guidance-only disabled shell alias. Do not retry run. Use verify for the configured build/test command and edit/line_edit/replace_def/replace_range for repairs.") - *obj-schema*) + *run-alias-schema*) (scoped-tool (lambda (args) (if run-aliases? (do-run-alias args cwd) @@ -3693,7 +3832,7 @@ (if run-aliases? "Safe alias for simple shell habits: supports ls/cat/head/tail/wc/grep/rg, plain file read, plain directory list, and mkdir -p PATH under write scope. Arbitrary shell is unavailable. Use verify for the configured build/test command." "Guidance-only disabled shell alias. Do not retry bash. Use verify for the configured build/test command and edit/line_edit/replace_def/replace_range for repairs.") - *obj-schema*) + *run-alias-schema*) (scoped-tool (lambda (args) (if run-aliases? (do-run-alias args cwd) @@ -3705,7 +3844,7 @@ (if run-aliases? "Safe alias for simple shell habits: supports ls/cat/head/tail/wc/grep/rg, plain file read, plain directory list, and mkdir -p PATH under write scope. Arbitrary shell is unavailable. Use verify for the configured build/test command." "Guidance-only disabled shell alias. Do not retry shell. Use verify for the configured build/test command and edit/line_edit/replace_def/replace_range for repairs.") - *obj-schema*) + *run-alias-schema*) (scoped-tool (lambda (args) (if run-aliases? (do-run-alias args cwd) @@ -3715,19 +3854,19 @@ (make-tool-def (make-tool-spec "edit" "Edit a file. Full write args: {\"path\": string, \"content\": string}; \"file\", \"filename\", \"file_path\", \"filepath\", \"target\", and \"target_path\" are accepted as path aliases. \"file\" is still accepted as a content alias when a separate path is present. \"contents\", \"new_content\", \"body\", and \"text\" are accepted as content aliases. Exact replacement args: {\"path\": string, \"old_str\": string, \"new_str\": string}; old_string/new_string are accepted aliases; \"content\" may be used instead of \"new_str\" when \"old_str\" is present. Line replacement args: {\"path\": string, \"line\": number, \"content\": string}." - *obj-schema*) + *edit-schema*) (scoped-tool (lambda (args) (do-edit args cwd))) '())) (write-def (make-tool-def (make-tool-spec "write" "Alias for edit. Full write args: {\"path\": string, \"content\": string}. Exact replacement accepts old_str/new_str or old_string/new_string. \"file\" is accepted as a path alias and content/file/body/text aliases are accepted like edit." - *obj-schema*) + *write-schema*) (scoped-tool (lambda (args) (do-edit args cwd))) '())) (line-edit-def (make-tool-def (make-tool-spec "line_edit" "Replace exactly one line in a file. args: {\"path\": string, \"line\": number, \"content\": string}. \"file\" is accepted as a path alias. Use this for compiler errors or verifier diagnostics with stable line numbers." - *obj-schema*) + *line-edit-schema*) (scoped-tool (lambda (args) (let ((path (arg-path args #f)) (content (arg-content args)) @@ -3748,14 +3887,14 @@ (make-tool-def (make-tool-spec "replace_def" "Replace a whole top-level function definition by name using delimiter matching. args: {\"path\": string, \"name\": string, \"content\": string}. Use this when a verifier error is inside one function and exact old_str replacement is brittle." - *obj-schema*) + *replace-def-schema*) (scoped-tool (lambda (args) (replace-definition args cwd))) '())) (replace-range-def (make-tool-def (make-tool-spec "replace_range" "Replace an inclusive line range, even if the old code is unbalanced. args: {\"path\": string, \"start\": number, \"end\": number, \"content\": string}. Replacement aliases accepted: new_content, new_str, new_string, replacement, body, text. Path/start/end without content does nothing. Use this after balance/read/sed identifies a broken span and replace_def cannot parse it." - *obj-schema*) + *replace-range-schema*) (scoped-tool (lambda (args) (replace-range args cwd))) '())) (verify-def @@ -3764,7 +3903,7 @@ (string-append "Run the build/tests (" verify-cmd "). No args. On failure it " "raises with the error output — read it, fix the code with edit, " "then call verify again.") - *obj-schema*) + *empty-obj-schema*) (make-verify-callable (scoped-tool (lambda (args) (let ((pending @@ -3784,7 +3923,7 @@ (make-tool-def (make-tool-spec "done" "Finish the task. Only call AFTER verify has passed. args: {\"summary\": string}." - *obj-schema*) + *done-schema*) (scoped-tool (lambda (args) (arg-ref args "summary" "done"))) '()))) (make-workflow --- a/test/run.ss +++ b/test/run.ss @@ -4359,7 +4359,23 @@ (lambda (s) (and (str-contains? s "create_verified_jerboa_script") (str-contains? s "optional") - (not (str-contains? s "life-blinker")))))) + (not (str-contains? s "life-blinker"))))) + (check-pred! "verified-run: edit schema advertises concrete args" + (tool-spec-parameters + (tool-def-spec (workflow-get-tool-def wf "edit"))) + (lambda (schema) + (let ([props (hashtable-ref schema "properties" #f)] + [required (hashtable-ref schema "required" '())]) + (and (hashtable-ref props "path" #f) + (hashtable-ref props "content" #f) + (member "path" required))))) + (check-pred! "verified-run: write schema requires path and content" + (tool-spec-parameters + (tool-def-spec (workflow-get-tool-def wf "write"))) + (lambda (schema) + (let ([required (hashtable-ref schema "required" '())]) + (and (member "path" required) + (member "content" required)))))) (safe-delete-test-file! target-path)) (let* ([vr-dir "/tmp"]