Release 0.2.3
ober
9b71f3ba537e7c120f68e72d9f303744411dface
--- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.2 +0.2.3 --- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -430,4 +430,318 @@ ("title" . "Copy-pasting duplicate utility functions across modules in same project") - ("tools" "jerboa_compile_check" "jerboa_verify" "grep"))) + ("tools" "jerboa_compile_check" "jerboa_verify" "grep")) + (("advice" + . + "When adding a new use of an API in an entry script or module with `(only (module) ...)`, add the symbol to that import list, confirm its signature/export, and run the binary/WPO build path in addition to `jerboa_verify` or `make compile`.") + ("avoid" + . + "Do not treat a successful library compile as sufficient after using a new symbol from a module imported with `(only ...)`. Whole-program/binary builds can fail later when the entry file's restricted import list omits the symbol.") + ("id" . "wpo-unbound-only-import") + ("kinds" "debug-error" "module") + ("pattern" + . + "WPO subprocess.*attempt to reference unbound identifier|attempt to reference unbound identifier .*jerboa-entry-shebangless") + ("severity" . "medium") + ("tags" "wpo" "only-import" "unbound-identifier" + "binary-build" "jerboa") + ("title" + . + "Trusting library compile after restricted import edits") + ("tools" + "jerboa_function_signature" + "jerboa_module_exports" + "jerboa_make" + "jerboa_verify")) + (("advice" + . + "Use Scheme bytevectors with u8* FFI parameters for sockaddr and socklen buffers, copy inet_pton output into the sockaddr bytevector, and mark connect/potentially-blocking socket calls __collect_safe. This keeps buffer ownership in Scheme and avoids GC stalls during blocking FFI.") + ("avoid" + . + "Do not allocate temporary sockaddr buffers with foreign-alloc and return or thread raw pointers through helper procedures. Do not declare socket connect without __collect_safe.") + ("id" . "temporary-sockaddr-foreign-alloc") + ("kinds" "ffi" "security") + ("pattern" + . + "foreign-alloc.*SOCKADDR|foreign-procedure\\s+\"connect\"\\s+\\(int void\\* int\\)") + ("severity" . "medium") + ("tags" "ffi" "sockaddr" "foreign-alloc" "bytevector" + "collect-safe") + ("title" + . + "Avoid foreign-alloc for temporary sockaddr buffers") + ("tools" + "jerboa_security_scan" + "jerboa_howto" + "jerboa_compile_check")) + (("advice" + . + "Use the imported signal constant (for example SIGTSTP) or the stop signal returned by WSTOPSIG/raw wait status. When logging or converting statuses, use signal-number->name to verify platform meaning.") + ("avoid" + . + "Do not hardcode numeric POSIX signal values such as assuming SIGTSTP is 20. Signal numbers vary by platform; on macOS SIGTSTP is 18 and 20 is SIGCHLD.") + ("id" . "hardcoded-posix-signal-number") + ("kinds" "ffi" "module" "debug-error") + ("pattern" + . + "\\(\\+\\s+128\\s+20\\)|SIGTSTP\\(20\\)|128\\s*\\+\\s*SIGTSTP\\(20\\)") + ("severity" . "medium") + ("tags" "signals" "ffi" "posix" "macos" "job-control") + ("title" . "Hardcoding POSIX signal numbers") + ("tools" + "jerboa_apropos" + "jerboa_function_signature" + "jerboa_verify" + "jerboa_run_tests")) + (("advice" + . + "In PTY mux code, mediate terminal probes at the server boundary: strip recognized DA/XTVERSION/DECRQM sequences from client-visible output, write conservative synthetic replies back to the pane PTY, and keep incomplete ESC prefixes pending across PTY read chunks. Only add cursor-position replies when the mux can answer from its virtual screen state.") + ("avoid" + . + "Do not forward terminal identity/status queries from an inner PTY directly to the outer client terminal while also forwarding the client's stdin back to the pane as normal input. The real terminal will answer probes on stdin, and those replies can be injected into the running pane application.") + ("id" . "mux-forwarded-terminal-query-replies") + ("kinds" "code" "terminal" "mux") + ("pattern" . "ESC\\[[?>].*(c|q|\\$p)|\\x1b\\[\\?2026\\$p") + ("severity" . "high") + ("tags" "jsh" "mux" "pty" "vterm" "terminal-queries" + "stdin") + ("title" + . + "Forwarding terminal query replies into pane input") + ("tools" + "jerboa_howto" + "jerboa_verify" + "jerboa_security_scan")) + (("advice" + . + "For new code that must pass `jerboa_verify`, use the explicit quoted keyword form already emitted in generated `.sls`, e.g. `(sandbox-launch policy 'command: argv 'fail-closed?: #t)`. Then run `jerboa_verify` on the file and `jerboa_make build` for the real transpile/compile path.") + ("avoid" + . + "Do not blindly add imports when `jerboa_verify` reports an unbound identifier like `fail-closed?:` or `capture-stdout?:` inside a keyword-style call such as `sandbox-launch`. In source `.ss`, the verifier path may not apply the same keyword reader/transpiler handling as the build path for these names.") + ("id" . "verify-keyword-question-mark-unbound") + ("kinds" "verification" "subprocess" "sandbox") + ("pattern" . "unbound identifier .*\\?:") + ("severity" . "medium") + ("tags" "jerboa_verify" "keywords" "sandbox-launch" + "quoted-keyword" "fail-closed") + ("title" + . + "Verifier Reports Keyword Ending In Question Mark As Unbound") + ("tools" + "jerboa_verify" + "jerboa_make" + "jerboa_error_fix_lookup")) + (("advice" + . + "Keep SIGTTOU, SIGTTIN, and SIGTSTP ignored in the shell process itself, and reapply that invariant before every tcsetpgrp handoff or reclaim. Children should reset job-control signals to defaults before exec.") + ("avoid" + . + "Do not replace an interactive shell's ignored SIGTTOU disposition with a normal handler after fg/job foregrounding.") + ("id" . "restoring-sigttou-handler-in-interactive-shell") + ("kinds" "module" "ffi" "debug-error") + ("pattern" . "add-signal-handler!\\s+SIGTTOU|ffi-tcsetpgrp") + ("severity" . "high") + ("tags" "jsh" "job-control" "SIGTTOU" "tcsetpgrp" + "terminal") + ("title" . "Restoring SIGTTOU Handler After fg") + ("tools" "jerboa_howto" "jerboa_function_signature" + "jerboa_verify" "jerboa_make" "jerboa_security_scan")) + (("advice" + . + "Invalidate the render cache before external terminal writes or alternate prompt displays, then force the next line refresh through the full redraw path. Keep pure cursor movement helpers responsible for updating the cached cursor position.") + ("avoid" + . + "Do not keep using a line editor diff/render cache after writing arbitrary terminal output, invoking a full-screen helper, switching to cooked mode for fzf, printing completions, or drawing a search prompt.") + ("id" . "stale-terminal-render-cache-after-external-output") + ("kinds" "module" "debug-error") + ("pattern" + . + "(display-completions|term-clear-screen|term-cooked!|refresh-search).*refresh-line") + ("severity" . "medium") + ("tags" "terminal" "lineedit" "redraw" "cache" "cursor" + "external-output") + ("title" + . + "Stale terminal render cache after external output") + ("tools" + "jerboa_howto" + "jerboa_check_balance" + "jerboa_make" + "jerboa_security_scan")) + (("advice" + . + "When `foreign-procedure` reports `no entry`, load the shared object directly in the same consuming library before the FFI declarations. Keep preload for subprocess/test launch compatibility, but add an idempotent `load-shared-object` guard in the module that owns the foreign-procedure definitions.") + ("avoid" + . + "Do not assume DYLD_INSERT_LIBRARIES or LD_PRELOAD alone will make a symbol visible to a Jerboa/Chez library that declares foreign-procedure bindings.") + ("id" . "preload-only-chez-ffi-no-entry") + ("kinds" "ffi" "debug-error" "test") + ("pattern" + . + "foreign-procedure: no entry for .*|DYLD_INSERT_LIBRARIES=.*\\.dylib") + ("severity" . "medium") + ("tags" "ffi" "foreign-procedure" "DYLD_INSERT_LIBRARIES" + "load-shared-object" "macOS" "Chez") + ("title" + . + "Relying only on preload for Chez FFI no-entry errors") + ("tools" + "jerboa_error_fix_lookup" + "jerboa_howto" + "jerboa_verify" + "jerboa_run_tests")) + (("advice" + . + "Use a platform-aware probe: try GNU batch mode where available, fall back to macOS `top -l 1 -n <count>`, and assert on portable output markers such as `load average` or `Load Avg`. For tests that only need process output, prefer a simpler portable command when possible.") + ("avoid" + . + "Do not hardcode GNU `top -b -n 1` in tests that run on macOS or other BSD-derived systems.") + ("id" . "gnu-top-batch-flags-in-portable-tests") + ("kinds" "test" "debug-error") + ("pattern" + . + "top\\s+-b\\s+-n\\s+1|top: illegal option -- b") + ("severity" . "low") + ("tags" "top" "macOS" "GNU" "portable-tests" "subprocess") + ("title" . "Using GNU top batch flags in portable tests") + ("tools" "jerboa_run_tests" "jerboa_failure_advisor")) + (("advice" + . + "After freezing terminal text into a Scintilla-backed buffer, walk the terminal screen cells and reapply foreground/background/attribute styles to matching byte ranges. Reuse the terminal style allocator keyed by effective `(fg . bg)`, handle reverse/default colors, and bind copy-mode accept keys such as Return separately from normal terminal input.") + ("avoid" + . + "Do not implement terminal copy mode by replacing the editor buffer with only the terminal screen text and assuming existing terminal colors or attributes will remain visible.") + ("id" . "terminal-copy-mode-plain-text-style-loss") + ("kinds" "ffi" "test" "module") + ("pattern" + . + "qt-terminal-get-screen-text|set-text!.*vterm|SCI_SETTEXT.*terminal") + ("severity" . "medium") + ("tags" "vterm" "scintilla" "copy-mode" "terminal-colors" + "styles" "qt") + ("title" + . + "Freezing terminal copy mode as plain text loses styles") + ("tools" + "jerboa_howto" + "jerboa_verify" + "jerboa_run_tests" + "jerboa_security_scan")) + (("advice" + . + "Vendor dependencies into the repository using an absolute Git URL, add the vendored libdir to jerbuild libdirs, and make the build fetch or verify the dependency with a pre-build/vendor target. Keep local checkout paths only in non-committed personal overrides.") + ("avoid" + . + "Do not wire builds, .jerbuild files, docs, or package scripts to a developer-local checkout path such as ~/mine/jerboa-yubikey.") + ("id" . "hardcoded-local-vendor-path") + ("kinds" "all" "module" "docs" "security") + ("pattern" + . + "(~\\/mine|/Users/[^ ]+/mine|/home/[^ ]+/mine).*(vendor|libdirs|pre-build|dependency)") + ("severity" . "medium") + ("tags" "vendor" "libdirs" "git-url" "local-path" + "packaging") + ("title" + . + "Hardcoding Local Checkout Paths for Vendored Dependencies") + ("tools" "jerboa_howto" "jerboa_verify" "jerboa_make")) + (("advice" + . + "Use an external unlock factor for production, such as YubiKey PIV, OS keyring, or a user passphrase. Keep build-local secrets as explicit dev/test/recovery modes, document the threat model, and require an opt-in environment variable for the weaker mode.") + ("avoid" + . + "Do not claim an embedded or generated build-local secret is production-grade encryption. Anyone with the generated source or binary can recover it.") + ("id" . "embedded-build-secret-production-encryption") + ("kinds" "security" "module" "docs") + ("pattern" + . + "(build-secret|generated secret|embedded secret).*(production|default|encrypted|vault)") + ("severity" . "high") + ("tags" "encryption" "vault" "secret" "binary" "yubikey") + ("title" + . + "Treating Embedded Build Secrets as Production Encryption") + ("tools" + "jerboa_security_scan" + "jerboa_howto" + "jerboa_verify")) + (("advice" + . + "Remove the copied tree or place the vendored libdir before local source libdirs. Verify with a binary/WPO build so the actual bundled module closure is exercised, not just an interpreter import.") + ("avoid" + . + "Do not leave a copied dependency module tree in the project source libdir while also vendoring the canonical dependency. If the local source libdir appears first, the stale copy can shadow the vendored library.") + ("id" . "stale-local-module-shadows-vendor") + ("kinds" "module" "debug-error" "test") + ("pattern" + . + "libdirs.*scheme.*vendor|scheme/.+\\(library \\(yubikey|ssh|aws|fuse") + ("severity" . "medium") + ("tags" "vendor" "libdirs" "module-resolution" "shadowing" + "jerbuild") + ("title" + . + "Stale Local Module Copy Shadows Vendored Library") + ("tools" + "jerboa_module_exports" + "jerboa_verify" + "jerboa_make")) + (("advice" + . + "Start the target as the current user in a stopped state, attach `sudo dtruss -f -p PID` to that process, then SIGCONT the target. Use sudo only for the tracer.") + ("avoid" + . + "Do not trace a policy-learning command with `sudo dtruss command ...`; that runs the target as root and learns the wrong paths and permissions.") + ("id" . "sudo-dtruss-runs-target-as-root") + ("kinds" "script" "security") + ("pattern" + . + "sudo\\s+dtruss\\b.*\\b(codex|jsh|command|\\$@)") + ("severity" . "high") + ("tags" "dtruss" "macos" "policy" "sudo" "tracing") + ("title" + . + "Running the dtruss policy-learning target as root") + ("tools" + "jerboa_howto" + "jerboa_security_scan" + "jerboa_run_tests")) + (("advice" + . + "Keep active and inactive pane borders the same width and change only color or other non-geometric properties. Cache the last active/inactive state per container and skip redundant stylesheet writes on repeated focus/modeline refreshes.") + ("avoid" + . + "Do not indicate active Qt panes by switching between different border widths such as inactive 1px and active 2px. Stylesheet border width changes alter size hints and can force visible splitter/layout recomputation.") + ("id" . "qt-active-border-width-jitter") + ("kinds" "module" "ffi") + ("pattern" + . + "border:\\s*(1px|2px).*qt-frame-update-visual-indicators|set-style-sheet!.*border") + ("severity" . "medium") + ("tags" "qt" "stylesheet" "border" "splitter" "layout" + "jitter") + ("title" + . + "Changing active pane border width causes Qt layout jitter") + ("tools" "jerboa_howto" "jerboa_verify" "jerboa_run_tests")) + (("advice" + . + "Batch the nearest visual container that owns all changed child widgets, usually the QStackedWidget parent, as well as the editor. Re-enable both in an exception-safe cleanup path and verify with the Qt split/buffer-switch tests.") + ("avoid" + . + "Do not assume disabling updates on the QScintilla child is enough when the operation also changes parent container state such as QStackedWidget current page, terminal/image widgets, or lexer setup.") + ("id" . "child-only-qt-update-batching") + ("kinds" "module" "ffi") + ("pattern" + . + "setUpdatesEnabled\\(false\\).*SCI_SETDOCPOINTER|qt-widget-set-updates-enabled!\\s+editor\\s+#f") + ("severity" . "medium") + ("tags" "qt" "qscintilla" "stacked-widget" "repaint" + "buffer-switch" "flicker") + ("title" + . + "Batching only child editor updates during stacked pane changes") + ("tools" + "jerboa_howto" + "jerboa_verify" + "jerboa_run_tests"))) --- a/data/changelog.sexp +++ b/data/changelog.sexp @@ -20,7 +20,7 @@ ("renamed") ("tier_changes") ("tools_added") - ("version" . #f)) + ("version" . "v0.2.3")) (("added" "*csv-max-field-length*" "*json-max-total-bytes*") ("date" . "2026-06-11") ("modules_added") @@ -32,7 +32,7 @@ ("renamed") ("tier_changes") ("tools_added") - ("version" . #f)) + ("version" . "v0.2.3")) (("added") ("date" . "2026-06-11") ("modules_added") @@ -44,7 +44,7 @@ ("renamed") ("tier_changes") ("tools_added") - ("version" . #f)) + ("version" . "v0.2.3")) (("added" "sqlite-open" "sqlite-close" "sqlite-exec" "sqlite-execute" "sqlite-query" "sqlite-prepare" "sqlite-finalize" "sqlite-step" "sqlite-bind" --- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -6052,4 +6052,124 @@ "configuration" "nonempty" "default") ("title" . - "Environment variable cascade with priority fallback"))) + "Environment variable cascade with priority fallback")) + (("code" + . + ";; Pattern for a PTY mux/server path that receives bytes from an inner pane PTY\n;; and writes mediated output to the outer client terminal.\n;; Use project-prefixed helper names: WPO/library builds may already import\n;; common names such as subbytevector.\n\n;; C shim helper:\n;; int ffi_termios_query_safe(int fd) {\n;; struct termios t;\n;; if (tcgetattr(fd, &t) != 0) return 0;\n;; return ((t.c_lflag & (ECHO | ICANON)) == 0) ? 1 : 0;\n;; }\n\n(define-jsh-foreign c-ffi-termios-query-safe \"ffi_termios_query_safe\" (int) -> int)\n(define (ffi-termios-query-safe? fd)\n (= (c-ffi-termios-query-safe fd) 1))\n\n(define *terminal-query-pending* (make-eqv-hashtable))\n\n(define (mux-subbytevector bv start end)\n (let* ([len (max 0 (- end start))]\n [out (make-bytevector len)])\n (when (> len 0)\n (bytevector-copy! bv start out 0 len))\n out))\n\n(define (mux-write-query-response! pane-id pane-fd response)\n ;; Only answer when the pane can read terminal replies without line-discipline\n ;; echo/canonical buffering. If cooked, strip the query but suppress response.\n (when (> (string-length response) 0)\n (when (guard (e [#t #f]) (ffi-termios-query-safe? pane-fd))\n (guard (e [#t (void)])\n (ffi-fd-write pane-fd response (string-length response))))))\n\n(define (mux-terminal-query-match bv i)\n ;; Return '(complete end response), 'incomplete, or #f.\n ;; Typical cases to mediate:\n ;; ESC [ c primary DA\n ;; ESC [ > q XTVERSION-style query\n ;; ESC [ ? 2026 $ p DECRQM private-mode query\n ;; Keep the actual implementation byte-oriented and preserve incomplete ESC\n ;; prefixes across reads.\n ...)\n\n(define (mux-filter-terminal-queries! pane-id pane-fd data-bv)\n (let* ([pending (hashtable-ref *terminal-query-pending* pane-id (make-bytevector 0))]\n [combined (if (> (bytevector-length pending) 0)\n (bytevector-append pending data-bv)\n data-bv)]\n [n (bytevector-length combined)]\n [out (make-bytevector n)])\n (hashtable-delete! *terminal-query-pending* pane-id)\n (let loop ([i 0] [out-pos 0])\n (cond\n [(>= i n) (mux-subbytevector out 0 out-pos)]\n [(and (= (bytevector-u8-ref combined i) #x1b)\n (mux-terminal-query-match combined i))\n => (lambda (m)\n (if (eq? m 'incomplete)\n (begin\n (hashtable-set! *terminal-query-pending* pane-id\n (mux-subbytevector combined i n))\n (mux-subbytevector out 0 out-pos))\n (begin\n (mux-write-query-response! pane-id pane-fd (caddr m))\n (loop (cadr m) out-pos))))]\n [else\n (bytevector-u8-set! out out-pos (bytevector-u8-ref combined i))\n (loop (+ i 1) (+ out-pos 1))]))))") ("id" . "mux-terminal-query-mediation") + ("imports" "(chezscheme)" "(jsh ffi)") + ("notes" + . + "A terminal mux must not blindly forward DA/XTVERSION/DECRQM-style probes from the pane PTY to the real client terminal. vterm/libvterm and other terminals answer those probes on stdin; if the mux forwards stdin as pane input, replies such as ESC[?1;2c or ESC[?2026;0$y appear inside the application. Strip recognized probes from client-visible output, synthesize conservative replies back to the pane PTY only when ECHO and ICANON are both disabled, and preserve incomplete ESC prefixes across PTY read boundaries. If the pane is cooked, a synthetic response can echo visibly as ^[P>|jsh-mux... and canonical input may buffer it anyway, so suppress the reply while still stripping the query. Avoid generic helper names such as subbytevector in library/WPO builds; they can collide with imported bindings and fail make binary with a multiple definitions error. Add DSR/CPR only with cursor-aware handling.") + ("tags" "jsh/mux-server" "mux" "terminal" "pty" "DA" + "DECRQM") + ("title" + . + "Mediate terminal identity/status queries in a PTY mux")) + (("code" + . + "(import :std/os/signal\n :jsh/ffi)\n\n(def (ignore-shell-job-control-signals!)\n ;; Interactive shells must not be stopped while handing the tty\n ;; to or from a foreground job. Children should reset these signals\n ;; to defaults before exec.\n (ffi-signal-set-ignore SIGTSTP)\n (ffi-signal-set-ignore SIGTTIN)\n (ffi-signal-set-ignore SIGTTOU))\n\n(def (give-terminal-to-pgrp! pgid)\n (ignore-shell-job-control-signals!)\n (with-catch (lambda (e) #!void)\n (lambda () (ffi-tcsetpgrp 0 pgid))))\n\n(def (reclaim-terminal-for-shell!)\n (ignore-shell-job-control-signals!)\n (with-catch (lambda (e) #!void)\n (lambda () (ffi-tcsetpgrp 0 (ffi-getpgid 0)))))") ("id" . "interactive-shell-tcsetpgrp-sigttou-ignore") + ("imports" "(std os signal)" "(jsh ffi)") + ("notes" + . + "If an interactive shell installs a normal SIGTTOU handler after `fg`, the next foreground external command may leave the terminal foreground pgrp on the child. The next line read can then fail with EIO/input-output-error because the shell is in a background process group. Keep SIGTSTP/SIGTTIN/SIGTTOU ignored in the shell itself, and call the helper before both giving the tty to a job and reclaiming it.") + ("tags" "jsh" "job-control" "SIGTTOU" "tcsetpgrp" + "foreground" "terminal") + ("title" + . + "Ignore Job-Control Stop Signals Around tcsetpgrp")) + (("code" + . + "(import (jerboa prelude))\n\n(def ESC-STR (string (integer->char 27)))\n(def (term-cursor-forward n out)\n (when (> n 0) (display (str ESC-STR \"[\" n \"C\") out)))\n(def (term-cursor-back n out)\n (when (> n 0) (display (str ESC-STR \"[\" n \"D\") out)))\n(def (term-clear-to-eol out)\n (display (string-append ESC-STR \"[K\") out))\n\n(defstruct render-state\n (prompt-width columns buffer cursor last-buffer last-cursor valid)\n transparent: #t)\n\n(def (screen-offset st pos)\n (+ (render-state-prompt-width st) pos))\n\n(def (same-row? st a b)\n (let ([cols (render-state-columns st)])\n (and (> cols 0)\n (= (quotient (screen-offset st a) cols)\n (quotient (screen-offset st b) cols)))))\n\n(def (record-render! st)\n (render-state-last-buffer-set! st (string-copy (render-state-buffer st)))\n (render-state-last-cursor-set! st (render-state-cursor st))\n (render-state-valid-set! st #t))\n\n(def (emit-cursor-delta delta out)\n (cond\n ((> delta 0) (term-cursor-forward delta out))\n ((< delta 0) (term-cursor-back (- delta) out))))\n\n(def (common-prefix-length a b)\n (let* ([limit (min (string-length a) (string-length b))])\n (let loop ([i 0])\n (if (and (< i limit) (char=? (string-ref a i) (string-ref b i)))\n (loop (+ i 1))\n i))))\n\n(def (full-redraw! st out)\n (display \"\\r> \" out)\n (display (render-state-buffer st) out)\n (term-clear-to-eol out)\n (term-cursor-back (- (string-length (render-state-buffer st))\n (render-state-cursor st))\n out)\n (force-output out)\n (record-render! st))\n\n(def (diff-redraw! st out)\n (let* ([old (render-state-last-buffer st)]\n [new (render-state-buffer st)]\n [old-cur (render-state-last-cursor st)]\n [new-cur (render-state-cursor st)]\n [max-len (max (string-length old) (string-length new))])\n (if (and (render-state-valid st)\n (same-row? st 0 max-len))\n (let ([start (common-prefix-length old new)])\n (emit-cursor-delta (- start old-cur) out)\n (display (substring new start (string-length new)) out)\n (term-clear-to-eol out)\n (emit-cursor-delta (- new-cur (string-length new)) out)\n (force-output out)\n (record-render! st))\n (full-redraw! st out))))\n\n(def (append-and-refresh! st text out)\n (let ([old (render-state-buffer st)]\n [old-cur (render-state-cursor st)])\n (render-state-buffer-set! st (string-append old text))\n (render-state-cursor-set! st (+ old-cur (string-length text)))\n (if (and (render-state-valid st)\n (= old-cur (string-length old))\n (same-row? st old-cur (render-state-cursor st)))\n (begin\n (display text out)\n (force-output out)\n (record-render! st))\n (diff-redraw! st out))))\n\n(def st (make-render-state 2 80 \"\" 0 \"\" 0 #f))\n(full-redraw! st (current-output-port))\n(append-and-refresh! st \"abc\" (current-output-port))\n(display \"\\n\")\n") ("id" . "terminal-lineedit-incremental-redraw-cache") + ("imports" "(jerboa prelude)") + ("notes" + . + "Use a render cache only when it matches the actual terminal cursor. Fast append/backspace paths should be limited to end-of-line edits that stay on the same screen row. Any external terminal output, completion listing, full-screen helper, search prompt, or clear-screen operation must invalidate the cache so the next refresh does a full redraw. Batch ANSI sequences with call-with-string-output-port when repainting more than a cursor move. The example uses (integer->char 27) rather than #\\\\escape so it works under verifier reader paths that do not accept that character name; it also uses explicit defstruct setters so it stands alone without generalized set! imports.") + ("tags" "terminal" "lineedit" "redraw" "cursor" + "bracketed-paste" "call-with-string-output-port") + ("title" + . + "Terminal line editor incremental redraw with render cache")) + (("code" + . + "#!/usr/bin/env python3\nimport os, pty, select, signal, time\n\nbinary = os.environ.get(\"JSH_BINARY\", \"./jsh-macos\")\nrepo = os.environ.get(\"JSH_REPO\", os.getcwd())\nprompt_marker = b\"\\x1b[?2004h\" # bracketed-paste enable emitted before prompts\n\npid, fd = pty.fork()\nif pid == 0:\n os.chdir(repo)\n os.environ.setdefault(\"TERM\", \"xterm\")\n os.execv(binary, [binary])\n\nbuf = bytearray()\n\ndef read_some(timeout=0.2):\n end = time.time() + timeout\n while time.time() < end:\n r, _, _ = select.select([fd], [], [], max(0, end - time.time()))\n if not r:\n break\n chunk = os.read(fd, 4096)\n if not chunk:\n break\n buf.extend(chunk)\n\ndef wait_for(needle, timeout=10, start=0):\n end = time.time() + timeout\n while time.time() < end:\n pos = bytes(buf).find(needle, start)\n if pos >= 0:\n return pos + len(needle)\n read_some(0.25)\n raise RuntimeError(\"timed out waiting for %r\" % (needle,))\n\ndef send(line):\n os.write(fd, line.encode())\n\ntry:\n pos = wait_for(prompt_marker, 30)\n send(\"/bin/sh -c 'kill -STOP $$; kill -STOP $$; sleep 1'\\n\")\n pos = wait_for(b\"Stopped\", 10, pos)\n pos = wait_for(prompt_marker, 10, pos)\n send(\"fg\\n\")\n pos = wait_for(b\"Stopped\", 10, pos)\n pos = wait_for(prompt_marker, 10, pos)\n send(\"echo survived\\n\")\n wait_for(b\"survived\", 10, pos)\nfinally:\n try:\n os.kill(pid, signal.SIGHUP)\n except ProcessLookupError:\n pass\n os.close(fd)\n") ("id" . "pty-job-control-self-stop-smoke") ("imports") + ("notes" + . + "For job-control regressions, terminal-generated Ctrl-Z can be flaky in a PTY harness. A child shell that runs `kill -STOP $$` creates a deterministic stopped foreground job; running it twice lets the test exercise initial stop, `fg`, second stop, terminal reclaim, and a following command. Use a real PTY, not pipes, because `tcsetpgrp` and foreground process groups require a controlling terminal.") + ("tags" "pty" "job-control" "fg" "SIGSTOP" "regression" + "terminal") + ("title" + . + "PTY Job-Control Smoke With Self-Stopping Child")) + (("code" + . + "(import (jerboa prelude))\n(import (jsh limits))\n\n(launch-policy-reset!)\n(launch-policy-add-exec! \"/bin\")\n(displayln (policy-check-exec-allowed? (current-launch-policy) \"/bin/echo\"))\n\n(launch-policy-reset!)\n(launch-policy-add-exec! \"/bin/sh\")\n(displayln (policy-check-exec-allowed? (current-launch-policy) \"/bin/sh\"))\n(displayln (policy-check-exec-allowed? (current-launch-policy) \"/bin/echo\"))") ("id" . "jsh-policy-exec-directory-grants") + ("imports" "(jerboa prelude)" "(jsh limits)") + ("notes" + . + "Use directory ,exec entries for reviewed tool roots in jsh policy shims (for example /bin, /usr/bin, /opt). Exact executable entries still match only that executable. Keep the check realpath-based so symlinked targets compare against normalized paths.") + ("tags" "jsh" "limits" "run-policy" "exec" "allowlist" + "shim") + ("title" + . + "JSH launch policy: directory ,exec grants match contained executables")) + (("code" + . + ";; Keep routine navigation redraws to one mux snapshot frame.\n(define redraw-client!\n (case-lambda\n [(state client)\n (redraw-client! state client #f)]\n [(state client force-repaint?)\n (let ([pane (client-active-pane state client)])\n (when pane\n (resize-pane-to-client! pane client)\n (send-output-to-client client\n (string->utf8\n (render-single-pane pane session cols rows)))\n (when force-repaint?\n (force-pane-repaint! pane))))]))\n\n;; Normal window/pane navigation:\n(redraw-client! state client)\n\n;; Explicit recovery command, e.g. Ctrl-b r / ~~r:\n(redraw-client! state client #t)\n\n;; In the renderer, choose main vs alternate screen once before clearing.\n(display redraw-reset-prefix out)\n(display (if in-alt-screen? \"\\x1B;[?1049h\" \"\\x1B;[?1049l\") out)\n(display \"\\x1B;[r\\x1B;[2J\\x1B;[H\" out)") ("id" . "mux-quiet-redraw-explicit-repaint") + ("imports" + "(chezscheme)" + "(jsh mux-screen)" + "(jsh mux-session)") + ("notes" + . + "Do not force a foreground app repaint after every mux snapshot redraw. The TIOCSWINSZ rows-1 -> rows jiggle is useful for explicit recovery, but GUI terminal widgets can visibly pop or jitter when every pane/window switch sends a snapshot and then makes the app repaint. Likewise, do not bounce alternate-screen panes through main screen before redrawing; select the intended screen buffer once and clear there. Verify with make jsh-macos and make test-mux-screen.") + ("tags" "mux" "redraw" "SIGWINCH" "terminal" "gui-jitter" + "alternate-screen") + ("title" + . + "Use quiet mux redraws and explicit forced repaint recovery")) + (("code" + . + "# Makefile\nJERBUILD ?= jerbuild\nJH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null)\nVENDOR_DEP_URL ?= https://git.sr.ht/~lisp/jerboa-yubikey\nVENDOR_DEP_DIR := vendor/jerboa-yubikey\nVENDOR_DEP_LIB := $(VENDOR_DEP_DIR)/lib\nJEXEC := $(JERBUILD) exec --libdirs $(CURDIR)/$(VENDOR_DEP_LIB):$(CURDIR)/scheme:$(JH)/lib\n\nvendor-yubikey:\n\tsh support/ensure-vendor.sh jerboa-yubikey \"$(VENDOR_DEP_URL)\"\n\t@test -f \"$(VENDOR_DEP_LIB)/yubikey/auth.sls\" || \\\n\t { echo \"ERROR: missing $(VENDOR_DEP_LIB)/yubikey/auth.sls\" >&2; exit 1; }\n\nbinary: vendor-yubikey\n\t$(JERBUILD) build\n\n# .jerbuild\n(entry \"scheme/main.ss\")\n(output \"my-program\")\n(libdirs \"vendor/jerboa-yubikey/lib\" \"scheme\")\n(pre-build \"sh support/ensure-vendor.sh jerboa-yubikey https://git.sr.ht/~lisp/jerboa-yubikey\")\n\n# support/ensure-vendor.sh\n#!/bin/sh\nset -eu\nname=\"$1\"\nurl=\"$2\"\ncase \"$url\" in https://*|git://*|ssh://*|git@*:*) ;; *) echo \"absolute Git URL required\" >&2; exit 2;; esac\ndir=\"vendor/$name\"\nif [ -d \"$dir\" ] && { [ -f \"$dir/README.md\" ] || [ -f \"$dir/Makefile\" ]; }; then exit 0; fi\nrm -rf \"$dir\"\nmkdir -p vendor\ntmp=\"vendor/.$name.tmp.$$\"\ntrap 'rm -rf \"$tmp\"' EXIT INT TERM\ngit clone --depth 1 \"$url\" \"$tmp\"\nrm -rf \"$tmp/.git\"\nmv \"$tmp\" \"$dir\"") ("id" . "jerbuild-vendor-dependency-prebuild") ("imports") + ("notes" + . + "Put the vendored libdir before local source libdirs if the project previously carried a copied module tree; otherwise the stale local copy can shadow the canonical dependency. Use an absolute Git URL in the pre-build hook, not a developer-local path such as ~/mine/.... Exclude build artifacts from package and Docker contexts.") + ("tags" "jerbuild" "vendor" "libdirs" "makefile" + "dependency" "git-url") + ("title" + . + "Vendor a Jerboa Dependency with a Pre-Build Hook")) + (("code" + . + "#!chezscheme\n(import (except (chezscheme) make-hash-table hash-table?)\n (yubikey auth))\n\n(define (prompt-secret label)\n (let ((p (current-error-port)))\n (display label p)\n (flush-output-port p)\n (let ((s (dynamic-wind\n (lambda () (system \"stty -echo 2>/dev/null || true\"))\n (lambda () (get-line (current-input-port)))\n (lambda ()\n (system \"stty echo 2>/dev/null || true\")\n (newline p)))))\n (if (eof-object? s)\n (error 'prompt-secret \"secret required\")\n s))))\n\n(define (yubikey-vault-passphrase)\n (unless (guard (e (#t #f)) (yubikey-present?))\n (error 'vault \"YubiKey unlock requested, but no YubiKey is present or no transport is available\"))\n (let ((pin (prompt-secret \"YubiKey PIV PIN: \")))\n (dynamic-wind\n (lambda () (void))\n (lambda ()\n ;; Requires the PIV PIN and touch when the slot policy demands it.\n ;; Returns bytes, suitable for APIs that accept a bytevector passphrase.\n (string->utf8 (yubikey-piv-derive-password pin 'length: 64)))\n (lambda () (set! pin #f)))))\n\n(define passphrase (yubikey-vault-passphrase))\n;; Use passphrase with a vault-open/vault-mount API, then clear it if mutable.\n(when (bytevector? passphrase)\n (bytevector-fill! passphrase 0))") ("id" . "yubikey-piv-derived-vault-passphrase") + ("imports" "(yubikey auth)") + ("notes" + . + "This makes the binary and vault file insufficient for offline decryption; the YubiKey participates in unlocking. Keep explicit passphrase/file overrides for recovery and tests. If a blank PIV slot must be provisioned, make that opt-in; do not silently write to the token on normal unlock.") + ("tags" "yubikey" "piv" "vault" "passphrase" + "hardware-unlock" "bytevector") + ("title" . "Use YubiKey PIV as a Vault Passphrase Source")) + (("code" + . + "# Pattern for a policy learner on macOS:\n# 1. Start the target as the current user, stopped before exec.\nwrapped_cmd=(/bin/sh -c 'kill -STOP \"$$\"; exec \"$@\"' jsh-learn-policy-target \"$@\")\n\"${wrapped_cmd[@]}\" &\ntarget_pid=$!\n\n# 2. Attach dtruss to that stopped PID. Use sudo for the tracer only.\nsudo dtruss -f -p \"$target_pid\" 2>trace.log &\ntracer_pid=$!\n\n# 3. Let dtruss attach, then resume the user-owned target.\nsleep 1\nkill -CONT \"$target_pid\"\nwait \"$target_pid\"\nwait \"$tracer_pid\"\n\n# 4. Parse successful syscall lines for read/write/exec paths.\n# dtruss learns filesystem and exec rules well; it does not provide the\n# Linux LD_PRELOAD DNS hook used for host allowlist learning.") ("id" . "macos-dtruss-user-target-policy-learning") + ("imports") + ("notes" + . + "Do not run `sudo dtruss command ...` for a policy learner because the target then runs as root and observes the wrong HOME, cache paths, config, credentials, and permission behavior. Start the target under the user, stop it before exec, attach `sudo dtruss -f -p PID`, then SIGCONT. Avoid Python `preexec_fn` for the stop point in threaded programs; a small `/bin/sh -c 'kill -STOP $$; exec \"$@\"'` wrapper avoids the fork/preexec deadlock class.") + ("tags" "jsh" "policy" "dtruss" "macos" "sandbox" "tracing") + ("title" + . + "Learn macOS policy rules with dtruss attached to a stopped user target")) + (("code" + . + ";; C shim helper when the Scheme layer only has the child editor handle:\n;; extern \"C\" qt_widget_t qt_widget_parent(qt_widget_t w) {\n;; QT_NULL_CHECK_RET(w, nullptr);\n;; QT_RETURN(qt_widget_t, static_cast<QWidget*>(w)->parentWidget());\n;; }\n\n(def ffi-qt-widget-parent\n (foreign-procedure \"qt_widget_parent\" (void*) void*))\n\n(def (qt-widget-parent widget)\n (ffi-qt-widget-parent widget))\n\n(def (attach-buffer-with-one-visible-repaint! editor buf)\n (let ((container (qt-widget-parent editor)))\n (def (updates-enabled! enabled?)\n (when container\n (qt-widget-set-updates-enabled! container enabled?))\n (qt-widget-set-updates-enabled! editor enabled?))\n (updates-enabled! #f)\n (with-catch\n (lambda (e)\n (updates-enabled! #t)\n (raise e))\n (lambda ()\n ;; Replace this block with the local attach sequence:\n ;; SCI_SETDOCPOINTER\n ;; read-only and wrap-mode sync\n ;; QStackedWidget page switch for terminal/image buffers\n ;; syntax highlighter or lexer re-setup\n (sci-send editor SCI_SETDOCPOINTER 0 (buffer-doc-pointer buf))\n (run-hooks! 'post-buffer-attach-hook editor buf)\n (updates-enabled! #t)))))") ("id" . "qt-stacked-buffer-switch-batch-updates") + ("imports" + "(jerboa-emacs qt sci-shim)" + "(jerboa-emacs core)") + ("notes" + . + "Disabling updates only on QScintilla is not enough when the attach hook also switches a QStackedWidget page, creates an image/terminal view, or reconfigures a per-widget lexer. Suspend updates on the stacked parent and the editor, then re-enable in an exception handler so a failed attach does not leave the pane visually frozen. Avoid importing the window module just to find the parent if that would create a cycle; expose a tiny qt_widget_parent shim instead.") + ("tags" "qt" "qscintilla" "stacked-widget" + "setUpdatesEnabled" "buffer-switch" "flicker") + ("title" + . + "Batch QStackedWidget and editor updates during Qt buffer switches"))) --- a/data/error-fixes.sexp +++ b/data/error-fixes.sexp @@ -2161,4 +2161,126 @@ ("pattern" . "(address.already.in.use|EADDRINUSE|port.*already.*in.*use|cannot.bind.*port)") - ("type" . "runtime"))) + ("type" . "runtime")) + (("code_example" + . + ";; Generated C host pattern\n#include <sys/stat.h> /* mkfifo */\n\nstatic void register_ffi_symbols(void) {\n Sforeign_symbol(\"mkfifo\", (void *)mkfifo);\n}\n\n;; jcode build script list entry\n\"mkstemp\" \"mkdtemp\" \"mkfifo\" \"unlink\" \"rmdir\"") + ("explanation" + . + "Static binaries cannot rely on the dynamic loader to make libc symbols visible to foreign-procedure. Imported libraries may create foreign-procedure bindings at visit time, so a missing Sforeign_symbol entry fails at process startup even if the C link succeeds.") + ("fix" + . + "For a static or embedded Jerboa/Chez binary, register the missing libc entry point in the host C symbol table with Sforeign_symbol and include the correct system header. In jcode cross builds, add the symbol to the generated POSIX/libc symbol list and rebuild the target binary, not just the Scheme libraries.") + ("id" . "ffi-no-entry-static-libc-symbol") + ("pattern" + . + "Exception in foreign-procedure: no entry for \"(mkfifo|[A-Za-z0-9_]+)\"") + ("type" . "ffi-runtime")) + (("code_example" + . + "(def (ollama-native-arguments-object args)\n (cond\n ((hash-table? args) args)\n ((string? args)\n (let ((parsed (guard (e [(error? e) #f])\n (string->json-object args))))\n (if (hash-table? parsed) parsed (make-hash-table))))\n (else (make-hash-table))))") + ("explanation" + . + "Ollama native /api/chat Qwen templates can reject prior assistant tool-call history when function.arguments is serialized in OpenAI form as a JSON string.") + ("fix" + . + "For native Ollama request bodies, convert message history tool_calls[].function.arguments from JSON strings to JSON objects before json-object->string. Keep OpenAI-compatible providers on the string form.") + ("id" . "ollama-native-tool-call-arguments-object") + ("pattern" + . + "Value looks like object, but can't find closing '}' symbol") + ("type" . "provider")) + (("code_example" + . + ";; Prefer unambiguous dispatch when repairing a deeply nested parser branch.\n(case (if (string? event-type) (string->symbol event-type) 'unknown)\n ((message_start) (handle-message-start json))\n ((content_block_delta) (handle-content-block-delta json))\n (else (void)))") + ("explanation" + . + "A normal-looking cond/case branch can be misnested by one missing close delimiter. The intended later clauses become ordinary expressions in the first clause body, e.g. `((equal? event-type \"next\") ...)`; when the test returns #f, Scheme tries to call #f as a procedure. This often only triggers when the first branch is true and then execution falls into the swallowed clause expression.") + ("fix" + . + "Inspect the raise continuation with Chez `--debug-on-exception` and `i`, then `sf`/`d`/`call`. If the pending call is an intended cond clause such as `((equal? ...) ...)`, inspect the generated `.sls` around that block, run `jerboa_check_balance`, and rewrite the dispatch using balanced clauses or `case` with an explicit `else`. Rebuild and verify the generated code no longer contains the swallowed clause expression.") + ("id" . "non-procedure-from-misnested-cond-clause") + ("pattern" . "Exception: attempt to apply non-procedure #f") + ("type" . "Non-Procedure Application")) + (("code_example" + . + "# Typical repair path in a Jerboa repo\nfind lib src -name '*.so' -delete 2>/dev/null || true\nfind lib src -name '*.wpo' -delete 2>/dev/null || true\nmake <repo-build-target>") + ("explanation" + . + "Chez refuses to load compiled libraries that depend on a different compilation instance of the same runtime library. This can happen when MCP verification loads one Jerboa runtime while project-local compiled artifacts were built against another.") + ("fix" + . + "Treat this as a stale or mixed compiled-artifact/libdirs problem, not as evidence of a source syntax error. Clean stale .so/.wpo artifacts or run the repo's authoritative build target, which stages and cleans compiled caches, then rerun verification.") + ("id" . "compiled-runtime-instance-mismatch") + ("pattern" + . + "compiled .* requires a different compilation instance of \\(jerboa runtime\\) from the one previously loaded") + ("type" . "module-resolution")) + (("code_example" + . + ";; Valid in jerbuild-managed jsh source:\n(export #t)\n(import :std/sugar :jsh/ffi)\n\n;; If direct jerboa_verify says `invalid export spec #t`, run the repo build\n;; rather than replacing this with an R6RS-style export list.") + ("explanation" + . + "Some jsh root override `.ss` modules use jerbuild-facing source syntax. Direct verifier imports can parse them as plain Scheme libraries and reject `(export #t)`, even though the repository transpiler accepts and compiles them.") + ("fix" + . + "For jerbuild-managed source files that intentionally use `(export #t)`, do not patch the export form. Verify through the repository jerbuild/build path, e.g. `jerboa_make all` or the repo's `make jerboa/compile`, so the file is translated before compilation.") + ("id" . "invalid-export-spec-star-jerbuild-source") + ("pattern" . "invalid export spec #t") + ("type" . "module-resolution")) + (("code_example" + . + ";; Source may be valid in the project build even if jerboa_verify reports:\\n;; Exception in read: invalid character name #\\\\escape\\n\\n;; Confirm with the repository build target before editing valid source:\\n;; make jsh-jerbuild") + ("explanation" + . + "Some verifier paths may read .ss source with a reader that does not accept the same character names as the project Jerboa build pipeline. In this session lineedit.ss failed jerboa_verify on #\\\\escape while make jsh-jerbuild compiled it successfully.") + ("fix" + . + "If the project build/transpiler accepts the file, do not rewrite valid project source just because jerboa_verify rejects #\\\\escape. Treat this as a verifier reader-path mismatch, then verify with the repository's jerbuild/make target or a reader-aware project verifier. Only replace #\\\\escape with an integer/char workaround if the actual project build rejects it too.") + ("id" . "verify-invalid-character-name-escape") + ("pattern" + . + "Exception in read: invalid character name #\\\\\\\\escape") + ("type" . "tooling")) + (("code_example" + . + ";; Sketch for a portable subprocess test:\n(or (process-output '(\"top\" \"-b\" \"-n\" \"1\"))\n (process-output '(\"top\" \"-l\" \"1\" \"-n\" \"5\")))") + ("explanation" + . + "macOS/BSD `top` does not support GNU procps batch mode flags. Tests that invoke `top -b -n 1` pass on Linux and fail on macOS even though `top` itself is available.") + ("fix" + . + "Do not use GNU `top -b -n 1` unconditionally on macOS. Try GNU batch mode first only when supported, or fall back to macOS `top -l 1 -n <count>` and adjust assertions for `Load Avg`.") + ("id" . "macos-top-no-batch-flag") + ("pattern" + . + "top: illegal option -- b|invalid option.*-b|top -b -n 1") + ("type" . "tool-portability")) + (("code_example" + . + "# Fails\njerbuild exec --libdirs \"$LIBDIRS\" - <<'EOF'\n(import (yubikey auth))\nEOF\n\n# Use MCP eval for a one-off import check, or write a temp script and pass its path.\n") + ("explanation" + . + "Unlike many Unix interpreters, this jerbuild exec wrapper loads the FILE argument directly and does not special-case `-` as standard input.") + ("fix" + . + "Do not pass `-` to `jerbuild exec` expecting stdin. It treats `-` as a file path. Use a temporary script file, a real source path, or `jerboa_eval` for one-off expressions.") + ("id" . "jerbuild-exec-dash-is-filename") + ("pattern" + . + "Exception in load: failed for -: no such file or directory") + ("type" . "tool-usage")) + (("code_example" + . + "# Failing path:\njerboa_verify(file_path=\"jerboa-src/src/jsh/mux-server.ss\")\n\n# Correct verification path in jerboa-shell:\nmake jsh-macos\nmake test-mux-screen") + ("explanation" + . + "These files are user-facing `.ss` sources that the repo transpiles into `.sls` libraries before compilation. Direct verifier imports can hit root-level `(export ...)` forms in source `.ss` files and report a module/library error even when the jerbuild path is valid.") + ("fix" + . + "For jerboa-shell jerbuild source modules under `jerboa-src/src/jsh/*.ss`, use the repository jerbuild build target (`make jsh-macos`, `make jsh-jerbuild`, or the relevant platform target) instead of treating the source file as a directly importable library with `jerboa_verify`.") + ("id" . "jerbuild-ss-ffi-export-outside-module") + ("pattern" + . + "export form outside of a module or library .*src/jsh/ffi\\.ss.*while verifying.*jerboa-src/src/jsh/.*\\.ss") + ("type" . "module-resolution"))) --- a/data/features.sexp +++ b/data/features.sexp @@ -2822,4 +2822,114 @@ ("use_case" . "Running jcode verified with small local models that are prone to inventing tool names from their training data. The structured tool API in verified mode is narrower than the full MCP set, and models need this constraint made explicit.") + ("votes" . 0)) + (("description" + . + "Teach jerboa_verify to handle repo source files that use jerbuild-facing forms such as `(export #t)`, or to automatically route them through the repository jerbuild translation step before syntax/compile checks. Today direct verification reports `invalid export spec #t` on valid jsh root override modules, forcing a fallback to make/jerboa_make.") + ("estimated_token_reduction" + . + "~300-700 tokens per affected edit by avoiding failure-advisor/error-lookup detours and fallback explanation.") + ("example_scenario" + . + "Editing `signals.ss` in jerboa-shell: `jerboa_verify` fails with `Exception: invalid export spec #t`, while `jerboa_make all` successfully transpiles and compiles the module.") + ("id" . "verify-jerbuild-export-star-files") + ("impact" . "medium") + ("tags" "verify" "jerbuild" "export" "source-forms") + ("title" . "Verify jerbuild source files with export #t") + ("use_case" + . + "When editing jsh root override `.ss` modules that are valid only after jerbuild translation, agents should still get focused verifier feedback without treating `(export #t)` as a source error.") + ("votes" . 0)) + (("description" + . + "jerboa_verify can false-fail before expansion when it reads a valid project .ss file through a reader path that rejects project-accepted syntax such as #\\\\escape. A verifier mode that uses the same reader/transpiler path as jerbuild, or clearly falls back to the configured project build reader, would avoid unnecessary manual switching to make targets.") + ("estimated_token_reduction" + . + "~300-800 tokens per verifier false-positive by avoiding diagnosis and fallback build explanation.") + ("example_scenario" + . + "lineedit.ss failed jerboa_verify with \"Exception in read: invalid character name #\\\\escape\" at line 22, but make jsh-jerbuild transpiled and compiled the same file successfully.") + ("id" . "jerboa-verify-project-reader-for-ss") + ("impact" . "medium") + ("tags" "verify" "reader" "jerbuild" "ss" "false-positive") + ("title" + . + "Make jerboa_verify use the project Jerboa reader for .ss files") + ("use_case" + . + "Validating Jerboa user-facing .ss files that rely on project reader behavior before running a full build.") + ("votes" . 0)) + (("description" + . + "Teach the failure advisor to distinguish `foreign-procedure: no entry` cases where a dylib is already present in DYLD_INSERT_LIBRARIES/LD_PRELOAD but Chez still cannot resolve the symbol. It should recommend loading the shared object directly in the consuming Scheme library before the foreign-procedure declarations, and point to the existing load-shared-object recipe/error fix.") + ("estimated_token_reduction" + . + "~500 tokens per FFI no-entry debugging loop") + ("example_scenario" + . + "A test target preloads `jemacs_tls_stubs.dylib`, but importing `(std net tls-rustls)` fails with `foreign-procedure: no entry for jerboa_tls_server_new`; the repair is to load the stub dylib directly before the FFI declarations.") + ("id" . "ffi-no-entry-preload-advisor") + ("impact" . "medium") + ("tags" "ffi" "foreign-procedure" "load-shared-object" + "preload" "advisor") + ("title" + . + "Advise direct FFI library loading when preload fails") + ("use_case" + . + "Debugging Jerboa/Chez FFI test failures where the native library or test stub is built and preloaded but symbol lookup still fails during library visit/compile.") + ("votes" . 0)) + (("description" + . + "Allow `jerbuild exec --libdirs ... -` to read a Scheme script from standard input, or provide an explicit `--stdin` mode. The current behavior treats `-` as a literal file name, which surprises users coming from common Unix interpreters and forces temp files or MCP eval for quick import/smoke checks.") + ("estimated_token_reduction" + . + "~100-300 tokens per ad hoc import or smoke check") + ("example_scenario" + . + "Checking `(import (yubikey auth))` with project libdirs required falling back to `jerboa_eval` because `jerbuild exec ... - <<EOF` failed with `Exception in load: failed for -: no such file or directory`.") + ("id" . "jerbuild-exec-stdin-dash") ("impact" . "low") + ("tags" "jerbuild" "exec" "stdin" "developer-experience") + ("title" . "Support stdin scripts in jerbuild exec") + ("use_case" + . + "Quickly smoke-test imports or tiny expressions with the same libdirs that a project test run uses.") + ("votes" . 0)) + (("description" + . + "`jerboa_verify` and `jerboa_compile_check` can fail on jerboa-shell source modules under `jerboa-src/src/jsh/*.ss` with `export form outside of a module or library` because the repo transpiles these user-facing `.ss` files into `.sls` libraries before compilation. A verifier mode should detect `;;; jerbuild-library:` headers or repo jerbuild metadata, run the matching transpile/build check, and report the correct project target instead of a misleading module-resolution error.") + ("estimated_token_reduction" + . + "~800 tokens per failed verification by avoiding failure-advisor loops and manual Makefile inspection.") + ("example_scenario" + . + "After changing `jerboa-src/src/jsh/mux-server.ss`, `jerboa_verify` reports an error in `src/jsh/ffi.ss`, while `make jsh-macos` successfully transpiles and compiles the module. The verifier should either run that path or clearly recommend it.") + ("id" . "verify-jerbuild-source-modules") + ("impact" . "medium") + ("tags" "verify" "jerbuild" "module-resolution" "ss-source" + "transpile") + ("title" + . + "Verify jerbuild .ss source modules through their generated-library build path") + ("use_case" + . + "When editing jerboa-shell `.ss` modules that are valid only through the repo's jerbuild transpilation step.") + ("votes" . 0)) + (("description" + . + "Add a helper that drives Qt offscreen or real-window scenarios, captures before/during/after screenshots or geometry snapshots for buffer switches and split operations, and reports visible intermediate states such as blank panes, border-driven geometry shifts, or excessive repaint phases.") + ("estimated_token_reduction" + . + "~700 tokens per GUI redraw debugging session") + ("example_scenario" + . + "A change to active pane styles keeps all split-window tests green, but the inactive border is 1px and active border is 2px, causing the editor content to move on every `other-window`; a visual regression helper would catch the geometry delta.") + ("id" . "qt-gui-jitter-visual-regression-tool") + ("impact" . "medium") + ("tags" "qt" "visual-regression" "screenshot" "jitter" + "repaint") + ("title" . "Qt GUI visual jitter regression helper") + ("use_case" + . + "When debugging GUI complaints where normal unit tests pass but the user sees distracting pops, redraws, or splitter jitter.") ("votes" . 0))) --- a/data/security-rules.sexp +++ b/data/security-rules.sexp @@ -1002,4 +1002,13 @@ . "(load-shared-object|\\.so|\\.wpo).*(cp|rsync|copy|snapshot)") ("scope" . "scheme") - ("severity" . "medium"))) + ("severity" . "medium")) + (("id" . "embedded-build-secret-vault-key") + ("message" + . + "Build-local or embedded secrets are recoverable from source or binaries and must not be treated as production-grade vault keys. Use hardware, OS keyring, or user passphrase unlock for production; keep build secrets explicit dev/test fallbacks only.") + ("pattern" + . + "(browser-build-vault-secret|build-vault-secret|JERBOA_BROWSER_VAULT_UNLOCK=build-secret|BUILD_VAULT_SECRET)") + ("scope" . "scheme") + ("severity" . "high"))) --- a/docs/release-artifacts.md +++ b/docs/release-artifacts.md @@ -76,7 +76,7 @@ git tag artifacts are the stable download channel. Package repos can vendor `support/ensure-jerboa.sh` and use: ```make -JERBOA_VERSION ?= v0.2.0 +JERBOA_VERSION ?= v0.2.3 JERBOA_BINDIR := .jerboa/bin .PHONY: install --- a/lib/std/lsp/server.ss +++ b/lib/std/lsp/server.ss @@ -104,7 +104,7 @@ "textDocumentSync" (make-json-obj "openClose" #t "change" 1) "completionProvider" (make-json-obj "triggerCharacters" '("(" " " "-")) "hoverProvider" #t) - "serverInfo" (make-json-obj "name" "jerboa-lsp" "version" "0.2.0"))) + "serverInfo" (make-json-obj "name" "jerboa-lsp" "version" "0.2.3"))) (def (handle-did-open state params) (let* ([td (jref params "textDocument")] --- a/lib/std/pkg/cli.ss +++ b/lib/std/pkg/cli.ss @@ -31,7 +31,7 @@ cmd-build cmd-clean cmd-policy cmd-audit cmd-search cmd-dir)) - (def jpkg-version "0.2.0") + (def jpkg-version "0.2.3") ;; ── command table ────────────────────────────────────────────────────── ;; Each entry: (name synopsis one-line-description handler) --- a/lsp/handlers/lifecycle.ss +++ b/lsp/handlers/lifecycle.ss @@ -28,7 +28,7 @@ ;; Return capabilities (json-obj "capabilities" (server-capabilities) - "serverInfo" (json-obj "name" "jerboa-lsp" "version" "0.2.0")))) + "serverInfo" (json-obj "name" "jerboa-lsp" "version" "0.2.3")))) (define (handle-initialized state params) ;; Index workspace in background after initialization --- a/lsp/main-binary.ss +++ b/lsp/main-binary.ss @@ -23,7 +23,7 @@ (loop (cddr args) (cons (cons 'log-level (cadr args)) opts))) (loop (cdr args) opts))] [(string=? arg "--version") - (display "jerboa-lsp 0.2.0\n") + (display "jerboa-lsp 0.2.3\n") (exit 0)] [(string=? arg "--help") (display "Usage: jerboa-lsp [--stdio] [--log-level debug|info|warn|error] [--version]\n") --- a/lsp/main.ss +++ b/lsp/main.ss @@ -40,7 +40,7 @@ (loop (cddr args) (cons (cons 'log-level (cadr args)) opts))) (loop (cdr args) opts))] [(string=? arg "--version") - (display "jerboa-lsp 0.2.0\n") + (display "jerboa-lsp 0.2.3\n") (exit 0)] [(string=? arg "--help") (display "Usage: jerboa-lsp [--stdio] [--log-level debug|info|warn|error] [--version]\n") --- a/mcp/server.ss +++ b/mcp/server.ss @@ -10,7 +10,7 @@ (std misc string)) (def server-name "jerboa-mcp") -(def server-version "0.2.1") +(def server-version "0.2.3") (def result-marker "JERBOA-MCP-RESULT:") (def error-marker "JERBOA-MCP-ERROR:") (def stdout-marker "JERBOA-MCP-STDOUT:") --- a/support/binary-entry.ss +++ b/support/binary-entry.ss @@ -31,7 +31,7 @@ (loop)])))] [(or (string=? (car args) "--version") (string=? (car args) "-v")) - (displayln "jerboa-bin 0.2.0") + (displayln "jerboa-bin 0.2.3") (displayln (string-append "Bundled " (scheme-version) " (Apache 2.0, (c) Cisco Systems, Inc.)")) (displayln "See LICENSE-CHEZ for Chez Scheme's NOTICE and license.")] --- a/support/build-jerboa-multicall.ss +++ b/support/build-jerboa-multicall.ss @@ -71,8 +71,8 @@ (let ([file-version (trim-ws (read-text-file path))]) (if (> (string-length file-version) 0) file-version - "0.2.0")) - "0.2.0"))))) + "0.2.3")) + "0.2.3"))))) (define (find-csv-dir lib-dir machine) (let lp ([entries (directory-list* lib-dir)]) --- a/support/ensure-jerboa.sh +++ b/support/ensure-jerboa.sh @@ -2,10 +2,10 @@ # Bootstrap a project-local Jerboa toolchain from SourceHut release artifacts. # # Usage: -# support/ensure-jerboa.sh v0.2.0 .jerboa/bin +# support/ensure-jerboa.sh v0.2.3 .jerboa/bin # # Override the artifact location with: -# JERBOA_RELEASE_BASE=https://example.org/releases/v0.2.0 +# JERBOA_RELEASE_BASE=https://example.org/releases/v0.2.3 # or the SourceHut repo with: # JERBOA_RELEASE_REPO=~lisp/jerboa # For testing or unusual hosts, override target detection with: --- a/support/jerboa-embed.c +++ b/support/jerboa-embed.c @@ -274,5 +274,5 @@ void jerboa_error_free(jerboa_error_t *err) { } const char *jerboa_version(void) { - return "jerboa 0.2.0"; + return "jerboa 0.2.3"; }