Update Jerboa model guidance data
ober
5980b5c1d7dd0d8f5210aacca1db6ac3a2e33a11
--- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -2,116 +2,287 @@ . "Use one complete .ss script with `(import (jerboa prelude))`, and execute behavior at top level or by calling the entry point.") ("avoid" - . - "Do not create a `.sls` library or `(library ...)` form for a simple user-facing script.") - ("id" . "script-written-as-library") - ("kinds" "script") - ("pattern" . "\\.sls|\\(library\\b") - ("severity" . "high") + . + "Do not create a `.sls` library or `(library ...)` form for a simple user-facing script.") + ("id" . "script-written-as-library") ("kinds" "script") + ("pattern" . "\\.sls|\\(library\\b") ("severity" . "high") ("tags" "script" "cli" "library" "sls" "entrypoint") ("title" . "Writing a library for a simple script") ("tools" "jerboa_script_scaffold_verify" "jerboa_verify")) - (("advice" - . - "Make the script run its behavior directly, or explicitly call `(main)` after defining it.") - ("avoid" - . - "Do not only define `main` and then report success without invoking it.") - ("id" . "script-main-not-called") - ("kinds" "script") - ("pattern" . "\\(def \\(main\\b") - ("severity" . "high") - ("tags" "script" "main" "entrypoint" "output" "cli") - ("title" . "Defining main without running it") - ("tools" "jerboa_verify" "jerboa_script_scaffold_verify")) - (("advice" - . - "Use Jerboa recipes, `(jerboa prelude)`, and confirmed `(std ...)` modules; check unfamiliar APIs with exports/signature tools.") - ("avoid" - . - "Do not use Gerbil commands, Racket/SRFI assumptions, or guessed imports for Jerboa tasks.") - ("id" . "cross-dialect-assumptions") - ("kinds" "all") - ("pattern" . "gxi|gxc|#lang|srfi-|racket|gerbil") - ("severity" . "high") - ("tags" "imports" "gerbil" "racket" "srfi" "dialect") - ("title" . "Assuming another Scheme dialect") - ("tools" "jerboa_howto" "jerboa_module_exports" "jerboa_function_signature")) - (("advice" - . - "Use the recommended shell command or MCP verifier, and make it exercise the requested behavior.") - ("avoid" - . - "Do not verify with `true`, file-existence checks, or load-only commands when the request needs observable output.") - ("id" . "weak-verify-command") - ("kinds" "all") - ("pattern" . "\\btrue\\b|test -f|load-only") - ("severity" . "high") - ("tags" "verify" "jcode" "success" "tests" "behavior") - ("title" . "Using a weak verifier") - ("tools" "jerboa_verify_plan" "jerboa_verify" "jerboa_run_tests")) - (("advice" - . - "Check both lower and upper bounds before computing a vector index or calling `vector-ref`/`vector-set!`. Prefer an `in-bounds?` helper such as `(and (>= x 0) (< x width) (>= y 0) (< y height))`.") - ("avoid" - . - "Do not check only `(< x width)` and `(< y height)` before vector-backed grid access; negative coordinates still pass those checks.") - ("id" . "upper-bound-only-grid-check") - ("kinds" "script" "module" "debug-error" "test") - ("pattern" . "vector-ref|vector-set!|not a valid index|grid|neighbor") - ("severity" . "high") - ("tags" "vector" "grid" "bounds" "index" "runtime") - ("title" . "Checking only upper bounds before vector grid access") - ("tools" "jerboa_failure_advisor" "jerboa_error_fix_lookup" "jerboa_howto" "jerboa_verify")) - (("advice" - . - "Run `jerboa_check_balance`, read the enclosing top-level form, and replace the whole broken span.") - ("avoid" - . - "Do not repair unbalanced code with random one-character paren edits.") - ("id" . "random-paren-pokes") - ("kinds" "debug-error" "script" "module") - ("pattern" . "unexpected close|unexpected end|unmatched|invalid syntax") - ("severity" . "medium") - ("tags" "syntax" "paren" "balance" "repair" "replace-range") - ("title" . "Random paren edits on unbalanced code") - ("tools" "jerboa_check_balance" "jerboa_read_forms" "jerboa_failure_advisor")) - (("advice" - . - "Patch the named failing span, then re-run the same verifier before reading more unrelated files.") - ("avoid" - . - "Do not keep broad-reading after the verifier has identified a concrete error and file.") - ("id" . "broad-reading-after-concrete-failure") - ("kinds" "debug-error" "script" "module" "test") - ("pattern" . "Exception|error|failed|line [0-9]+") - ("severity" . "medium") - ("tags" "failure" "repair" "verifier" "focus" "loop") - ("title" . "Broad inspection after a concrete verifier failure") - ("tools" "jerboa_failure_advisor" "jerboa_explain_error" "jerboa_error_fix_lookup")) - (("advice" - . - "Confirm the symbol with `jerboa_module_exports`, `jerboa_suggest_imports`, or `jerboa_function_signature` before editing.") - ("avoid" - . - "Do not patch unbound or arity errors by guessing names, imports, or argument order.") - ("id" . "guessing-after-symbol-error") - ("kinds" "debug-error" "script" "module") - ("pattern" . "unbound|not bound|wrong number of arguments|arity") - ("severity" . "medium") - ("tags" "unbound" "arity" "imports" "signature" "symbols") - ("title" . "Guessing after symbol or arity errors") - ("tools" "jerboa_module_exports" "jerboa_suggest_imports" "jerboa_function_signature")) - (("advice" - . - "Use `jerboa_doc_verify` or `make check-docs` for fenced Scheme/Jerboa examples before claiming the docs are done.") - ("avoid" - . - "Do not mark documentation complete when fenced code blocks have not been compiled.") - ("id" . "unverified-doc-code") - ("kinds" "docs") - ("pattern" . "```scheme|```jerboa") - ("severity" . "medium") - ("tags" "docs" "markdown" "code-blocks" "verify" "examples") - ("title" . "Unverified documentation code blocks") - ("tools" "jerboa_doc_verify" "jerboa_doc_status_audit"))) + (("advice" + . + "Make the script run its behavior directly, or explicitly call `(main)` after defining it.") + ("avoid" + . + "Do not only define `main` and then report success without invoking it.") + ("id" . "script-main-not-called") ("kinds" "script") + ("pattern" . "\\(def \\(main\\b") ("severity" . "high") + ("tags" "script" "main" "entrypoint" "output" "cli") + ("title" . "Defining main without running it") + ("tools" "jerboa_verify" "jerboa_script_scaffold_verify")) + (("advice" + . + "Use Jerboa recipes, `(jerboa prelude)`, and confirmed `(std ...)` modules; check unfamiliar APIs with exports/signature tools.") + ("avoid" + . + "Do not use Gerbil commands, Racket/SRFI assumptions, or guessed imports for Jerboa tasks.") + ("id" . "cross-dialect-assumptions") ("kinds" "all") + ("pattern" . "gxi|gxc|#lang|srfi-|racket|gerbil") + ("severity" . "high") + ("tags" "imports" "gerbil" "racket" "srfi" "dialect") + ("title" . "Assuming another Scheme dialect") + ("tools" + "jerboa_howto" + "jerboa_module_exports" + "jerboa_function_signature")) + (("advice" + . + "Use the recommended shell command or MCP verifier, and make it exercise the requested behavior.") + ("avoid" + . + "Do not verify with `true`, file-existence checks, or load-only commands when the request needs observable output.") + ("id" . "weak-verify-command") ("kinds" "all") + ("pattern" . "\\btrue\\b|test -f|load-only") + ("severity" . "high") + ("tags" "verify" "jcode" "success" "tests" "behavior") + ("title" . "Using a weak verifier") + ("tools" + "jerboa_verify_plan" + "jerboa_verify" + "jerboa_run_tests")) + (("advice" + . + "Check both lower and upper bounds before computing a vector index or calling `vector-ref`/`vector-set!`. Prefer an `in-bounds?` helper such as `(and (>= x 0) (< x width) (>= y 0) (< y height))`.") + ("avoid" + . + "Do not check only `(< x width)` and `(< y height)` before vector-backed grid access; negative coordinates still pass those checks.") + ("id" . "upper-bound-only-grid-check") + ("kinds" "script" "module" "debug-error" "test") + ("pattern" + . + "vector-ref|vector-set!|not a valid index|grid|neighbor") + ("severity" . "high") + ("tags" "vector" "grid" "bounds" "index" "runtime") + ("title" + . + "Checking only upper bounds before vector grid access") + ("tools" + "jerboa_failure_advisor" + "jerboa_error_fix_lookup" + "jerboa_howto" + "jerboa_verify")) + (("advice" + . + "Run `jerboa_check_balance`, read the enclosing top-level form, and replace the whole broken span.") + ("avoid" + . + "Do not repair unbalanced code with random one-character paren edits.") + ("id" . "random-paren-pokes") + ("kinds" "debug-error" "script" "module") + ("pattern" + . + "unexpected close|unexpected end|unmatched|invalid syntax") + ("severity" . "medium") + ("tags" "syntax" "paren" "balance" "repair" "replace-range") + ("title" . "Random paren edits on unbalanced code") + ("tools" + "jerboa_check_balance" + "jerboa_read_forms" + "jerboa_failure_advisor")) + (("advice" + . + "Patch the named failing span, then re-run the same verifier before reading more unrelated files.") + ("avoid" + . + "Do not keep broad-reading after the verifier has identified a concrete error and file.") + ("id" . "broad-reading-after-concrete-failure") + ("kinds" "debug-error" "script" "module" "test") + ("pattern" . "Exception|error|failed|line [0-9]+") + ("severity" . "medium") + ("tags" "failure" "repair" "verifier" "focus" "loop") + ("title" + . + "Broad inspection after a concrete verifier failure") + ("tools" + "jerboa_failure_advisor" + "jerboa_explain_error" + "jerboa_error_fix_lookup")) + (("advice" + . + "Confirm the symbol with `jerboa_module_exports`, `jerboa_suggest_imports`, or `jerboa_function_signature` before editing.") + ("avoid" + . + "Do not patch unbound or arity errors by guessing names, imports, or argument order.") + ("id" . "guessing-after-symbol-error") + ("kinds" "debug-error" "script" "module") + ("pattern" + . + "unbound|not bound|wrong number of arguments|arity") + ("severity" . "medium") + ("tags" "unbound" "arity" "imports" "signature" "symbols") + ("title" . "Guessing after symbol or arity errors") + ("tools" + "jerboa_module_exports" + "jerboa_suggest_imports" + "jerboa_function_signature")) + (("advice" + . + "Use `(error 'who \"message\" irritant ...)` for ordinary validation failures, or define `(define (fail msg . xs) (apply error 'script msg xs))`. Look up `bare-string-error-arity` if the verifier reports an incorrect argument count.") + ("avoid" + . + "Do not call `(error \"message\")`, do not switch to `raise` with a bare string, and do not spend turns guessing `make-condition` helpers for simple script validation.") + ("id" . "bare-string-error-call") + ("kinds" "script" "module" "debug-error") + ("pattern" + . + "incorrect argument count in call \\(error \\\"|\\(error \\\"|\\(raise \\\"|make-condition|raise-error") + ("severity" . "medium") + ("tags" "error" "arity" "validation" "script" "raise") + ("title" . "Calling error with only a string") + ("tools" + "jerboa_error_fix_lookup" + "jerboa_howto" + "jerboa_failure_advisor")) + (("advice" + . + "Use `jerboa_doc_verify` or `make check-docs` for fenced Scheme/Jerboa examples before claiming the docs are done.") + ("avoid" + . + "Do not mark documentation complete when fenced code blocks have not been compiled.") + ("id" . "unverified-doc-code") ("kinds" "docs") + ("pattern" . "```scheme|```jerboa") ("severity" . "medium") + ("tags" "docs" "markdown" "code-blocks" "verify" "examples") + ("title" . "Unverified documentation code blocks") + ("tools" "jerboa_doc_verify" "jerboa_doc_status_audit")) + (("advice" + . + "Use `let`, `let*`, or `let-values` for local values inside a function or expression body. Keep `def`/`define` at top level or at the start of a definition body before expressions.") + ("avoid" + . + "Do not introduce local variables with `(def name ...)` or internal `(define name ...)` after expressions such as `when`, `display`, or another `let` body expression.") + ("id" . "local-def-after-expression") + ("kinds" "script" "module" "debug-error") + ("pattern" + . + "invalid context for definition|\\((def|define) [a-zA-Z0-9_?!<>*+-]+ ") + ("severity" . "high") + ("tags" "definition" "let" "local-binding" "invalid-context" + "script") + ("title" . "Using def for local bindings after expressions") + ("tools" + "jerboa_error_fix_lookup" + "jerboa_howto" + "jerboa_failure_advisor" + "jerboa_verify")) + (("advice" + . + "Remember the difference between JSON/tool-call escaping and the text that belongs in the .ss file. In actual Jerboa/Scheme source, character literals use one backslash after `#`: hash is `#\\#` and dot is `#\\.`. After writing through a JSON tool call, read or verify the file and ensure it did not contain doubled source backslashes such as `#\\\\#` or `#\\\\.`.") + ("avoid" + . + "Do not copy JSON-escaped character literals into source files as doubled backslashes, such as writing `#\\\\#` or `#\\\\.` in the actual .ss file.") + ("id" . "overescaped-character-literals") + ("kinds" "script" "module" "debug-error") + ("pattern" . "invalid sharp-sign prefix|#\\\\#|#\\\\.") + ("severity" . "medium") + ("tags" "character-literal" "sharp-sign" "json-escape" + "syntax" "reader") + ("title" + . + "Writing JSON-escaped character literals into source") + ("tools" + "jerboa_error_fix_lookup" + "jerboa_failure_advisor" + "jerboa_verify")) + (("advice" + . + "Inspect the trace for the last provider/tool events, sample the process, and check open file descriptors for duplicate sessions.db-lock handles. If a thread is blocked in flock, serialize same-process session DB opens/transactions with a mutex and add a concurrent session write regression test.") + ("avoid" + . + "Do not assume a jcode UI hang after tool results is provider/model thinking when the trace stops before the next provider stream.") + ("id" . "jcode-session-db-flock-self-deadlock") + ("kinds" "debug-error" "module" "test") + ("pattern" + . + "sessions\\.db-lock|agent: executing-tools|tool-result.*DRAW role=error|flock") + ("severity" . "high") + ("tags" "jcode" "jsqlite" "flock" "sessions-db" "threads" + "hang") + ("title" + . + "Mistaking session DB flock self-deadlock for model thinking") + ("tools" "jerboa_howto" "jerboa_module_exports" + "jerboa_function_signature" "jerboa_check_balance" + "jerboa_run_tests")) + (("advice" + . + "Check trace ordering first: if the provider has returned but tool execution does not begin, inspect session persistence and DB/lock holders. Keep active sessions in small per-session files or another bounded append-oriented store. Treat the legacy global DB as lazy fallback by explicit id only; do not list/search/open it during startup or normal chat turns. Add tests that new sessions do not create/open sessions.db.") + ("avoid" + . + "Do not leave active/new jcode sessions on the legacy global sessions.db hot path, especially when the DB can grow to hundreds of MB. jsqlite may load and rewrite the whole image, so per-turn persistence can look like the model is thinking forever.") + ("id" . "jcode-giant-session-db-hot-path") + ("kinds" "debug-error" "module") + ("pattern" + . + "session-(add-message|list|search|get-messages).*sessions\\.db|sqlite.*active chat|global session db") + ("severity" . "high") + ("tags" "jcode" "session" "jsqlite" "hot-path" "persistence" + "hang") + ("title" + . + "Keeping active chat writes on a giant global jsqlite DB") + ("tools" "jerboa_howto" "jerboa_module_exports" + "jerboa_function_signature" "jerboa_check_balance" + "jerboa_security_scan" "jerboa_make")) + (("advice" + . + "Use SSH access to linux.cons.io for Linux builds and tests. Run the relevant make, jerbuild, or shell commands remotely over ssh linux.cons.io, and copy or sync artifacts as needed instead of relying on containers.") + ("avoid" + . + "Do not use Docker or Podman for Jerboa or jerboa-emacs Linux builds, cross-build verification, or CI-style smoke tests. Do not cite unavailable Docker/Podman as a blocker.") + ("id" . "no-containers-use-linux-cons") + ("kinds" "workflow" "build" "test") + ("pattern" + . + "(?i)\\b(docker|podman)\\b.*\\b(linux|build|cross|jerboa|jerbuild)\\b") + ("severity" . "high") + ("tags" "docker" "podman" "linux.cons.io" "cross-build" + "jerboa" "jerboa-emacs") + ("title" . "Do not use containers for Linux builds") + ("tools" "exec_command")) + (("advice" + . + "Use distinct names for every binding in a `let`, `let*`, `let-values`, or named `let`. In named loops, choose separate names for the counter and the carried state, and update the recursive call in the same order, such as `(let loop ([i 0] [current grid]) ...)`.") + ("avoid" + . + "Do not bind the same identifier twice in one binding list, for example `(let loop ([g 0] [g grid]) ...)`.") + ("id" . "duplicate-let-binding-name") + ("kinds" "script" "module" "debug-error") + ("pattern" + . + "duplicate bound variable|\\(let\\s+[^()]+\\s*\\(\\([^)]*\\b([a-zA-Z0-9!?+\\-*/<>=]+)\\b[^)]*\\b\\1\\b") + ("severity" . "medium") + ("tags" "let" "named-let" "binding" "duplicate" "syntax") + ("title" . "Reusing one name for two let bindings") + ("tools" + "jerboa_error_fix_lookup" + "jerboa_failure_advisor" + "jerboa_verify")) + (("advice" + . + "Call `filter` with exactly two arguments: a one-argument predicate and the list. If the test needs a constant or a second value, close over it in a lambda, such as `(filter (lambda (c) (char=? c #\\#)) chars)`. Use `jerboa_function_signature` when unsure about a list helper's arity.") + ("avoid" + . + "Do not call `filter` with extra predicate arguments or comparison constants, such as `(filter char=? chars #\\#)`. Jerboa's `filter` does not curry or forward extra arguments into the predicate.") + ("id" . "filter-extra-comparison-arguments") + ("kinds" "script" "module" "debug-error") + ("pattern" + . + "incorrect argument count in call \\(filter|possible incorrect argument count in call \\(filter|\\(filter\\s+char=\\?\\s+[^)]*#\\\\") + ("severity" . "medium") + ("tags" "filter" "arity" "list" "predicate" "lambda") + ("title" . "Passing extra comparison arguments to filter") + ("tools" + "jerboa_error_fix_lookup" + "jerboa_function_signature" + "jerboa_failure_advisor" + "jerboa_verify"))) --- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -47,10 +47,10 @@ ("title" . "Pattern Matching")) (("code" . - "(import (jerboa prelude))\n\n; try/catch style (from jerboa prelude)\n(try\n (error \"something went wrong\" 42)\n (catch e\n (display (error-message e))\n (newline)))\n\n; try/catch/finally\n(try\n (error \"oops\")\n (catch e\n (display (error-message e)))\n (finally\n (display \"cleanup done\")))\n\n; guard style (R6RS/Chez)\n(guard (e\n [else (error-message e)])\n (error \"test error\"))") ("id" . "jerboa-try-catch") ("imports" "(jerboa prelude)") + "(import (jerboa prelude))\n\n;; Raise a proper Chez/Jerboa error with WHO + message.\n(try\n (error 'demo \"something went wrong\" 42)\n (catch e\n (display (error-message e))\n (newline)))\n\n;; try/catch/finally\n(try\n (error 'demo \"oops\")\n (catch e\n (display (error-message e))\n (newline))\n (finally\n (display \"cleanup done\")\n (newline)))\n\n;; guard style (R6RS/Chez)\n(guard (e\n [else (error-message e)])\n (error 'test \"test error\"))") ("id" . "jerboa-try-catch") ("imports" "(jerboa prelude)") ("notes" . - "Both try/catch (jerboa prelude) and guard (R6RS) work. Use error-message (not condition/message) to extract the message string. try/catch/finally implemented via guard + dynamic-wind.") + "Use `(error 'who \"message\" irritant ...)`, not `(error \"message\")`. Chez/R6RS `error` expects a who argument first (usually a symbol) followed by a message string and optional irritants. `raise` is for already-constructed condition objects; do not use `(raise \"message\")` for ordinary validation failures. Use `error-message` to extract the message string from a caught condition.") ("tags" "try" "catch" "error" "exception" "guard" "finally") ("title" . "Error Handling with try/catch")) (("code" @@ -5165,13 +5165,13 @@ "Expose a machine-readable JSON command surface for editor frontends")) (("code" . - "#!/usr/bin/env jerboa\n;;; `jerboa <script.ss>` runs a file directly; this shebang does the same.\n(import (chezscheme))\n\n;; (command-line-arguments) INCLUDES the script name as the first element:\n;; jerboa foo.ss a b -> (\"foo.ss\" \"a\" \"b\")\n;; so drop the head to get real args. ((command-line) also prepends the\n;; interpreter's own prog path.)\n(define argv (cdr (command-line-arguments)))\n(for-each (lambda (a) (display a) (newline)) argv)") ("id" . "jerboa-script-interpreter-bundled-subset") - ("imports") + "#!/usr/bin/env jerboa\n;;; `jerboa <script.ss>` runs a file directly; this shebang does the same.\n(import (chezscheme))\n\n;; `(command-line)` includes the program/interpreter element first. Depending\n;; on the launcher, the script path may be the next element, so normalize it.\n(define raw (command-line))\n(define args\n (let ([xs (if (pair? raw) (cdr raw) '())])\n (if (and (pair? xs)\n (string? (car xs))\n (let ([s (car xs)])\n (and (>= (string-length s) 3)\n (string=? (substring s (- (string-length s) 3) (string-length s)) \".ss\"))))\n (cdr xs)\n xs)))\n\n(for-each (lambda (a) (display a) (newline)) args)") ("id" . "jerboa-script-interpreter-bundled-subset") + ("imports" "(chezscheme)") ("notes" . - "The STANDALONE `jerboa` interpreter bundles only a SUBSET of (std ...) — not the whole stdlib. Confirmed resolvable from a script: (jerboa prelude), (std misc string), (chezscheme). Confirmed NOT bundled: (std os aproc), (std net tcp) — importing them raises `library (std os aproc) not found`. So for subprocess/socket/OS work in a standalone script, fall back to (chezscheme) kernel primitives (open-process-ports, file ports) — see [[open-process-ports-full-duplex]]. Import form is the PAREN form `(std misc string)`, NOT the colon `:std/misc/string` form (the colon form is only for jerbuild-compiled project source). Probe availability fast with a one-liner: `printf '(import (std X Y))(display 1)' > p.ss && jerboa p.ss`.") - ("tags" "shebang" "command-line-arguments" "interpreter" - "script" "std-subset" "library-not-found") + "The standalone `jerboa` interpreter bundles only a subset of `(std ...)`, not the whole stdlib. Confirm module availability before importing optional std libraries. Use `(command-line)`, not `command-line-arguments`, for portable script argument handling in this environment. Import form is the paren form `(std misc string)`, not the colon `:std/misc/string` form used by some compiled project source.") + ("tags" "shebang" "command-line" "interpreter" "script" + "std-subset" "library-not-found") ("title" . "Standalone jerboa scripts: shebang, args, and the bundled (std ...) subset")) @@ -5516,16 +5516,16 @@ ("title" . "Prompt Before Entering Terminal Raw Mode")) (("code" . - "(import (jerboa prelude))\n\n(def raw-args (command-line-arguments))\n(def args\n (if (and (pair? raw-args)\n (string? (car raw-args))\n (string-suffix? \".ss\" (car raw-args)))\n (cdr raw-args)\n raw-args))\n\n(def (usage)\n (display \"usage: sum.ss A B\")\n (newline))\n\n(def (parse-number label value)\n (let ([n (string->number value)])\n (if n n (error 'script \"expected numeric argument\" label value))))\n\n(if (= (length args) 2)\n (let ([a (parse-number \"A\" (list-ref args 0))]\n [b (parse-number \"B\" (list-ref args 1))])\n (display (+ a b))\n (newline))\n (usage))") ("id" . "jerboa-cli-script-args-verify-friendly") - ("imports") + "(import (jerboa prelude))\n\n(def raw-command-line (command-line))\n(def args\n (let ([xs (if (and (pair? raw-command-line)\n (string? (car raw-command-line)))\n (cdr raw-command-line)\n raw-command-line)])\n (if (and (pair? xs)\n (string? (car xs))\n (string-suffix? \".ss\" (car xs)))\n (cdr xs)\n xs)))\n\n(def (usage)\n (display \"usage: sum.ss A B\")\n (newline))\n\n(def (parse-number label value)\n (let ([n (string->number value)])\n (if n n (error 'script \"expected numeric argument\" label value))))\n\n(if (= (length args) 2)\n (let ([a (parse-number \"A\" (list-ref args 0))]\n [b (parse-number \"B\" (list-ref args 1))])\n (display (+ a b))\n (newline))\n (usage))") ("id" . "jerboa-cli-script-args-verify-friendly") + ("imports" "(jerboa prelude)") ("notes" . - "Run with `/Users/user/mine/jerboa/.chez/bin/scheme --libdirs /Users/user/mine/jerboa/lib --script sum.ss 7 5`. `scheme --script` gives user args directly, while some wrappers may include the script path; the normalizer supports both. Keep the no-arg path non-throwing so `jerboa_verify`/compile checks do not fail before the smoke run with real args.") - ("tags" "script" "cli" "command-line-arguments" "verify" - "scheme-script" "jerboa") + "Use `(command-line)`, not `command-line-arguments`. Drop the interpreter/program element first, then drop an optional `.ss` script path if the launcher includes it. Keep the no-arg path non-throwing when a verifier compiles or smoke-checks without real args.") + ("tags" "script" "cli" "command-line" "args" "verify" + "scheme-script") ("title" . - "CLI scripts with command-line-arguments that pass verification")) + "CLI scripts with command-line args that pass verification")) (("code" . "(import (jerboa prelude))\n\n(def (skip-leading-spaces s start len)\n (let loop ((i start))\n (if (and (< i len) (char-whitespace? (string-ref s i)))\n (loop (+ i 1))\n i)))\n\n(def (find-wrap-break s start end)\n (let loop ((i (- end 1)))\n (cond\n ((<= i start) -1)\n ((char-whitespace? (string-ref s i)) i)\n (else (loop (- i 1))))))\n\n(def (wrap-text s width)\n (cond\n ((<= width 0) '(\"\"))\n (#t\n (let ((len (string-length s)))\n (let loop ((start (skip-leading-spaces s 0 len)) (acc '()))\n (cond\n ((>= start len)\n (if (null? acc) '(\"\") (reverse acc)))\n (#t\n (let ((end (min len (+ start width))))\n (cond\n ((>= end len)\n (reverse (cons (substring s start len) acc)))\n (#t\n (let ((cut-point (find-wrap-break s start end)))\n (if (>= cut-point 0)\n (loop (skip-leading-spaces s (+ cut-point 1) len)\n (cons (substring s start cut-point) acc))\n (loop (skip-leading-spaces s end len)\n (cons (substring s start end) acc))))))))))))))\n\n(assert! (equal? (wrap-text \"Alpha beta gamma delta\" 12)\n '(\"Alpha beta\" \"gamma delta\")))\n(assert! (equal? (wrap-text \"abcdefghijk\" 4)\n '(\"abcd\" \"efgh\" \"ijk\")))\n(displayln \"ok\")") ("id" . "word-wrap-string-width") @@ -6253,4 +6253,28 @@ "dynamic-forwarding" "MSG-FORWARD-OPEN") ("title" . - "Mux dynamic SOCKS forwarding: local SOCKS, remote egress"))) + "Mux dynamic SOCKS forwarding: local SOCKS, remote egress")) + (("code" + . + "(import (jerboa prelude))\n\n(def raw-command-line (command-line))\n(def args (cdr raw-command-line))\n\n(def (parse-n s)\n (let ([n (string->number s)])\n (if n n (error 'parse-n \"expected a number\" s))))\n\n(def (main argv)\n (if (null? argv)\n (error 'main \"missing count\")\n (let ([count (parse-n (car argv))])\n (display count)\n (newline))))\n\n(main args)\n") ("id" . "script-local-bindings-with-let") + ("imports" "(jerboa prelude)") + ("notes" + . + "Inside a function or `begin` body, do not introduce local values with `(def name ...)` after expressions such as `when`, `display`, or another `let`. Use `let`, `let*`, or `let-values` for local bindings. Keep `def`/`define` for top-level functions and constants. Use `(command-line)` for script arguments; `command-line-arguments` is not available in this Jerboa environment.") + ("tags" "script" "let" "local-binding" "invalid-context" + "define" "def") + ("title" + . + "Use let for local bindings inside script functions")) + (("code" + . + "(import (jerboa prelude))\n\n(def raw-command-line (command-line))\n(def args\n (let ([xs (if (and (pair? raw-command-line)\n (string? (car raw-command-line)))\n (cdr raw-command-line)\n raw-command-line)])\n (if (and (pair? xs)\n (string? (car xs))\n (string-suffix? \".ss\" (car xs)))\n (cdr xs)\n xs)))\n\n(def (fail msg . irritants)\n (apply error 'script msg irritants))\n\n(def (one-nonnegative-int argv)\n (cond\n [(not (= (length argv) 1)) (fail \"expected exactly one argument\")]\n [else\n (let ([n (string->number (car argv))])\n (cond\n [(not (and n (integer? n))) (fail \"expected integer argument\" (car argv))]\n [(< n 0) (fail \"expected non-negative argument\" n)]\n [else n]))]))\n\n(def (read-stdin-lines)\n (let loop ([out '()])\n (let ([line (read-line)])\n (if (eof-object? line)\n (reverse out)\n (loop (cons line out))))))\n\n(def n (one-nonnegative-int args))\n(def lines (read-stdin-lines))\n(when (null? lines) (fail \"empty input\"))\n(for-each (lambda (line) (display line) (newline)) lines)") ("id" . "jerboa-script-validated-stdin-cli") + ("imports" "(jerboa prelude)") + ("notes" + . + "For scripts that must reject bad input non-zero, define a small `fail` helper around `(apply error 'script msg irritants)`. Do not use `(error \"message\")`; it has the wrong arity in Chez/R6RS. Use `(command-line)`, not nonexistent `command-line-arguments`; derive script arguments from `(cdr (command-line))`, and normalize an extra `.ss` script path if a wrapper includes it. `read-line` from `(jerboa prelude)` reads stdin one line at a time.") + ("tags" "script" "cli" "stdin" "validation" "error" + "command-line") + ("title" + . + "Validated stdin CLI script with proper errors"))) --- a/data/error-fixes.sexp +++ b/data/error-fixes.sexp @@ -14,6 +14,34 @@ ("type" . "Unbound Variable")) (("code_example" . + ";; Wrong\n(error \"Missing STEPS argument\")\n\n;; Right\n(error 'script \"Missing STEPS argument\")\n\n;; Helper\n(define (fail msg . irritants)\n (apply error 'script msg irritants))") + ("explanation" + . + "Chez/R6RS `error` is not a one-argument string procedure. It expects a who argument first, then the message string, then optional irritants.") + ("fix" + . + "Replace `(error \"message\" ...)` with `(error 'who \"message\" ...)`, using a symbol for `who`. For reusable validation helpers, use `(define (fail msg . xs) (apply error 'script msg xs))`.") + ("id" . "bare-string-error-arity") + ("pattern" + . + "incorrect argument count in call \\(error \\\"|possible incorrect argument count in call \\(error \\\"") + ("type" . "arity")) + (("code_example" + . + ";; WRONG: `filter` does not pass extra constants to the predicate\n(filter char=? chars #\\#)\n\n;; RIGHT: close over constants in a one-argument predicate\n(filter (lambda (c) (char=? c #\\#)) chars)\n\n;; Count matching elements\n(length (filter (lambda (c) (char=? c #\\#)) chars))") + ("explanation" + . + "Jerboa's list filter takes exactly a predicate and one list. A common local-model mistake is to pass a predicate plus extra comparison arguments, such as `(filter char=? xs #\\#)`.") + ("fix" + . + "Call `filter` with exactly two arguments: a one-argument predicate and the list. Wrap comparisons in a lambda, e.g. `(filter (lambda (c) (char=? c #\\#)) chars)`. If you only need a count, use `(length (filter (lambda (c) ...) chars))`.") + ("id" . "filter-extra-arguments") + ("pattern" + . + "incorrect argument count in call \\(filter [^)]+ [^)]+ [^)]+\\)|possible incorrect argument count in call \\(filter .*\\)|filter.*wrong number of arguments|filter.*incorrect argument count") + ("type" . "arity")) + (("code_example" + . "; Check signature:\n; (sort lst pred) takes 2 args\n(sort '(3 1 2) <)") ("explanation" . @@ -2034,5 +2062,61 @@ . "Add both lower and upper coordinate bounds before computing the index or calling vector-ref/vector-set!: `(and (>= x 0) (< x width) (>= y 0) (< y height))`. Prefer a reusable `in-bounds?` helper and use it in neighbor loops and grid accessors.") ("id" . "vector-ref-negative-grid-index") - ("pattern" . "vector-ref: -?[0-9]+ is not a valid index|not a valid index for") - ("type" . "runtime-vector-bounds"))) + ("pattern" + . + "vector-ref: -?[0-9]+ is not a valid index|not a valid index for") + ("type" . "runtime-vector-bounds")) + (("code_example" + . + ";; Bad inside a function body:\n(def (main)\n (when missing? (error 'main \"missing\"))\n (def steps (parse-steps args))\n (run steps))\n\n;; Good:\n(def (main)\n (when missing? (error 'main \"missing\"))\n (let ((steps (parse-steps args)))\n (run steps)))") + ("explanation" + . + "Jerboa script code is expanded through Chez/R6RS rules where definitions are only valid in definition contexts. A local variable assignment in expression position must be a lexical binding, not `(def name ...)`.") + ("fix" + . + "Do not use `def` or internal `define` as a statement inside `begin`, `when`, `let`, `let-values`, or procedure bodies. Move definitions to top level, or bind local values with `let`/`let*`/`let-values`. For sequential code in a function, wrap the rest of the body in a `let` that binds the value first.") + ("id" . "invalid-context-for-definition-local-binding") + ("pattern" + . + "invalid context for definition \\((define|def) [^)]*\\).*") + ("type" . "syntax")) + (("code_example" + . + ";; bad source text\n#\\\\#\n#\\\\.\n\n;; good source text\n#\\#\n#\\.") + ("explanation" + . + "The model often writes JSON-escaped character literals into the source file. `#\\\\#` in the actual file is parsed as a backslash character literal followed by an invalid `#` token, which can surface as `invalid sharp-sign prefix #)`.") + ("fix" + . + "Inspect the reported line for over-escaped character literals copied from JSON/tool-call strings. In the actual .ss source, Scheme character literals use one backslash after #. Replace #\\\\# with #\\# and #\\\\. with #\\. before re-running verify.") + ("id" . "invalid-sharp-sign-overescaped-character-literal") + ("pattern" . "invalid sharp-sign prefix #\\).*") + ("type" . "syntax")) + (("code_example" + . + ";; Bad\n(let loop ([g 0] [g grid])\n (if (= g steps)\n (display-grid g)\n (loop (+ g 1) (step g))))\n\n;; Good\n(let loop ([i 0] [current grid])\n (if (= i steps)\n (display-grid current)\n (loop (+ i 1) (step current))))") + ("explanation" + . + "Chez/Jerboa rejects duplicate identifiers in a binding list before runtime. Local models often reuse `g` both as a generation counter and as a grid value in named let loops.") + ("fix" + . + "A let/named-let binding list cannot bind the same variable name twice. Rename one binding and update all uses, e.g. `(let loop ([i 0] [grid grid]) ...)` instead of `(let loop ([g 0] [g grid]) ...)`. For named loops, use distinct names for the counter/state variables and pass them in the recursive call in the same order.") + ("id" . "duplicate-bound-variable-let") + ("pattern" + . + "duplicate bound variable [^\\n]+ in \\((let|let\\*|letrec|let-values|letrec-values|let\\s+[^\\s()]+)") + ("type" . "syntax")) + (("code_example" + . + "(import (jerboa prelude))\n\n(def raw (command-line))\n(def args\n (let ([xs (cdr raw)])\n (if (and (pair? xs)\n (string? (car xs))\n (string-suffix? \".ss\" (car xs)))\n (cdr xs)\n xs)))") + ("explanation" + . + "Jerboa/Chez provides `command-line`; `command-line-arguments` is a common Racket/Gerbil-style guess and is not available here.") + ("fix" + . + "Use `(command-line)`, not `command-line-arguments`. For script arguments, start with `(cdr (command-line))`; if your wrapper includes the `.ss` script path as the first remaining item, drop that too before parsing user args.") + ("id" . "missing-command-line-arguments") + ("pattern" + . + "Could not find command-line-arguments|unbound.*command-line-arguments|command-line-arguments.*not found") + ("type" . "api")))