kidlisp: v2 structural index + dashboard kidlisp tab
Every kidlisp piece is now parsed into a flat AST (one entity per node,
linked via :ast/parent refs) and persisted alongside the piece in
Datomic. Parser is Node-only (system/backend/kidlisp-ast.mjs) so the
sidecar stays a dumb persister — the authoritative kidlisp grammar
still lives in kidlisp.mjs. Indexing happens fire-and-forget on the
write path; failures don't block the user response.
New AST attrs in schema: :ast/piece (indexed ref back to piece),
:ast/kind, :ast/op (indexed for corpus-wide structural queries),
:ast/literal, :ast/parent, :ast/position, :ast/depth. The piece side
gets :kidlisp/ast-nodes as a component-many ref so cascade retraction
works. schema/ensure! now does a per-attribute presence check instead
of a single sentinel, so additive schema evolution Just Works on
sidecar restart.
Two new client endpoints (client-secret gated):
- POST /kidlisp/:code/ast — atomic replace-all
- GET /kidlisp/:code/ast — flat node list for tree rendering
- GET /kidlisp/structural/pieces-using?op=X — the corpus-wide
datalog query that Mongo couldn't do
Silo dashboard gains a kidlisp tab with a piece picker (by hits or
recent, with search), a source+metadata pane, and an AST tree pane.
Every :call node's op is clickable — one click runs a datalog query
for every piece in the corpus using that op, with the results
rendered as clickable pills that jump to the piece.
Small sidecar fix: added ring.middleware.params/wrap-params so the
structural endpoint can actually read its query string.
backfill-kidlisp-ast.mjs reparses the existing 17k pieces' sources
into AST via the sidecar. Idempotent. Concurrency-4 runs at ~45
pieces/sec on silo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>