ClojureScript VS Code extension providing REPL access to AI agents via Language Model API and MCP. Enables Interactive Programming where AI evaluates code in the user's running environment rather than guessing.
Functional core with unidirectional flow:
dispatch! → handle-actions → [enrich → route] → {:ex/db, :ex/fxs, :ex/dxs} → update state → execute effects
Actions (*/axs.cljs): Pure functions returning state changes and effects
Effects (*/fxs.cljs): Side effect handlers with dispatch access
State: Single app-db atom in src/calva_backseat_driver/app/db.cljs
The app-db atom contains:
{:vscode/extension-context ; VS Code extension API access
:app/log-file-uri ; Logging location
:app/min-log-level ; :debug/:info/:warn/:error
:mcp/wrapper-config-path ; ~/.config/calva/backseat-driver
:calva/output-line-counter ; Monotonic line counter for output entities
:calva/history-storage-uri ; Workspace-scoped Transit file for persistent history
:extension/disposables ; VS Code subscriptions
:extension/when-contexts} ; Context keys for enablement!output-conn— Session-scoped, schemaless. All REPL output categories. Cap: 1000 entities.!history-conn— Persistent,:output/lineunique identity schema.evaluatedCodeonly. Cap: 10000 entities. Persisted toeval-history.transit.jsonundercontext.storageUri.
Available at calva-backseat-driver.integrations.calva.api/calva-api:
{:repl {:evaluateCode, :currentSessionKey, :onOutputLogged}
:ranges {:currentTopLevelForm, :currentEnclosingForm, ...}
:editor {:replace}
:document {:getNamespace, :getNamespaceAndNsForm}
:info {:getSymbolInfo, :getClojureDocsDotOrg}}npm run watch # shadow-cljs + nREPL, auto-runs tests
# Connect Calva: Ctrl+Alt+C Ctrl+Alt+C
# Launch Extension Host: F5Critical: This is a ClojureScript project running in VS Code Extension Host (Node.js). Use cljs REPL session, not clj.
;; Explore runtime state
(in-ns 'calva-backseat-driver.app.db)
@!app-db
;; Test Calva API
(in-ns 'calva-backseat-driver.integrations.calva.api)
(keys (:repl calva-api))
;; Test utilities
(in-ns 'calva-backseat-driver.integrations.calva.editor)
(require '[calva-backseat-driver.integrations.calva.editor-util :as util])
(util/get-context-lines sample-text 3 10)bb run-e2e-tests-ws # Full E2E
bb run-mcp-inspector # Test MCP tools interactively
bb package-pre-release # Package pre-release VSIXNote: When running bb run-e2e-tests-ws: Detailed output goes to .tmp/e2e-output.log — read that file for details, if needed. The command outputs a very brief summary. Don't pipe or redirect.
Load the e2e-testing skill (.github/skills/e2e-testing/SKILL.md) when writing, modifying, or debugging e2e tests. It covers test runner mechanics, shared helpers, session lifecycle patterns, and data shape through MCP.
Key rule: always use wait-for+ polling instead of p/delay/setTimeout for async conditions.
Actions use keyword placeholders enriched at runtime:
;; In action:
[:vscode/fx.show-input-box {:title "Name" :ex/then [[:handler :ex/action-args]]}]
;; Effect enriches and dispatches:
(dispatch! context (ax/enrich-with-args then-actions result))
;; Placeholders: :ex/action-args (entire result), :ex/action-args%1 (first element), etc.When structural editing fails, returns diagnostic context:
;; 21 lines around target with line numbers and marker
" 13 | (defn subtract-numbers
14 | \"Subtracts b from a\"
15 | [a b]
16 | (- a b))
→ 17 |
18 | (defn add-numbers
..."Implemented in integrations/calva/editor-util.cljs:
(util/get-context-lines doc-text line-number context-size)
;; Returns formatted string with line numbers and → marker at target- Side effects:
function-name! - Promises:
function-name+ - Inline debugging:
(def var var)inside REPL comment blocks - Test names: kebab-case describing area/thing (
file-context-formatting)
The clojure_edit_files tool uses targetLineText (first line of target form) + line number ±2:
;; clojure_edit_files batch with replace/insert edits
{:edits [{:type "replace"
:filePath "/absolute/path.clj"
:line 23
:targetLineText "(defn multiply-numbers"
:newForm "(defn multiply-numbers\n [x y]\n (* x y))"}]}- Bracket balancing: Parinfer-powered (
integrations/parinfer.cljs) - Rich Comment support: Forms inside
(comment ...)treated as top-level - Diagnostic context: On failure, returns 21-line context + remedy message
When making multiple edits, work from highest line number to lowest (line numbers shift down as you edit above).
- Backend server runs in Extension Host (TCP socket)
- Port file:
${workspaceFolder}/.calva/mcp-server/port - stdio wrapper (
dist/calva-mcp-server.js) relays stdio ↔ socket - One server per workspace folder
assets/skills/andassets/instructions/are the canonical source for content bundled with the extension. When updating skill or instruction content, edit these files — not the installed extension copies under~/.vscode*/extensions/.- Skills declared in
package.jsonundercontributes.chatSkillsare exposed as MCP resources - Discovery:
resources/listreturns skill name, description, and URI - Reading:
resources/readat/skills/{name}/SKILL.mdreturns full skill content - Dynamic instructions: the
initializeresponse includes instructions composed from available tools and skills - Implementation:
src/calva_backseat_driver/mcp/requests.cljs—skill-manifests,get-skills,compose-instructions
Settings read via enrichment:
;; In action/effect context:
:vscode/config.enableMcpReplEvaluation ; boolean
:vscode/config.mcpSocketServerPort ; number (default 1664, 0=random)
:vscode/config.autoStartMCPServer ; boolean
:vscode/config.provideBdSkill ; boolean (default true) — enable/disable Backseat Driver skill
:vscode/config.provideEditSkill ; boolean (default true) — enable/disable structural editing skill- Create
assets/skills/{name}/SKILL.mdwith YAML frontmatter (name,description) - Add entry to
package.json→contributes.chatSkills:{"path": "./assets/skills/{name}/SKILL.md"} - The MCP server picks it up automatically — no code changes needed
- Tool manifest:
package.json→languageModelTools - MCP handler:
src/calva_backseat_driver/mcp/requests.cljs - VS Code tool:
src/calva_backseat_driver/integrations/vscode/tools.cljs - Test:
bb run-mcp-inspector
;; Action (pure, in */axs.cljs)
[:app/ax.register-command command-id actions]
{:ex/fxs [[:app/fx.register-command command-id actions]]}
;; Effect (side effect, in */fxs.cljs)
[:app/fx.register-command command-id actions]
(vscode/commands.registerCommand command-id
(fn [] (dispatch! context actions)))- DON'T access
@!app-dbdirectly from helpers (pass data explicitly) - DON'T perform side effects in action handlers (return effects instead)
- DON'T forget namespace reloads after file edits:
(require 'ns :reload) - DO evaluate in REPL before file edits
- DO use
cljssession (this is ClojureScript, not Clojure) - DO check
:calva/output-bufferin app-db to see REPL activity - DO work bottom-to-top when multiple structural edits
Key Namespaces:
calva-backseat-driver.ex.ex- Dispatchercalva-backseat-driver.app.{axs,fxs,db}- Application layercalva-backseat-driver.mcp.{server,requests,axs,fxs}- MCP servercalva-backseat-driver.integrations.calva.{api,editor,editor-util}- Calva integrationcalva-backseat-driver.integrations.vscode.tools- VS Code Language Model tools
REPL Inspection Commands:
@calva-backseat-driver.app.db/!app-db ; See full state
(keys calva-backseat-driver.integrations.calva.api/calva-api) ; Available Calva APIs
(:calva/output-buffer @calva-backseat-driver.app.db/!app-db) ; Recent REPL outputBuild Artifacts:
out/extension.js- Main extension bundledist/calva-mcp-server.js- MCP stdio wrapperout/extension-tests.js- Unit tests
For deeper architecture details, see:
dev/EX_ARCHITECTURE.md- Action/effect systemdev/MCP_OVERVIEW.md- MCP protocol specificsPROJECT_SUMMARY.md- Complete project overview