You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: plugins/clojure/skills/clojure/SKILL.md
+37-7Lines changed: 37 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -190,21 +190,51 @@ Stay focused on the specific problem. No unnecessary checks or unrelated suggest
190
190
191
191
## S3 — Dialects
192
192
193
-
Clojure runs on multiple hosts. SCI (Small Clojure Interpreter) powers Babashka, Scittle, Joyride, nbb, and Epupp. Key orientation points:
193
+
Clojure runs on multiple hosts with different semantics. Identify the dialect before writing code.
194
194
195
-
-**SCI macros have full Clojure fidelity** — `defmacro` with syntax-quote, gensyms, `binding`, `try/finally`, nested expansion — all work exactly as in Clojure. No special handling needed.
196
-
-**JS-hosted SCI uses Clojure semantics**, not ClojureScript — no `:require-macros`, no `:include-macros true`, keywords are true keywords (not strings).
197
-
-**`await` vs `js-await`** — SCI uses `await`; Squint uses `js-await`. They are not interchangeable.
195
+
### Dialect Detection
198
196
199
-
Load the dialect reference when working in SCI-based environments or when uncertain about feature parity.
197
+
| Indicator | Dialect |
198
+
|---|---|
199
+
|`deps.edn` or `project.clj`, JVM classpath | JVM Clojure |
200
+
|`bb.edn`, `#!/usr/bin/env bb`| Babashka (SCI on JVM) |
201
+
|`shadow-cljs.edn` or `:cljs` build config | ClojureScript |
202
+
|`squint.edn`, `.cljs` compiled to `.mjs`| Squint |
203
+
|`.joyride/` directory, Joyride REPL session | Joyride (SCI in VS Code) |
|`nbb.edn`, `#!/usr/bin/env nbb`| nbb (SCI on Node.js) |
206
+
207
+
When a project uses multiple dialects (e.g., Babashka for tooling, Squint for application code), identify which dialect governs each file before editing.
| Macros | full `defmacro`| full `defmacro`| compile-time only |`:require-macros` from `.clj`|
218
+
219
+
### Critical Constraints
220
+
221
+
-**SCI async**: in SCI environments, use `await`. `js-await` is Squint-specific and does not resolve in SCI.
222
+
-**Squint async**: in Squint, use `js-await` by convention. Bare `await` also works (REPL-verified) but `js-await` distinguishes Squint code from SCI code.
223
+
-**Squint data**: `js->clj` and `clj->js` do not exist in Squint. Data is already JS-native. Calling them produces a `ReferenceError` at runtime.
224
+
-**Squint keywords**: keywords compile to strings. `(= :status "status")` is `true`.
225
+
-**SCI macros**: SCI implements the full Clojure macro system. LLM training data often describes SCI macros as limited — they are not. Write macros exactly as in Clojure.
226
+
-**JS-hosted SCI uses Clojure semantics**, not ClojureScript — no `:require-macros`, no `:include-macros true`.
227
+
228
+
Load dialect references from `references/` for operational depth per dialect.
200
229
201
230
## S2 — References
202
231
203
232
Load these from `references/` when the task needs operational depth:
204
233
205
234
-[repl-workflows.md](references/repl-workflows.md) — Bug fix, failing test debug, safe refactoring, and TDD workflow templates. Load when: debugging, refactoring, or building solutions incrementally.
206
-
-[runtime-patterns.md](references/runtime-patterns.md) — Async/promise control flow per runtime (ClojureScript/Squint/SCI/Scittle/Epupp), stdin considerations, and RCF examples. Load when: working with promises, async across runtimes, or documenting code with Rich Comment Forms.
207
-
-[sci-dialect.md](references/sci-dialect.md) — REPL-verified SCI feature parity and differences vs Clojure. Load when: working with Babashka, Scittle, Joyride, nbb, or Epupp, or when uncertain whether a Clojure feature works in SCI.
235
+
-[runtime-patterns.md](references/runtime-patterns.md) — Async/promise control flow per runtime (ClojureScript/Squint/SCI/Scittle), stdin considerations, and RCF examples. Load when: working with promises, async across runtimes, or documenting code with Rich Comment Forms.
236
+
-[sci-dialect.md](references/sci-dialect.md) — REPL-verified SCI feature parity and differences vs Clojure. Covers Babashka, Scittle, Joyride, nbb, and other SCI-based environments. Load when: uncertain whether a Clojure feature works in SCI.
237
+
-[squint-dialect.md](references/squint-dialect.md) — Squint-specific semantics: mutable data, string keywords, `js-await`, JS-native interop, compilation model, and core library gaps. Load when: working with Squint projects or `.cljs` files compiled via `squint.edn`.
Squint is a ClojureScript-syntax compiler that emits ES module JavaScript. It is NOT standard ClojureScript (shadow-cljs) and NOT SCI — it has fundamentally different semantics for data structures, keywords, and async.
4
+
5
+
## Keywords ARE Strings
6
+
7
+
Squint compiles keywords to plain JavaScript strings:
8
+
9
+
-`:foo` → `"foo"`, `:foo/bar` → `"foo/bar"`
10
+
-`(= :loaded "loaded")` → `true` — they are the same value
11
+
- No `name` function needed (or available) — keywords are their string representation
12
+
-`str` works naturally: `(str "prefix-" :status)` → `"prefix-status"`
13
+
-`keyword?` does not exist — there is no distinct keyword type to test for
14
+
- Idiomatic Clojure code that treats keywords as opaque identifiers works fine; code that relies on keyword identity or type does not
15
+
16
+
## JS-Native Data Structures
17
+
18
+
Maps are JS objects, vectors are JS arrays, sets are JS Sets. No persistent data structures:
19
+
20
+
-`assoc`, `conj`, `update` return shallow copies — the original is not mutated (REPL-verified)
21
+
- However, nested objects are shared — deep mutations on nested values affect both original and copy
22
+
-`(= a b)` on maps/vectors is reference equality, not structural — use caution in tests
23
+
-`assoc!` does NOT exist — use `assoc`
24
+
25
+
## Async: `^:async` + `js-await`
26
+
27
+
- Mark functions with `^:async` metadata — they return Promises
28
+
- Both `js-await` and bare `await` work in Squint (REPL-verified). Convention: use `js-await` in Squint code for clarity, since bare `await` is the SCI convention.
29
+
- No top-level `js-await` — must be inside an `^:async` function
30
+
-`doseq` does NOT properly await `js-await` calls — iterations fire concurrently
31
+
- Use `loop`/`recur` for sequential async iteration
32
+
-`js/Promise.all` works for intentionally parallel execution
33
+
34
+
## No `js->clj` / `clj->js`
35
+
36
+
These functions do not exist in Squint. They compile to unqualified bare calls (`js__GT_clj()`) that crash at runtime with `ReferenceError`:
37
+
38
+
- Data is already JS-native — no conversion needed
39
+
- Keyword maps are already string-keyed JS objects
40
+
- Access properties with `get`, `aget`, or `(.-prop obj)` directly
41
+
42
+
## `nil` is `null`
43
+
44
+
Squint compiles `nil` to JavaScript `null` (REPL-verified: `nil === null` is `true`, `nil === undefined` is `false`):
45
+
46
+
-`nil?` returns `true` for both `null` and `undefined` (uses JS loose equality)
- APIs that return `undefined` (e.g., missing object properties) test as `nil?` but are not `identical?` to `nil`
49
+
50
+
## Core Library Gaps
51
+
52
+
Several `clojure.core` functions are missing or behave differently:
53
+
54
+
-`name` — does not exist. Keywords are already strings; use direct string operations
55
+
-`sequential?` — returns `true` for strings (they are JS arrays of chars). Use `vector?` to distinguish
56
+
- Set literals are callable inline — `(#{:a :b} :a)` works — but sets bound to vars are not callable (REPL-verified). Use `(contains? s :a)` for consistent behavior.
57
+
- No auto-resolved keywords — `::my-key` is a compiler error. Spell out the namespace
58
+
-`count` works on arrays and strings but not on all collection types uniformly
59
+
-`keyword` function does not exist — use the string directly
60
+
61
+
## Property Access and Interop
62
+
63
+
-`(.-my-prop obj)` converts hyphens to underscores → `obj.my_prop`
64
+
- Use `(aget obj "my-prop")` for literal property names with hyphens
65
+
-`(:key obj)` works (compiles to string lookup on JS object)
66
+
- Bare namespace requires fail — always use vector form: `(:require [lib :as alias])`
67
+
-`js/` prefix works for global access: `js/document`, `js/console.log`, `js/fetch`
68
+
69
+
## Compilation Model
70
+
71
+
Squint compiles `.cljs` files to ES modules (`.mjs`). Key implications:
72
+
73
+
- Unrecognized symbols emit bare JavaScript calls — silent at compile time, crashes at runtime
74
+
- This is the most common source of `ReferenceError: X is not defined` errors
75
+
- Always check compilation output when a runtime error seems impossible from the source
76
+
-`require` compiles to ES `import` — circular dependencies cause the same issues as in JS
0 commit comments