Add cookbook task bundle tool for agents
ober
f07a8727ec392dcf7839672739e45662ed102f41
--- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -5605,16 +5605,15 @@ "Jerboa Qt offscreen timer-driven redraw smoke test")) (("code" . - "(import (jerboa prelude))\n\n(def (string-prefix-local? prefix s)\n (let ([plen (string-length prefix)]\n [slen (string-length s)])\n (and (>= slen plen)\n (string=? (substring s 0 plen) prefix))))\n\n(def (path-basename path)\n (let ([len (string-length path)])\n (let loop ([i (- len 1)])\n (cond\n [(< i 0) path]\n [(char=? (string-ref path i) #\\/)\n (substring path (+ i 1) len)]\n [else (loop (- i 1))]))))\n\n(def (jsh-shell-path? self-path path)\n (and path\n (> (string-length path) 0)\n (let ([base (path-basename path)])\n (or (string=? path self-path)\n (string=? base \"jsh\")\n (string-prefix-local? \"jsh-\" base)\n (string-prefix-local? \".jsh-program-\" base)))))\n\n(def (valid-pane-shell self-path path)\n (and path\n (> (string-length path) 0)\n (not (jsh-shell-path? self-path path))\n path))\n\n(def (pane-shell-path self-path mux-shell shell)\n (or (valid-pane-shell self-path mux-shell)\n (valid-pane-shell self-path shell)\n (if (file-exists? \"/bin/bash\") \"/bin/bash\" \"/bin/sh\")))\n\n(displayln (pane-shell-path \"/usr/local/bin/jsh\" #f \"/tmp/.jsh-program-1234.so\"))") ("id" . "mux-static-binary-pane-shell-fallback") + "(import (jerboa prelude))\n\n(def (string-prefix-local? prefix s)\n (let ([plen (string-length prefix)]\n [slen (string-length s)])\n (and (>= slen plen)\n (string=? (substring s 0 plen) prefix))))\n\n(def (fd-pseudo-path? path)\n (and path\n (> (string-length path) 0)\n (or (string-prefix-local? \"/proc/self/fd/\" path)\n (string-prefix-local? \"/dev/fd/\" path))))\n\n(def (valid-pane-shell path)\n (and path\n (> (string-length path) 0)\n (not (fd-pseudo-path? path))\n path))\n\n(def (valid-self-pane-shell path)\n (and (valid-pane-shell path)\n (file-exists? path)\n path))\n\n(def (pane-shell-path self-path mux-shell mux-jsh shell)\n (or (valid-pane-shell mux-shell)\n (valid-pane-shell mux-jsh)\n (valid-self-pane-shell self-path)\n (valid-pane-shell shell)\n (if (file-exists? \"/bin/bash\") \"/bin/bash\" \"/bin/sh\")))\n\n(def self-path (if (file-exists? \"/bin/sh\") \"/bin/sh\" \"/bin/bash\"))\n(displayln (pane-shell-path self-path #f #f \"/proc/self/fd/3\"))\n(displayln (pane-shell-path self-path \"/custom/jsh\" #f \"/proc/self/fd/3\"))") ("id" . "mux-static-binary-pane-shell-fallback") ("imports" "(jerboa prelude)") ("notes" . - "In embedded/static jsh builds, the interactive shell may set SHELL to an extracted temporary program such as /tmp/.jsh-program-1234.so. If a mux pane blindly execs $SHELL -i, the child exits 126 and the server can repeatedly respawn blank panes. Treat self paths, jsh-looking basenames, and .jsh-program-* basenames as invalid pane shells; allow an explicit non-jsh override such as JSH_MUX_SHELL before falling back to a real system shell.") - ("tags" "mux" "static-binary" "SHELL" "pty" - "embedded-program" "shell-fallback") + "For jsh mux panes, do not treat all jsh-looking paths as invalid. The broken behavior was: reject $SHELL=/proc/self/fd/N, then fall through to /bin/bash, so C-b c opened bash instead of jsh. The correct ordering is explicit JSH_MUX_SHELL, explicit JSH_MUX_JSH, the running jsh executable from ffi-self-exe-path (validated to avoid fd pseudo-paths), then a non-fd SHELL fallback, then /bin/bash or /bin/sh. Regression tests should run a mux server with SHELL=/proc/self/fd/3 and assert the debug log contains SPAWN-PANE shell=.*jsh, not shell=/bin/bash.") + ("tags" "mux" "static-binary" "SHELL" "pty" "fd-path" "jsh") ("title" . - "Avoid recursive mux panes from static-binary SHELL values")) + "Prefer the jsh executable when mux SHELL is an fd pseudo-path")) (("code" . ";;; Contract verifier file. Generated module must export:\n;;; (make-static-board-window paint-count-box) -> values window canvas\n(import (jerboa prelude))\n(import (chezscheme))\n(import (jerboa-qt qt))\n(import (huihui static-board))\n\n(def immediate-exit (foreign-procedure \"_exit\" (int) void))\n\n(def (finish status)\n (flush-output-port (current-output-port))\n (flush-output-port (current-error-port))\n (immediate-exit status))\n\n(def (check expr message)\n (unless expr\n (error 'generated-qt-contract \"~a\" message)))\n\n(def (pump! app n)\n (when (> n 0)\n (qt-app-process-events! app)\n (pump! app (- n 1))))\n\n(def app (qt-app-create))\n(def paint-count-box (vector 0))\n\n(let-values (((win canvas) (make-static-board-window paint-count-box)))\n (check (not (eqv? win 0)) \"window handle should be nonzero\")\n (check (not (eqv? canvas 0)) \"canvas handle should be nonzero\")\n (qt-widget-show! win)\n (qt-paint-widget-update! canvas)\n (pump! app 10)\n (check (> (vector-ref paint-count-box 0) 0)\n \"paint callback did not increment verifier-owned counter\")\n (check (qt-widget-screenshot! win \"/tmp/generated-qt-contract.png\")\n \"screenshot failed\")\n (displayln \"generated Qt contract passed\")\n (finish 0))\n") ("id" . "jerboa-qt-generated-module-contract") @@ -6010,4 +6009,156 @@ "r6rs") ("title" . - "Classify top-level definition macros before reordering library bodies"))) + "Classify top-level definition macros before reordering library bodies")) + (("code" + . + "(import (jerboa prelude))\n\n;; Replace these stubs with project-specific socket/process primitives.\n(def (open-listener sock-path backlog)\n ;; Must bind and listen before returning a non-negative fd.\n 3)\n(def (close-listener fd) #t)\n(def (remove-socket! path) #t)\n(def (publish-ready! pid-path) 0) ;; 0 ok, negative errno on failure\n(def (read-ready-pid pid-path) 12345)\n(def (child-finished-status pid) #f) ;; e.g. waitpid(pid, WNOHANG)\n(def (sleep-ms n) #t)\n\n(def (start-listener-and-publish-ready sock-path pid-path)\n (let ([listen-fd (open-listener sock-path 5)])\n (when (< listen-fd 0)\n (error 'start-listener-and-publish-ready \"cannot listen\" sock-path))\n ;; Publish readiness only after listen() has succeeded, and treat failure\n ;; to publish as startup failure: the parent relies on this marker.\n (let ([rc (publish-ready! pid-path)])\n (when (< rc 0)\n (close-listener listen-fd)\n (remove-socket! sock-path)\n (error 'start-listener-and-publish-ready \"cannot publish readiness\" pid-path)))\n listen-fd))\n\n(def (wait-for-ready-pid pid-path expected-pid max-attempts)\n (let loop ([attempts 0])\n (cond\n [(>= attempts max-attempts) #f]\n [(child-finished-status expected-pid) => identity]\n [(= (read-ready-pid pid-path) expected-pid) #t]\n [else\n (sleep-ms 100)\n (loop (+ attempts 1))])))") ("id" . "unix-socket-listener-ready-probe") + ("imports" "(jerboa prelude)") + ("notes" + . + "Checking for the Unix socket pathname is insufficient because bind() creates the pathname before listen() accepts connections, which can produce transient ECONNREFUSED. A connect-probe proves that listen() is ready, but it also creates an accepted connection; avoid that for protocols with authentication, session state, or other connection side effects. For those servers, write a readiness marker or pidfile only after listen() succeeds, check the write result, and fail startup if the marker cannot be published. The parent should wait for that marker and verify it belongs to the expected child pid; it should also poll direct child status with waitpid(pid, WNOHANG), because kill(pid, 0) reports zombies as alive and can otherwise turn an early daemon exit into a long readiness timeout. A connect-probe is acceptable only for stateless or unauthenticated servers that immediately tolerate EOF and clean up the probe connection.") + ("tags" "mux" "unix-socket" "readiness" "pidfile" "waitpid" + "auth") + ("title" + . + "Use checked post-listen readiness markers for Unix socket servers")) + (("code" + . + "#!/usr/bin/env bash\nset -euo pipefail\nBINARY=${1:-./jsh}\nNAME=\"mux-auth-test-$$\"\nRUNTIME=\"${XDG_RUNTIME_DIR:+$XDG_RUNTIME_DIR/jsh}\"\n: \"${RUNTIME:=$HOME/.jsh}\"\ncleanup() {\n if [ -f \"$RUNTIME/$NAME.pid\" ]; then\n kill \"$(cat \"$RUNTIME/$NAME.pid\")\" 2>/dev/null || true\n fi\n rm -f \"$RUNTIME/$NAME.sock\" \"$RUNTIME/$NAME.pid\"\n}\ntrap cleanup EXIT\ncleanup\nprintf '%s' 'correctpass' | _JSH_MUX_PW_FD=0 \"$BINARY\" --server --name \"$NAME\" >/dev/null 2>&1 &\nfor _ in $(seq 1 100); do\n [ -S \"$RUNTIME/$NAME.sock\" ] && break\n sleep 0.1\ndone\nexpect -c \"\n set timeout 10\n spawn $BINARY --attach --name $NAME\n expect { \\\"Password:\\\" {} timeout { exit 1 } }\n send \\\"correctpass\\r\\\"\n expect { \\\"attached\\\" { exit 0 } timeout { exit 1 } }\n\"") ("id" . "jsh-mux-auth-fd-expect-test") ("imports") + ("notes" + . + "The server password is intentionally not accepted on argv or in an environment variable. Start auth servers by writing the password to an inherited pipe and setting _JSH_MUX_PW_FD to that read fd, commonly fd 0 in tests. Attach-side tests should drive the password prompt with expect. Do not use --server --password in new tests; it can silently start an unauthenticated server if the parser ignores the stale flag.") + ("tags" "jsh" "mux" "auth" "expect" "_JSH_MUX_PW_FD" "test") + ("title" . "Test jsh mux auth using inherited password fd")) + (("code" + . + "# Regression test pattern for a jsh mux printable fallback.\n# The mux client should intercept these lines before they reach the pane shell:\n# ~~c<Enter> -> new-window\n# ~~d<Enter> -> detach\n# ~~h<Enter> -> help\nMUX_PRINTABLE=\"test-printable-$$\"\nstart_auth_server \"$MUX_PRINTABLE\" \"$TEST_PASSWORD\"\nif wait_for_socket \"$MUX_PRINTABLE\" 5; then\n got=$(timeout 30 expect -c \"\n log_user 0\n set timeout 20\n spawn $BINARY --attach --name $MUX_PRINTABLE\n expect \\\"Password:\\\"\n send \\\"$TEST_PASSWORD\\r\\\"\n expect \\\"attached\\\"\n sleep 2\n send \\\"~~c\\r\\\"\n sleep 2\n send \\\"echo printable-mux-ok\\r\\\"\n expect \\\"printable-mux-ok\\\"\n send \\\"~~d\\r\\\"\n expect {\n \\\"detached\\\" { puts \\\"PRINTABLE-MUX-DETACH-OK\\\" }\n eof { puts \\\"PRINTABLE-MUX-DETACH-OK\\\" }\n }\n \" 2>&1)\nfi") ("id" . "jsh-mux-printable-command-fallback") ("imports") + ("notes" + . + "Use this when attach succeeds but the mux feels like a plain shell because Ctrl-b/Ctrl-a is consumed by tmux, readline, browser terminals, or another terminal layer. Implement the fallback in the client relay loop, not the server: the client must intercept the printable line before forwarding it to the pane shell. Keep the normal prefix-key path too, and test both.") + ("tags" "jsh" "mux" "terminal" "prefix-key" "expect" + "fallback") + ("title" + . + "Add printable mux commands when Ctrl-prefix keys are intercepted")) + (("code" + . + "(import (chezscheme)\n (jerboa prelude))\n\n(def captured\n (call-with-values\n open-bytevector-output-port\n (lambda (out get-output)\n (put-bytevector out (string->utf8 \"hello\"))\n (get-output))))\n\n(displayln (utf8->string captured))\n") ("id" . "bytevector-output-port-values") + ("imports" "(chezscheme)" "(jerboa prelude)") + ("notes" + . + "Chez/Jerboa open-bytevector-output-port returns two values: the output port and a getter procedure. There is no globally bound get-output-port-bytevector in this environment. Use call-with-values to capture the getter, especially in binary-port tests.") + ("tags" "bytevector" "output-port" "call-with-values" + "chezscheme" "binary-io" "testing") + ("title" + . + "Capture bytes from open-bytevector-output-port")) + (("code" + . + "#!chezscheme\n(library (example native)\n (export native-version)\n (import\n (except (rnrs) partition)\n (except (jerboa prelude) meta atom? ->)\n (std foreign))\n\n (define-foreign c-native-version\n \"native_version\" () -> uint32)\n\n (def (native-version)\n (c-native-version)))\n") ("id" . "std-foreign-exclude-prelude-arrow") + ("imports" + "(except (jerboa prelude) meta atom? ->)" + "(std foreign)") + ("notes" + . + "`(std foreign)` exports `->` as an auxiliary keyword for `define-foreign`, while `(jerboa prelude)` exports the threading macro `->`. If both are imported unqualified, whole-program import/jerbuild can report invalid syntax at the `define-foreign` form. Exclude `->` from the prelude in FFI wrapper modules that use `define-foreign`.") + ("tags" "std foreign" "define-foreign" "prelude" + "import conflict" "ffi" "arrow") + ("title" + . + "Use std foreign define-foreign with the prelude")) + (("code" + . + "# .jerbuild\n(entry \"bin/tool.ss\")\n(output \"tool\")\n(libdirs \"lib\")\n(ffi-symbols \"support/ffi-symbols.gen\")\n(rust-crates \"rust/Cargo.toml\")\n\n# rust/my-native/Cargo.toml\n[lib]\ncrate-type = [\"staticlib\", \"cdylib\"]\n\n# Makefile excerpt\nJEXEC := jerbuild exec --libdirs lib:$$(jerbuild --jerboa-home 2>/dev/null)/lib\nUNAME_S := $(shell uname -s)\nifeq ($(UNAME_S),Darwin)\nNATIVE_SHARED := $(CURDIR)/rust/target/release/libmy_native.dylib\nNATIVE_PRELOAD := DYLD_INSERT_LIBRARIES=\"$(NATIVE_SHARED)\"\nelse\nNATIVE_SHARED := $(CURDIR)/rust/target/release/libmy_native.so\nNATIVE_PRELOAD := LD_PRELOAD=\"$(NATIVE_SHARED)\"\nendif\n\ntest: binary\n\t$(NATIVE_PRELOAD) $(JEXEC) tests/native-test.ss\n") ("id" . "preload-local-cdylib-for-jerbuild-exec-ffi-tests") + ("imports" "Makefile" ".jerbuild") + ("notes" + . + "A standalone jerbuild binary can resolve local FFI functions through generated `Sforeign_symbol` registrations, but `jerbuild exec` runs tests in the Jerboa/Chez process and does not inherit those registrations. If a Scheme test imports a library with top-level `define-foreign`/`foreign-procedure`, unresolved symbols fail while visiting the library. Build the Rust helper as both `staticlib` and `cdylib`: the static archive is linked into the final binary, while the shared object is preloaded for `jerbuild exec` tests.") + ("tags" "jerbuild" "exec" "ffi" "cdylib" "Sforeign_symbol" + "tests") + ("title" + . + "Preload a local cdylib for jerbuild exec FFI tests")) + (("code" + . + "// rust/my-native/src/lib.rs\n#![no_std]\n\nuse core::panic::PanicInfo;\n\n#[panic_handler]\nfn panic(_info: &PanicInfo<'_>) -> ! {\n loop {}\n}\n\n#[no_mangle]\npub extern \"C\" fn my_native_version() -> u32 {\n 1\n}\n\n# rust/my-native/Cargo.toml\n[lib]\ncrate-type = [\"staticlib\", \"cdylib\"]\n\n# .jerbuild\n(rust-crates\n (\"@bundle/jerboa-native-rs/Cargo.toml\" features: \"wasm\" no-default-features: #t)\n \"rust/Cargo.toml\")\n") ("id" . "no-std-rust-staticlib-for-small-jerbuild-ffi") + ("imports" "rust/Cargo.toml" ".jerbuild") + ("notes" + . + "When a standalone jerbuild binary links the bundled `jerboa-native-rs` static archive plus another Rust `staticlib` that uses `std`, the final C link can fail with duplicate Rust runtime symbols such as `std::panicking::EMPTY_PANIC` or `_rust_eh_personality`. For tiny C ABI helpers that do not need allocation, threads, TLS, or other std services, make the helper `#![no_std]` and expose only simple `extern \"C\"` functions. Larger std-using helpers should be consolidated into one Rust runtime archive or linked through a deliberate platform-specific strategy.") + ("tags" "rust" "staticlib" "no_std" "jerbuild" "ffi" + "duplicate-symbols") + ("title" + . + "Use no_std for small Rust staticlib FFI helpers linked with jerbuild")) + (("code" + . + "(import (jerboa prelude))\n(import (jsh ffi))\n\n(def buf (make-bytevector 256))\n(def n (ffi-bv-read-nonblock 0 buf 0 256))\n(cond\n [(< n 0)\n ;; EOF or real read error: stop the relay / clean up.\n 'eof]\n [(> n 0)\n ;; Process bytes in buf[0,n).\n 'bytes]\n [else\n ;; 0 means EAGAIN/EWOULDBLOCK: keep polling.\n 'again])") ("id" . "ffi-bv-read-nonblock-eagain-zero") + ("imports" "(jsh ffi)") + ("notes" + . + "`ffi_bv_read_nonblock` returns 0 for EAGAIN/EWOULDBLOCK and -1 for EOF/error. Do not use `(<= n 0)` as an EOF test in terminal or PTY relay loops. That caused jsh mux attach to detach immediately before Ctrl-b commands could be processed.") + ("tags" "ffi" "nonblocking" "stdin" "mux" "eagain" "eof") + ("title" + . + "Treat ffi-bv-read-nonblock zero as EAGAIN, not EOF")) + (("code" + . + "(import (jerboa prelude))\n\n(def (now-seconds)\n (datetime->epoch (datetime-now)))\n\n(displayln (integer? (now-seconds)))") ("id" . "prelude-epoch-seconds-datetime") + ("imports" "(jerboa prelude)") + ("notes" + . + "In project modules that import the Jerboa prelude, use (datetime->epoch (datetime-now)) for epoch seconds. Chez bindings such as current-time/time-second may work only with a direct Chez import and can be unbound in Jerboa modules.") + ("tags" "datetime" "epoch" "prelude" "current-time" + "time-second" "watchdog") + ("title" + . + "Get epoch seconds from the Jerboa prelude datetime API")) + (("code" + . + "# Regression test pattern for default Ctrl-b prefix handling.\n# Send Ctrl-b and the command byte as separate terminal writes; this matches\n# human keypresses and catches bugs hidden by a single send of \"\\002d\".\nMUX_PREFIX_DEFAULT=\"test-prefix-default-$$\"\nstart_auth_server \"$MUX_PREFIX_DEFAULT\" \"$TEST_PASSWORD\"\nif wait_for_socket \"$MUX_PREFIX_DEFAULT\" 5; then\n got=$(timeout 30 expect -c \"\n log_user 0\n set timeout 20\n spawn $BINARY --attach --name $MUX_PREFIX_DEFAULT\n expect \\\"Password:\\\"\n send \\\"$TEST_PASSWORD\\r\\\"\n expect \\\"attached\\\"\n sleep 1\n send -- \\\"\\002\\\"\n sleep 1\n send \\\"c\\\"\n sleep 2\n send \\\"echo separated-cb-ok\\r\\\"\n expect \\\"separated-cb-ok\\\"\n send -- \\\"\\002\\\"\n sleep 1\n send \\\"d\\\"\n expect {\n \\\"detached\\\" { puts \\\"DEFAULT-PREFIX-DETACH-OK\\\" }\n eof { puts \\\"DEFAULT-PREFIX-DETACH-OK\\\" }\n }\n \" 2>&1)\nfi\n") ("id" . "jsh-mux-separated-default-prefix-test") + ("imports") + ("notes" + . + "Use this in addition to tight-byte tests such as `send -- \"\\002d\"`. A mux client can pass the tight send while still failing real interactive typing if prefix-active state is not preserved across read batches or if configurable-prefix plumbing loses the default Ctrl-b byte.") + ("tags" "jsh" "mux" "prefix-key" "ctrl-b" "expect" + "regression") + ("title" + . + "Test default jsh mux prefix as separated keypresses")) + (("code" + . + ";; Pattern: clean dead client records before accepting new connections, and\n;; never send protocol messages from generic dead-client cleanup.\n;;\n;; Event loop ordering:\n(check-signals! state)\n(cleanup-dead-clients! state) ; stale fds closed before accept can reuse them\n(accept-new-clients! state)\n(handle-client-input! state)\n\n;; Explicit/live detach paths may notify the client before marking it dead.\n(guard (e [#t (void)])\n (send-to-client-safe client MSG-STATUS (string->utf8 \"detached\")))\n(mux-client-attached?-set! client #f)\n\n;; Generic cleanup only closes/removes records. Do not write MSG-STATUS here;\n;; the saved transport id may be a stale fd number already reused by a new attach.") ("id" . "jsh-mux-cleanup-fd-reuse") + ("imports" "(jsh mux-server)" "(jsh mux-proto)") + ("notes" + . + "A stale mux-client record can hold a numeric fd after the peer detached or disconnected. If the server accepts a new client before removing that dead record, the OS can reuse the same fd number; generic cleanup can then send stale MSG-STATUS \"detached\" or close the new connection. Keep cleanup before accept, and send user-facing detach status only from live/explicit detach paths such as MSG_DETACH or session-gone handling.") + ("tags" "jsh" "mux" "cleanup" "fd-reuse" "detach" + "server-loop") + ("title" + . + "Mux server: avoid stale fd reuse when cleaning dead clients")) + (("code" + . + "Generic workflow for constrained model runs:\n\n1. Ask jmcp for a cookbook task bundle for the current task. Use include_code=true when jcode itself cannot call MCP during the verified run.\n\n printf '%s\\n%s\\n' \\\n '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}' \\\n '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"jerboa_cookbook_task_bundle\",\"arguments\":{\"task\":\"<task>\",\"file_path\":\"<target>.ss\",\"max_recipes\":4,\"include_code\":true}}}' \\\n | ~/.local/bin/jmcp > /tmp/jcode-task-bundle.json\n\n2. Extract the returned text content into a plain guide file.\n\n3. Run jcode verified with the guide file:\n\n jcode verified \"<task>\" --cwd \"$PWD\" --write-scope \"<target>.ss\" --guidance-file /tmp/jcode-task-guide.txt --verify \"<verify command>\"\n\n4. Keep task examples in cookbooks; keep jcode generic. If the run discovers a new working Jerboa/API pattern, save it back with jerboa_howto_add.") ("id" . "jcode-cookbook-task-bundle-guidance-file") + ("imports") + ("notes" + . + "This pattern prevents hardcoding benchmark-specific instructions in jcode. jcode only receives caller-supplied context through --guidance-file; jmcp selects examples from cookbook data using the task query. Use include_code=false when the model can call MCP itself, and include_code=true when running a closed verified workflow with no MCP tools.") + ("tags" "jcode" "verified" "cookbook" "task-bundle" + "guidance-file" "mcp") + ("title" + . + "Use cookbook task bundles as jcode verified guidance")) + (("code" + . + "(import (jerboa prelude))\n\n(def (pane-rows-for-client rows)\n (max 1 (- (if (<= rows 0) 24 rows) 1)))\n\n(def (render-status-overlay status cols rows)\n (let ([cols (if (<= cols 0) 80 cols)]\n [rows (if (<= rows 0) 24 rows)])\n (string-append\n \"\\x1B;7\"\n \"\\x1B;[\" (number->string rows) \";1H\"\n \"\\x1B;[7m\" status\n (make-string (max 0 (- cols (string-length status))) #\\space)\n \"\\x1B;[0m\"\n \"\\x1B;8\")))\n\n(def (bytevector-contains-subseq? bv needle)\n (let ([n (bytevector-length bv)]\n [m (bytevector-length needle)])\n (and (> m 0)\n (let loop ([i 0])\n (cond\n [(> (+ i m) n) #f]\n [(let inner ([j 0])\n (cond\n [(= j m) #t]\n [(= (bytevector-u8-ref bv (+ i j))\n (bytevector-u8-ref needle j))\n (inner (+ j 1))]\n [else #f]))\n #t]\n [else (loop (+ i 1))])))))\n\n(def status-erasing-sequences\n (map string->utf8\n '(\"\\x1B;[J\" \"\\x1B;[0J\" \"\\x1B;[1J\" \"\\x1B;[2J\" \"\\x1B;[3J\"\n \"\\x1B;c\" \"\\x1B;[?1049h\" \"\\x1B;[?1049l\")))\n\n(def (needs-status-overlay? output-bv)\n (any (lambda (needle) (bytevector-contains-subseq? output-bv needle))\n status-erasing-sequences))\n\n(display (render-status-overlay \"[0:0*]\" 80 24))") ("id" . "mux-persistent-status-bar-overlay") + ("imports" "(jerboa prelude)") + ("notes" + . + "A mux status bar is not persistent if the child PTY is sized to the full client height; shells and full-screen programs can draw over the last row. Size the pane PTY and virtual terminal to client rows minus one, and draw the status bar on the physical last row with DECSC/DECRC so cursor position is restored. Do not redraw the overlay after every PTY output chunk because it can interfere with interactive timing; instead redraw after resize and after PTY output containing clear-screen, reset, or alternate-screen escape sequences.") + ("tags" "mux" "pty" "status-bar" "terminal" + "escape-sequences" "overlay") + ("title" + . + "Keep a mux status bar persistent below a PTY pane"))) --- a/data/features.sexp +++ b/data/features.sexp @@ -2351,7 +2351,7 @@ "~300-800 tokens per binding-heavy task by avoiding manual source inspection for exported generated bindings.") ("example_scenario" . - "While hardening a Jerboa Qt Tetris verifier, module_exports confirmed `qt-painter-fill-rect!` exists in `(jerboa-qt qt)`, but function_signature returned `Could not find qt-painter-fill-rect!`, requiring manual source/runtime confirmation.") + "While hardening a generated FFI binding verifier, module_exports confirmed an exported drawing helper exists, but function_signature returned `Could not find` for the same binding, requiring manual source/runtime confirmation.") ("id" . "function-signature-generated-exports") ("impact" . "medium") ("tags" @@ -2387,13 +2387,13 @@ ("votes" . 0)) (("description" . - "When a model calls an unavailable workflow tool during `jcode verified`, return a structured reminder of the allowed tool names and keep the run recoverable when possible instead of letting repeated unknown-tool calls consume the attempt until the no-progress guard aborts. This came up in a Jerboa Qt Tetris ladder: after verifier failures, the model tried `run` even though the prompt allowed only `read`, `edit`, `verify`, and `done`. A runner-level recovery hint would make prompt-only discipline less brittle.") + "When a model calls an unavailable workflow tool during `jcode verified`, return a structured reminder of the allowed tool names and keep the run recoverable when possible instead of letting repeated unknown-tool calls consume the attempt until the no-progress guard aborts. This came up during a generated-module run: after verifier failures, the model tried `run` even though the prompt allowed only `read`, `edit`, `verify`, and `done`. A runner-level recovery hint would make prompt-only discipline less brittle.") ("estimated_token_reduction" . "~300-800 tokens per failed verifier loop and can save one full live retry.") ("example_scenario" . - "A generated `src/huihui/tetris-game.ss` had an extra close parenthesis. After several `verify` failures, the model attempted `run` to inspect the file, causing unknown-tool errors and then a no-progress abort. A tool-surface reminder could steer it back to `read`/`edit`/`verify`.") + "A generated `src/generated/module.ss` had an extra close parenthesis. After several `verify` failures, the model attempted `run` to inspect the file, causing unknown-tool errors and then a no-progress abort. A tool-surface reminder could steer it back to `read`/`edit`/`verify`.") ("id" . "jcode-verified-unknown-tool-reminder") ("impact" . "medium") ("tags" "jcode" "verified" "tools" "generated-module" @@ -2426,13 +2426,13 @@ ("votes" . 0)) (("description" . - "When balance reports an unexpected close or unclosed delimiter, include the next concrete repair action for local-model workflows: read a small line span, then use line_edit for a single bad line or replace_range for an enclosing unbalanced function. The Huihui Tetris runs repeatedly identified the correct line with balance but then kept re-reading sed ranges or attempting brittle old_str replacements instead of choosing the repair tool.") + "When balance reports an unexpected close or unclosed delimiter, include the next concrete repair action for local-model workflows: read a small line span, then use line_edit for a single bad line or replace_range for an enclosing unbalanced function. Constrained model runs repeatedly identify the correct line with balance but then keep re-reading source ranges or attempting brittle old_str replacements instead of choosing the repair tool.") ("estimated_token_reduction" . "~300-1000 tokens per syntax repair loop; can eliminate repeated sed/read calls.") ("example_scenario" . - "A generated tetris.ss has `Unexpected close ) at line 157`. The model calls balance and reads the same line range many times. A balance hint saying `Use line_edit(path,line=157,content=...) or replace_range for the enclosing function` would likely save the loop.") + "A generated main.ss has `Unexpected close ) at line 57`. The model calls balance and reads the same line range many times. A balance hint saying `Use line_edit(path,line=57,content=...) or replace_range for the enclosing function` would likely save the loop.") ("id" . "balance-repair-action-hints") ("impact" . "medium") ("tags" "balance" "local-model" "repair" "line-edit" "replace-range") @@ -2462,25 +2462,6 @@ ("votes" . 0)) (("description" . - "Add a tool that creates and verifies a staged GUI/game curriculum for Jerboa Qt: static paint widget, pure board logic, line clearing, rotation, keyboard movement, timer drop, and full game. The tool should emit SFT examples, DPO rejected variants for common Qt/API hallucinations, and eval manifests with screenshot or pixel checks where applicable.") - ("estimated_token_reduction" - . - "~3000 tokens per training/eval cycle by replacing ad hoc prompt writing, manual screenshot checks, and repeated ladder reconstruction.") - ("example_scenario" - . - "Instead of prompting a model to build Tetris in one pass, the harness generates verified ladder rungs and checks each one. Failures identify whether the model is missing pure game logic, Qt painting, keyboard handling, timers, or integration.") - ("id" . "jerboa-qt-tetris-ladder-dataset-generator") - ("impact" . "high") - ("tags" "jerboa-qt" "tetris" "training-data" "eval" "gui") - ("title" - . - "Generate staged Jerboa Qt game ladder datasets and evals") - ("use_case" - . - "Training and evaluating local models on GUI/game construction incrementally before attempting a full Tetris clone.") - ("votes" . 0)) - (("description" - . "Add a Jerboa MCP tool that accepts a list of project paths or a glob such as ~/mine/jerboa* and runs the standard checks per repository: git status, balance/syntax checks for changed Scheme files, security scan on changed files, known build/test targets, and a compact final matrix. It should preserve dirty-tree boundaries and clearly separate pre-existing local changes from files touched during the current session.") ("estimated_token_reduction" . @@ -2554,4 +2535,192 @@ ("use_case" . "Use jerboa_verify on build scripts and helper transpilers, then fix syntax or macro issues without falling back to Makefile execution or manual bisection.") + ("votes" . 0)) + (("description" + . + "Add a Jerboa MCP tool that takes a source file plus a Chez/Jerboa error like \"invalid context for definition ... at line N\" and reports the likely enclosing function/form span that swallowed the definition. It should return the previous top-level define line, suspected end line, and a suggested replace_range call shape.") + ("estimated_token_reduction" + . + "~1000-3000 tokens per failure by avoiding broad read/balance loops.") + ("example_scenario" + . + "A generated main.ss is delimiter-balanced, but verify fails at line 104 with invalid context for definition because process-items lacks a close and swallows later top-level helpers. The tool would point to process-items and suggest replacing the enclosing span.") + ("id" . "invalid-context-definition-diagnoser") + ("impact" . "high") + ("tags" "diagnostics" "syntax" "top-level" "replace-range") + ("title" . "Diagnose invalid-context top-level definitions") + ("use_case" + . + "Constrained models often produce delimiter-balanced Scheme where a function body accidentally includes later top-level definitions. Balance says OK, but compile reports invalid context for definition. The model needs a structural diagnosis, not more raw source reads.") + ("votes" . 0)) + (("description" + . + "Add or extend a compact MCP tool that combines cookbook lookup, relevant function signatures, common gotchas, and an ordered repair workflow for a task/error in one response. Output should be short and prescriptive for small or noisy local models.") + ("estimated_token_reduction" + . + "~1500-5000 tokens per task by replacing several MCP/tool reads and prompt retries.") + ("example_scenario" + . + "For a Jerboa command-line utility, the tool would return exact imports, relevant call shapes, self-test shape, forbidden forms, and the recommended verify/balance/replace_range repair ladder in under 1000 tokens.") + ("id" . "compact-jerboa-workflow-advisor-for-local-models") + ("impact" . "high") + ("tags" "workflow" "compact" "local-model" "cookbook") + ("title" + . + "Compact Jerboa workflow advisor for constrained local models") + ("use_case" + . + "Small models burn turns by separately discovering howto recipes, API names, arities, and repair steps. A single task-specific advisor can give a minimal tool path and code constraints before generation.") + ("votes" . 0)) + (("description" + . + "Add an MCP/source tool that returns top-level form spans and nested form spans for a .ss file, optionally around a line number. It should produce start/end lines suitable for replace_range and identify whether the form is syntactically balanced, semantically top-level, or likely swallowing following forms.") + ("estimated_token_reduction" + . + "~800-2500 tokens per repair by replacing multiple read/balance/old_str attempts.") + ("example_scenario" + . + "Given main.ss and line 104, return that process-items starts at 88 and appears to continue through later top-level defines, with the next expected top-level define at 136. The model can replace that range instead of repeatedly reading or using brittle old_str.") + ("id" . "source-form-span-and-range-repair-tool") + ("impact" . "high") + ("tags" "source" "form-span" "repair" "replace-range") + ("title" . "Report source form spans for repair tools") + ("use_case" + . + "Models struggle with exact old_str edits and paren counting. Line spans let them use replace_range safely after verifier diagnostics.") + ("votes" . 0)) + (("description" + . + "Add a development/test utility that scans MCP or workflow tool implementations for branches that can return Scheme void where a string/result is expected. It should flag cond clauses using when/unless in value position and optionally wrap tool results to reject void with a diagnostic.") + ("estimated_token_reduction" + . + "~500-2000 tokens per debugging session by preventing opaque null tool outputs.") + ("example_scenario" + . + "note-inspection-after-failed-verify! used when in value position. Before the inspection limit it returned void, so do-read returned null instead of file contents. A guard would catch this during tests.") + ("id" . "tool-result-void-null-guard") ("impact" . "medium") + ("tags" "tooling" "void" "null" "tests") + ("title" . "Guard against void/null tool results") + ("use_case" + . + "A workflow tool returning void serialized as null caused local models to see read/cat results as null after failed verify, leading to loops and bad recovery.") + ("votes" . 0)) + (("description" + . + "When jerboa_verify fails with errors like a module yielding a different compilation instance from the one required by a compiled dependency, it should recognize the stale-artifact pattern and suggest or optionally run a clean verification path. The current behavior reports the raw Chez error, forcing the user to infer that compiled .so/.wpo artifacts need cleaning or that the build should be used as the authoritative check.") + ("estimated_token_reduction" + . + "~300-700 tokens per occurrence by avoiding manual diagnosis and fallback build explanation.") + ("example_scenario" + . + "Running jerboa_verify on jsh mux-server after edits failed with: loading std/crypto/native-rust yielded a different compilation instance from that required by compiled jsh mux-auth. make compile succeeded after stale artifacts were cleaned by the project build.") + ("id" . "verify-stale-instance-auto-diagnose") + ("impact" . "medium") + ("tags" + "verify" + "stale-artifacts" + "compiled-instance" + "diagnostics") + ("title" + . + "Have jerboa_verify diagnose stale compiled-instance conflicts") + ("use_case" + . + "Verifying a Jerboa source file after editing a dependency-heavy project where old compiled artifacts remain in libdirs.") + ("votes" . 0)) + (("description" + . + "function_signature should be able to inspect symbols that module_exports lists from an imported module, or return a diagnostic explaining why a symbol is exported but no signature is available. In this session module_exports showed ffi-waitpid-pid, ffi-waitpid-status, WIFEXITED, and WIFSIGNALED from (jsh ffi), while function_signature reported it could not find those symbols.") + ("estimated_token_reduction" + . + "~200-500 tokens per API check by eliminating fallback source reads and manual signature inference.") + ("example_scenario" + . + "Before adding waitpid polling to jsh.ss, module_exports confirmed the waitpid helpers existed, but function_signature could not find the same exported names with imports=[\"(jsh ffi)\"].") + ("id" . "function-signature-resolve-module-exports") + ("impact" . "medium") + ("tags" + "function-signature" + "module-exports" + "ffi" + "introspection") + ("title" + . + "Make function_signature resolve exported module symbols") + ("use_case" + . + "Checking arity and usage for project-local FFI helpers before editing Jerboa code.") + ("votes" . 0)) + (("description" + . + "When jerboa_module_exports or jerboa_function_signature loads a project module and hits Chez/Jerboa errors like \"yielded a different compilation instance\" or \"requires a different compilation instance\", the tool should detect the stale-artifact condition and offer an automatic retry mode that clears relevant .so/.wpo artifacts or re-runs the same logic in an isolated process. The current failure forces manual source inspection or separate stale-artifact cleanup even for simple arity/export checks.") + ("estimated_token_reduction" + . + "~600-1200 tokens per failed introspection by avoiding source fallback and repeated tool attempts.") + ("example_scenario" + . + "While fixing jsh ,server behavior, jerboa_module_exports for (jsh mux-server) failed with a different compilation-instance error, and jerboa_function_signature could not find mux-server-check-running or mux-client-attach. Source inspection was needed even though the functions were imported and present.") + ("id" . "mcp-introspection-stale-artifact-recovery") + ("impact" . "medium") + ("tags" "mcp" "introspection" "stale-artifacts" + "module-exports" "function-signature") + ("title" + . + "Recover MCP introspection from stale compilation-instance mismatches") + ("use_case" + . + "Checking local project module exports and arities before editing Jerboa code, especially in repositories that rebuild .ss/.sls files frequently.") + ("votes" . 0)) + (("description" + . + "jerboa_verify reported no issues for modules that later failed during jerbuild WPO or test execution with unbound identifiers such as time-second and rename-file. A mode that expands/checks with the same project library closure and import/export resolution used by WPO would catch these before a full build.") + ("estimated_token_reduction" + . + "~1000 tokens per WPO import/debug failure") + ("example_scenario" + . + "A queue module used (time-second (current-time)) and then rename-file. jerboa_verify passed, but make test failed in WPO/runtime because those identifiers were not available from the module imports.") + ("id" . "verify-wpo-export-resolution") ("impact" . "high") + ("tags" "verify" "wpo" "unbound-identifier" "imports" + "jerbuild") + ("title" + . + "Have jerboa_verify catch WPO export-resolution failures") + ("use_case" + . + "After editing a Jerboa library module, run one verifier that catches symbols accepted by local expansion but missing from the actual WPO/static build closure.") + ("votes" . 0)) + (("description" + . + "Single-file `jerboa_verify` can pass for a library even when importing it through a project entrypoint fails due to macro/import interactions, such as `(std foreign)` using `->` while `(jerboa prelude)` also exports `->`. A verify mode that visits a target module through the project import closure or a named entry file would catch these earlier.") + ("estimated_token_reduction" + . + "~800 tokens per incident by avoiding one failed build and follow-up diagnosis") + ("example_scenario" + . + "`lib/jerboa-smtp/native.ss` verified cleanly alone, but `build.ss` verification caught invalid syntax in `define-foreign` because of an imported `->` conflict. A closure verify would point directly at the import conflict.") + ("id" . "verify-import-closure-mode") ("impact" . "medium") + ("tags" "verify" "imports" "macros" "jerbuild" "ffi") + ("title" + . + "Add a verify mode for project import-closure expansion") + ("use_case" + . + "Before committing a new Jerboa module that is imported by a binary entrypoint, verify the same import path the build will use without running a full jerbuild binary build.") + ("votes" . 0)) + (("description" + . + "Provide an MCP/tooling helper that spawns a jsh binary under a pseudo-terminal, sends raw control bytes such as C-b c/C-b d, waits for attach/detach/status/output markers with consumed-buffer semantics, and optionally runs the same flow over SSH. This would avoid ad hoc Python PTY harnesses and catch Linux environments where expect(1) is not installed.") + ("estimated_token_reduction" + . + "~800-1500 tokens per mux debugging session; eliminates repeated ad hoc PTY harness code.") + ("example_scenario" + . + "A Linux host lacked expect, so the shell test suite skipped prefix regression tests. A hand-written Python PTY harness was needed to discover that SHELL=/proc/self/fd/3 made mux panes exit 127 and made C-b appear broken.") + ("id" . "mux-pty-regression-helper") ("impact" . "medium") + ("tags" "mux" "pty" "testing" "expect" "ssh") + ("title" . "Add a PTY mux regression helper") + ("use_case" + . + "Debugging jsh mux attach, prefix, detach, and replace flows across macOS and Linux without relying on expect being installed.") ("votes" . 0))) --- a/data/security-rules.sexp +++ b/data/security-rules.sexp @@ -977,4 +977,11 @@ . "open-process-ports\\s*\\(\\s*(string-append|format|string-join)") ("scope" . "scheme") - ("severity" . "high"))) + ("severity" . "high")) + (("id" . "unchecked-pidfile-readiness-write") + ("message" + . + "Standalone ffi-write-pidfile calls ignore whether the readiness marker was actually published. In daemon startup code this can leave a listener running without a pidfile/readiness marker, causing parent wait loops, attach/list commands, or cleanup paths to misbehave. Bind the return code, check for negative errno, clean up opened listeners/sockets, and fail startup if the pidfile cannot be written. See recipe unix-socket-listener-ready-probe.") + ("pattern" . "^\\s*\\(ffi-write-pidfile\\s+[^)]*\\)\\s*$") + ("scope" . "scheme") + ("severity" . "medium"))) --- a/mcp/build-jmcp.sh +++ b/mcp/build-jmcp.sh @@ -186,6 +186,8 @@ cat > "$builder" <<'SCHEME' (std misc ports) (std misc process) (std os path) + (std os shell) + (std security taint) (std misc string))) (define (import-form? form) --- a/mcp/server.ss +++ b/mcp/server.ss @@ -5,6 +5,8 @@ (std misc ports) (std misc process) (std os path) + (std os shell) + (std security taint) (std misc string)) (def server-name "jerboa-mcp") @@ -27,6 +29,7 @@ "jerboa_function_signature" "jerboa_howto" "jerboa_howto_get" + "jerboa_cookbook_task_bundle" "jerboa_security_scan" "jerboa_security_profile" "jerboa_run_tests" @@ -125,9 +128,11 @@ (def (scheme-path) (def explicit (getenv "JERBOA_MCP_SCHEME_PATH")) (def repo-scheme (repo-local-scheme-path)) + (def home-scheme (path-join (jerboa-home #f) ".chez" "bin" "scheme")) (cond [(and explicit (file-exists? explicit)) explicit] [repo-scheme repo-scheme] + [(file-exists? home-scheme) home-scheme] [(file-exists? (path-join (getenv "HOME") "mine" "ChezScheme" "tarm64osx" "bin" "tarm64osx" "scheme")) (path-join (getenv "HOME") "mine" "ChezScheme" "tarm64osx" "bin" "tarm64osx" "scheme")] [(file-exists? (path-join (getenv "HOME") ".local" "bin" "scheme")) @@ -138,6 +143,43 @@ (let ([candidate (path-join (jerboa-home home) ".chez" "bin" "scheme")]) (if (file-exists? candidate) candidate (scheme-path)))) +(def (mcp-extract-keyword args key default) + (let loop ([rest args]) + (cond + [(null? rest) default] + [(null? (cdr rest)) default] + [(eq? (car rest) key) (cadr rest)] + [else (loop (cddr rest))]))) + +(def (mcp-run-process args . rest) + ;; The multicall jmcp embedding can lose output from std/misc/process' + ;; thread-drained open-process-ports path. Capture through shell redirection + ;; instead so MCP verification tools see child stdout reliably. + (let* ([dir (mcp-extract-keyword rest 'directory: #f)] + [out-file (tmp-artifact-base "jerboa-mcp-stdout")] + [err-file (tmp-artifact-base "jerboa-mcp-stderr")] + [cmd (if (string? args) + args + (string-join (map shell-quote args) " "))] + [full-cmd (string-append + (if dir (string-append "cd " (shell-quote dir) " && ") "") + cmd + " > " (shell-quote out-file) + " 2> " (shell-quote err-file))] + [rc (safe-system full-cmd)] + [stdout (if (file-exists? out-file) + (guard (e [else ""]) (read-file-string out-file)) + "")] + [stderr (if (file-exists? err-file) + (guard (e [else ""]) (read-file-string err-file)) + "")]) + (when (file-exists? out-file) (safe-delete-file out-file)) + (when (file-exists? err-file) (safe-delete-file err-file)) + (cond + [(> (string-length stdout) 0) stdout] + [(not (= rc 0)) stderr] + [else stdout]))) + (def (libdirs home) (path-join home "lib")) @@ -566,7 +608,7 @@ (begin (write-file-string tmp code) (guard (e [else (string-append error-marker "\n" (error-message e))]) - (run-process cmd 'timeout-ms: timeout-ms)))]) + (mcp-run-process cmd 'timeout-ms: timeout-ms)))]) (when (file-exists? tmp) (delete-file tmp)) (if (script-output-has-marker? output) output @@ -718,7 +760,7 @@ (def (chez-version-text) (string-trim (guard (e [else "Chez Scheme unknown"]) - (run-process (list (scheme-path) "--version"))))) + (mcp-run-process (list (scheme-path) "--version"))))) (def (jerboa-runtime-version-text) (string-append "Jerboa MCP " server-version " / " (chez-version-text))) @@ -916,6 +958,7 @@ '(("howto_add" "add howto recipe cookbook save discovery create writer") ("howto" "search find recipe cookbook examples pattern") ("howto_get" "get fetch recipe cookbook by id") + ("cookbook_task_bundle" "cookbook task bundle recipe pack examples context workflow") ("suggest_feature" "add suggest feature proposal backlog create request") ("list_features" "list features feature backlog suggestions remaining votes") ("vote_feature" "vote feature upvote prioritize backlog") @@ -1529,7 +1572,7 @@ #t)])))) (def (tool-version args) - (def out (guard (e [else (error-message e)]) (run-process (list (scheme-path) "--version")))) + (def out (guard (e [else (error-message e)]) (mcp-run-process (list (scheme-path) "--version")))) (text-result (string-append "Chez Scheme: " (string-trim out) "\nJerboa home: " (jerboa-home (hash-get* args "jerboa_home" #f)) "\nServer: " server-name " " server-version))) @@ -1704,6 +1747,74 @@ (text-result (recipe-text found #f)) (text-result (string-append "Recipe not found: " id) #t))))) +(def (cookbook-task-text args) + (string-trim + (hash-get* args "task" + (hash-get* args "query" + (hash-get* args "description" ""))))) + +(def (recipe-summary-line recipe) + (string-append + "- " (recipe-field recipe "id") " - " (recipe-field recipe "title") + "\n tags: " (string-join (hash-get* recipe "tags" '()) ", ") + "\n imports: " (string-join (hash-get* recipe "imports" '()) ", ") + "\n fetch: jerboa_howto_get {\"id\":\"" (recipe-field recipe "id") "\"}")) + +(def (recipe-bundle-entry recipe include-code?) + (if include-code? + (recipe-text recipe #f) + (recipe-summary-line recipe))) + +(def (cookbook-bundle-workflow file project) + (string-append + "Generic workflow:\n" + "1. Read the target files and project layout before editing.\n" + "2. Use the recipe imports, API names, and code shape as verified examples; do not copy unrelated logic blindly.\n" + "3. Confirm unfamiliar APIs with jerboa_module_exports or jerboa_function_signature.\n" + "4. Write the smallest complete artifact that should pass the verifier.\n" + "5. Run jerboa_verify on the changed .ss file" + (if file (string-append " (" file ")") "") + ", then the smallest runnable command or focused test" + (if project (string-append " in " project) "") + ".\n" + "6. If verification fails, use jerboa_explain_error or jerboa_error_fix_lookup before the next edit.\n" + "7. Save a non-trivial new working pattern with jerboa_howto_add.")) + +(def (tool-cookbook-task-bundle args) + (let* ([task (cookbook-task-text args)] + [path (hash-get* args "cookbook_path" (data-path "cookbooks.sexp"))] + [project (hash-get* args "project_path" #f)] + [file (hash-get* args "file_path" #f)] + [limit (positive-integer-arg args "max_recipes" 5)] + [include-code? (hash-get* args "include_code" #f)] + [recipes (load-cookbook path)]) + (if (= (string-length task) 0) + (text-result "task, query, or description is required." #t) + (let* ([matches (bounded-take + (rank-recipes + (filter (lambda (r) (recipe-matches? r task)) recipes) + task) + limit)] + [entries (map (lambda (r) (recipe-bundle-entry r include-code?)) matches)]) + (if (null? matches) + (text-result + (string-append + "Cookbook task bundle\n" + "Task: " task "\n\n" + "No matching cookbook recipes were found. Use jerboa_task_workflow_advisor, jerboa_stdlib_search, jerboa_module_exports, and jerboa_function_signature to derive the implementation, then save the discovered pattern with jerboa_howto_add.")) + (text-result + (string-append + "Cookbook task bundle\n" + "Task: " task "\n" + (maybe-line "Project: " project) + (maybe-line "File: " file) + "Source: " path "\n" + "Recipes selected: " (number->string (length matches)) "\n\n" + "Recipe context:\n" + (string-join entries "\n\n") + "\n\n" + (cookbook-bundle-workflow file project)))))))) + (def (line-comment-index line) (string-index line #\;)) @@ -2065,7 +2176,7 @@ (if (and base (> (string-length base) 0)) (guard (e [else ""]) (let ([root (or project (repo-root))]) - (run-process + (mcp-run-process (append (list "git" "-C" root "diff" "--unified=0" base "--") (if file (list file) '()))))) ""))) @@ -2179,8 +2290,8 @@ "--script" f)) (def out (guard (e [else (error-message e)]) (if project - (run-process cmd 'directory: project) - (run-process cmd)))) + (mcp-run-process cmd 'directory: project) + (mcp-run-process cmd)))) (if (or (string-contains out "Exception") (string-contains out "ERROR")) (string-append "FAIL " f "\n" out) (string-append "PASS " f))) @@ -2334,7 +2445,7 @@ (def (run-phase-command project name cmd) (guard (e [else (phase-record name "FAIL" (error-message e))]) - (phase-record name "PASS" (run-process cmd 'directory: project)))) + (phase-record name "PASS" (mcp-run-process cmd 'directory: project)))) (def (format-phase-record rec) (string-append (hash-ref rec "status") " " @@ -2433,7 +2544,7 @@ (text-result (string-append "No Makefile found in " project) #t)] [else (let* ([cmd (if (> (string-length target) 0) (list "make" target) (list "make"))] - [out (guard (e [else (error-message e)]) (run-process cmd 'directory: project))]) + [out (guard (e [else (error-message e)]) (mcp-run-process cmd 'directory: project))]) (text-result (string-append "make " (if (> (string-length target) 0) target "(default)") " output:\n\n" out)))])) (def (tool-security-profile args) @@ -4745,7 +4856,7 @@ (string-append "Baseline for constrained local models:\n" "1. Call jerboa_task_workflow_advisor with the user task.\n" - "2. Call jerboa_howto before writing Jerboa code.\n" + "2. Call jerboa_cookbook_task_bundle or jerboa_howto before writing Jerboa code.\n" "3. Confirm unknown APIs with jerboa_module_exports or jerboa_function_signature.\n" "4. Write the smallest complete .ss file or patch.\n" "5. Call jerboa_verify on changed .ss code, then jerboa_security_scan for file/process/FFI code.\n" @@ -4819,11 +4930,12 @@ "4. jerboa_make for the repo target only after focused tests pass.")] [else (list - "1. jerboa_howto {\"query\":\"<main API or task>\", \"compact\":true}" - "2. jerboa_module_exports for unfamiliar modules; jerboa_function_signature for unfamiliar calls." - (string-append "3. jerboa_verify {\"file_path\":\"" (if file file "changed-file.ss") "\"}") - "4. jerboa_security_scan if the change touches file I/O, process execution, network, FFI, or shell commands." - "5. Save non-trivial discoveries with jerboa_howto_add.")])) + "1. jerboa_cookbook_task_bundle {\"task\":\"<user task>\", \"max_recipes\":5}" + "2. jerboa_howto {\"query\":\"<main API or task>\", \"compact\":true} if the bundle misses the key API." + "3. jerboa_module_exports for unfamiliar modules; jerboa_function_signature for unfamiliar calls." + (string-append "4. jerboa_verify {\"file_path\":\"" (if file file "changed-file.ss") "\"}") + "5. jerboa_security_scan if the change touches file I/O, process execution, network, FFI, or shell commands." + "6. Save non-trivial discoveries with jerboa_howto_add.")])) (def (workflow-gotchas kind) (append @@ -4861,6 +4973,7 @@ (def manifest-core-tool-names '("jerboa_task_workflow_advisor" "jerboa_compact_tool_manifest" + "jerboa_cookbook_task_bundle" "jerboa_howto" "jerboa_howto_get" "jerboa_module_exports" @@ -4886,6 +4999,7 @@ (cond [(string=? name "jerboa_task_workflow_advisor") "first call for a task-specific plan"] [(string=? name "jerboa_compact_tool_manifest") "small-context catalog and call order"] + [(string=? name "jerboa_cookbook_task_bundle") "package relevant cookbook recipes for any task"] [(string=? name "jerboa_howto") "verified recipes before coding"] [(string=? name "jerboa_howto_get") "fetch full recipe after search"] [(string=? name "jerboa_module_exports") "confirm module contents"] @@ -5038,7 +5152,7 @@ [out (begin (write-file-string tmp code) (guard (e [else (string-append "ERROR: " (condition->text e))]) - (run-process cmd)))]) + (mcp-run-process cmd)))]) (when (file-exists? tmp) (delete-file tmp)) out)) @@ -6004,7 +6118,7 @@ (apply append (map scan-fork-in-fiber-file files)))))) (def (command-output cmd) - (guard (e [else #f]) (run-process cmd))) + (guard (e [else #f]) (mcp-run-process cmd))) (def (command-exists? name) (let ([out (command-output (list "sh" "-c" (string-append "command -v " name)))]) @@ -6571,7 +6685,7 @@ [(not binary) (text-result "binary_path is required." #t)] [(not (file-exists? binary)) (text-result (string-append "Binary not found: " binary) #t)] [else - (let ([out (guard (e [else (error-message e)]) (run-process (append (list binary) argv)))]) + (let ([out (guard (e [else (error-message e)]) (mcp-run-process (append (list binary) argv)))]) (if (or (string-contains out "Exception") (string-contains out "segmentation")) (text-result (string-append "Static binary smoke test failed:\n\n" out) #t) (text-result (string-append "Static binary smoke test passed:\n\n" (string-trim out)))))]))) @@ -6630,7 +6744,7 @@ [(not (command-exists? "openssl")) (text-result "openssl not found." #t)] [else (let ([out (guard (e [else (error-message e)]) - (run-process (list "openssl" "req" "-x509" "-newkey" "rsa:2048" + (mcp-run-process (list "openssl" "req" "-x509" "-newkey" "rsa:2048" "-keyout" key "-out" cert "-days" "1" "-nodes" "-subj" "/CN=localhost")))]) (text-result (string-append "Generated mTLS certificate:\ncert: " cert "\nkey: " key "\n\n" out)))]))) @@ -6998,6 +7112,19 @@ "Fetch a single cookbook recipe by ID." (schema (list (list "id" (property "string" "Recipe ID"))) '("id")) tool-howto-get #t '()) + (register-tool "jerboa_cookbook_task_bundle" "Cookbook Task Bundle" + "Package relevant cookbook recipes and a generic verification workflow for any Jerboa task." + (schema (list (list "task" (property "string" "User task or goal")) + (list "query" (property "string" "Alias for task")) + (list "description" (property "string" "Alias for task")) + (list "file_path" (property "string" "Target or changed file")) + (list "project_path" (property "string" "Project path")) + (list "cookbook_path" (property "string" "Cookbook path")) + (list "max_recipes" (property "number" "Maximum selected recipes")) + (list "include_code" (property "boolean" "Include full recipe code examples"))) + '()) + tool-cookbook-task-bundle #t + '("cookbook_task_bundle" "task_bundle" "recipe_bundle")) (register-tool "jerboa_security_scan" "Security Scanner" "Static security scanner for Jerboa, C, and Rust code." (schema (list (list "file_path" (property "string" "Single file")) --- a/support/build-jerboa-multicall.ss +++ b/support/build-jerboa-multicall.ss @@ -191,6 +191,8 @@ (std misc ports) (std misc process) (std os path) + (std os shell) + (std security taint) (std misc string))) (define (transform-mcp repo)