Skip to content

Experiment: (vibe "...") — LLM code generation spliced in place - #5

Merged
nooga merged 3 commits into
mainfrom
vibe
Sep 18, 2026
Merged

nooga merged 3 commits into
mainfrom
vibe

Conversation

@nooga

@nooga nooga commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Draft / experiment — not for mainline as-is.

Type a call where you want the code, put point in it, press C-c C-v:

(defn triple [x] (* 3 x))

(vibe "a fn that doubles x, named double")
(defn triple [x] (* 3 x))

(defn double [x]
  (* 2 x))

One undo puts the call back.

What makes the answers usable

The model gets the whole buffer with the call site swapped for a
<<<VIBE:HERE>>> marker, plus the column that marker starts at — so it writes
code shaped to fit that spot (surrounding defns, aliases, indentation) rather
than a generic answer to the prompt in isolation.

vibe stays an ordinary function: C-x C-e on the form echoes the generated
code instead of splicing it, and (str (vibe "a") (vibe "b")) works.
vibe-replace parks the call site in an atom for the duration of the eval
rather than passing it as an argument, which is what makes both true at once.

Blocking without freezing

This is the only command in legmacs that waits on something slow, and it stays
out of the pure pipeline. vibe-replace does no I/O — it sets
:message "vibing..." plus :pending-vibe and returns, exactly like any other
command. main.lg's loop paints that frame, then resolve-effects runs the
request. Inline I/O would have left the screen frozen on a stale frame for the
whole round trip with no way to say what it was waiting for.

Setup

~/.config/legmacs/vibe.edn:

{:key "sk-..."}

Optional :model (default gpt-5.6-luna). The file is merged wholesale, so
:provider / :url / :max-tokens / :system work from there too. Four
layers, each beating the previous: defaults, vibe.edn, the config atom
(legmacs-init.lg), per-call opts. :openai and :anthropic ship; a provider
is four keys and two functions.

Testing

40 tests, no network: call-site search, prompt building, fence stripping,
config parsing and splicing are pure, and resolve-pending is exercised on the
paths that fail before any HTTP. Verified end to end against a mock OpenAI
server driving the real chord pipeline. Full suite 1029/1029 on let-go 1.12.2.

The first commit is independent and probably should land

1aade41 fixes a pre-existing bug that has nothing to do with vibe:
load-stringing a buffer that opens with (ns foo) — every real .lg file —
relocates the running namespace permanently, even from inside a compiled
function. Since legmacs evaluates in its own live namespace, one C-c C-e on
an ordinary file cost you buf/, km/, dispatch/ and render/ for the rest
of the session, and the next C-x C-e failed with "Can't resolve ... in this
context". vibe just made it visible. Cherry-pick it out if this branch never
merges.

🤖 Generated with Claude Code

nooga and others added 3 commits September 18, 2026 23:23
`load-string`ing a buffer that opens with `(ns foo)` -- which every real
.lg file does -- switches the current namespace permanently, and it
escapes the call: it happens even when `load-string` runs from inside an
already-compiled function, and it never switches back.

legmacs evaluates in its own live namespace, so that namespace *is* the
editor. One `C-c C-e` on an ordinary file therefore cost you every alias
main.lg set up -- `buf/`, `km/`, `dispatch/`, `render/` -- for the rest
of the session, and the next `C-x C-e` failed with "Can't resolve ... in
this context".

`preserving-ns` grabs `(ns-name *ns*)`, runs the thunk, and `in-ns`es
back. It reifies the outcome rather than using `finally`, since a let-go
`try` whose only trailing clause is `finally` swallows the in-flight
exception, and a `catch` that rethrows skips the `finally` -- neither
shape both propagates and restores.

Only eval-buffer is wrapped. `C-x C-e`, `C-j` and the *repl* buffer are
deliberately left alone: an `(in-ns 'foo)` you typed and evaluated on
purpose should still move you.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Type a call where you want the code, put point in it, press C-c C-v, and
the form is replaced by whatever the model writes -- as one undo step.

The context is the point of it. vibe-replace sends the whole buffer with
the call site swapped for a <<<VIBE:HERE>>> marker, plus the column that
marker starts at, so the answer is written to fit that exact spot --
surrounding defns, aliases and indentation included -- rather than being
a generic answer to the prompt in isolation.

`vibe` stays an ordinary function: C-x C-e on a (vibe "...") form echoes
the generated code instead of splicing it, and it composes, e.g.
(str (vibe "a") (vibe "b")). vibe-replace parks the call site in an atom
for the duration of the eval rather than passing it as an argument, which
is what makes both work.

This is the only command here that blocks on something slow, and it
stays out of the pure pipeline: vibe-replace does no I/O at all, it sets
:message "vibing..." and :pending-vibe and returns. main.lg's loop paints
that frame first, then resolve-effects runs the request. Doing it inline
would have frozen the screen on a stale frame for the whole round trip.

Config is ~/.config/legmacs/vibe.edn ({:key "sk-..."}, optional :model,
default gpt-5.6-luna), merged wholesale so :provider/:url/:max-tokens/
:system work from there too. Four layers, each beating the last:
defaults, vibe.edn, the config atom (legmacs-init.lg), per-call opts.
:openai and :anthropic ship; a provider is four keys and two functions.

Two guards are load-bearing. resolve-pending re-checks that a (vibe form
still opens at :start before touching the buffer, and it refuses to
splice a non-string result -- let-go's `eval` returns compile errors as
an #error value instead of raising them, so a typo'd prompt would
otherwise be pasted into your code.

Tested without a network: the call-site search, prompt building, fence
stripping, config parsing and splicing are all pure, and resolve-pending
is exercised on the paths that fail before any HTTP happens. Verified
end to end against a mock OpenAI server driving the real chord pipeline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@nooga
nooga marked this pull request as ready for review September 18, 2026 22:28
@nooga
nooga merged commit 1ed8483 into main Sep 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant