Filesystem tools exposed as bare symbols in the Python sandbox.
Two layers:
Structured helpers for tree / search / structure:
(ls dir) ; a DIRECTORY -> [{name path type size}], directories first;
(ls dir, depth=2) ; nested rows sit in children. A SANDBOX helper, not a
; native tool: it is called inside a python_execution block.
; A nil or blank path throws before any I/O.
(grep query) ; -> ONE anchored TEXT block, never a map: a summary line,
; then <line>:<hash>| <text> rows under each path;
; query = a term or list of terms (OR), smart-case
; substring — or a REGEX with is_regex.
; Opts: paths/include/limit/is_hidden/is_regex
(struct_index paths) ; -> per-file skeleton: imports, definitions, signatures
Cwd-safe wrappers over the babashka.fs file API. Code is edited by NAME with
struct_patch and anything else by ADDRESS with cat/patch — ONE patch
call carries every edit for one file and writes once; plain Python owns
whole-file creation and deletion:
(create-dirs path) (copy src dest) (move src dest) (delete path) (delete-if-exists path) (exists? path)
Hard guard: every path must stay inside the session's working
directory (fs/cwd); .. traversal is rejected before any I/O.
Filesystem tools exposed as bare symbols in the Python sandbox.
Two layers:
1. Structured helpers for tree / search / structure:
(ls dir) ; a DIRECTORY -> [{name path type size}], directories first;
(ls dir, depth=2) ; nested rows sit in `children`. A SANDBOX helper, not a
; native tool: it is called inside a python_execution block.
; A nil or blank path throws before any I/O.
(grep query) ; -> ONE anchored TEXT block, never a map: a summary line,
; then ` <line>:<hash>| <text>` rows under each path;
; query = a term or list of terms (OR), smart-case
; substring — or a REGEX with `is_regex`.
; Opts: paths/include/limit/is_hidden/is_regex
(struct_index paths) ; -> per-file skeleton: imports, definitions, signatures
2. Cwd-safe wrappers over the babashka.fs file API. Code is edited by NAME with
`struct_patch` and anything else by ADDRESS with `cat`/`patch` — ONE `patch`
call carries every edit for one file and writes once; plain Python owns
whole-file creation and deletion:
(create-dirs path)
(copy src dest)
(move src dest)
(delete path)
(delete-if-exists path)
(exists? path)
Hard guard: every path must stay inside the session's working
directory (`fs/cwd`); `..` traversal is rejected before any I/O.Unicode-escape hygiene for model-authored edit TEXT.
Public surface: decode-unicode-escapes — undo the \uXXXX drift a model
writes when it means the character itself, and nothing else.
Unicode-escape hygiene for model-authored edit TEXT. Public surface: `decode-unicode-escapes` — undo the `\uXXXX` drift a model writes when it means the character itself, and nothing else.
Pure hashline primitives: the ANCHOR vocabulary cat mints, grep echoes and
patch spends.
An anchor is <1-based line>:<3-hex content hash> (Can Bölük's original
hashline shape). The LINE NUMBER locates the line; the CONTENT HASH verifies
it. Two coordinates, so a stale or reused anchor can no longer silently land
an edit on the wrong line: when the content sits far from the stated line the
write is REFUSED (:anchor-misplaced) instead of corrupting the file.
This namespace is pure — no IO, no tool wiring, no extension envelope. Every surface that addresses a line routes here so the scheme is never recomputed:
split-content-lines / char-offset-at-line blob <-> line/char coordinates
line-hash / line-anchor / anchor->line text -> <line>:<hash>
render-hashline-block [[ln text]…] -> gutter text
anchor-token / parse-anchor rendered line -> bare anchor
indices-matching-hash content-only hash lookup
resolve-one-anchor / resolve-anchor-range anchor -> live line, or refusal
resolve-anchor-range-read the READ-tolerant twin
resolve-anchor-edit-span anchor span -> char span
Pure hashline primitives: the ANCHOR vocabulary `cat` mints, `grep` echoes and `patch` spends. An anchor is `<1-based line>:<3-hex content hash>` (Can Bölük's original hashline shape). The LINE NUMBER locates the line; the CONTENT HASH verifies it. Two coordinates, so a stale or reused anchor can no longer silently land an edit on the wrong line: when the content sits far from the stated line the write is REFUSED (`:anchor-misplaced`) instead of corrupting the file. This namespace is pure — no IO, no tool wiring, no extension envelope. Every surface that addresses a line routes here so the scheme is never recomputed: split-content-lines / char-offset-at-line blob <-> line/char coordinates line-hash / line-anchor / anchor->line text -> `<line>:<hash>` render-hashline-block [[ln text]…] -> gutter text anchor-token / parse-anchor rendered line -> bare anchor indices-matching-hash content-only hash lookup resolve-one-anchor / resolve-anchor-range anchor -> live line, or refusal resolve-anchor-range-read the READ-tolerant twin resolve-anchor-edit-span anchor span -> char span
Structural INDEX: a high-level, line-ranged skeleton of a source file produced via tree-sitter (com.blockether/tree-sitter-language-pack, which sources Clojure from our own grammar fork).
Every item carries its FIRST and LAST 1-based line, so a definition's whole span is readable straight from the index — no intermediate read. Each line is:
<kind> <name> <signature> @<start-line>..<end-line>
e.g.
class Greeter @3..7 function hello @6..7 function main @9..10
Read it FIRST: a cheap map of a file so you jump straight to the right range instead of reading the whole file.
Requiring this namespace also requires the native resolver, which selects the right per-platform FFI library at runtime.
Structural INDEX: a high-level, line-ranged skeleton of a source file
produced via tree-sitter (com.blockether/tree-sitter-language-pack, which
sources Clojure from our own grammar fork).
Every item carries its FIRST and LAST 1-based line, so a definition's whole
span is readable straight from the index — no intermediate read. Each line is:
<kind> <name> <signature> @<start-line>..<end-line>
e.g.
class Greeter @3..7
function hello @6..7
function main @9..10
Read it FIRST: a cheap map of a file so you jump straight to the right range
instead of reading the whole file.
Requiring this namespace also requires the native resolver, which selects the
right per-platform FFI library at runtime.Thin Clojure adapter over the pack's Java structural-edit engine
(dev.kreuzberg.treesitterlanguagepack.StructuralApi). All the work —
locate the definition by name from the tree-sitter outline, splice its line
span, and re-parse to refuse syntax-breaking edits — lives in Java so it is
language-neutral, reusable from any JVM consumer, and native-image clean.
This namespace only maps vis op keywords onto the Java API.
Thin Clojure adapter over the pack's Java structural-edit engine (`dev.kreuzberg.treesitterlanguagepack.StructuralApi`). All the work — locate the definition by name from the tree-sitter outline, splice its line span, and re-parse to refuse syntax-breaking edits — lives in Java so it is language-neutral, reusable from any JVM consumer, and native-image clean. This namespace only maps vis op keywords onto the Java API.
Language-neutral STRUCTURAL ZIPPER over the tree-sitter pack (306+ langs) —
the unified cursor the name-based structural ops were missing.
A node's location is a STATELESS PATH: a vector of NAMED-child indices from
the root (e.g. [2 0] = first named child of the third named child of the
file). Stateless means it round-trips cleanly through async tool calls — no
live native cursor to keep between calls. Relative moves (down/up/next/prev)
are pure path arithmetic on top, so the model navigates like a rewrite-clj
zipper but over EVERY language tree-sitter understands.
Edits splice the target node's UTF-8 byte range and RE-PARSE, refusing a
result that introduces a syntax error the original didn't have — the same
safety contract as structural. Pairs with the name-based ops: locate a def
by name, then walk into it by path.
All native handles (Parser/Tree/Node) are opened and closed inside each call; only plain Clojure data escapes.
Language-neutral STRUCTURAL ZIPPER over the tree-sitter pack (306+ langs) — the unified cursor the name-based `structural` ops were missing. A node's location is a STATELESS PATH: a vector of NAMED-child indices from the root (e.g. `[2 0]` = first named child of the third named child of the file). Stateless means it round-trips cleanly through async tool calls — no live native cursor to keep between calls. Relative moves (down/up/next/prev) are pure path arithmetic on top, so the model navigates like a rewrite-clj zipper but over EVERY language tree-sitter understands. Edits splice the target node's UTF-8 byte range and RE-PARSE, refusing a result that introduces a syntax error the original didn't have — the same safety contract as `structural`. Pairs with the name-based ops: locate a def by name, then walk into it by path. All native handles (Parser/Tree/Node) are opened and closed inside each call; only plain Clojure data escapes.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |