Skip to content

Commit 866442e

Browse files
committed
Add Squint skill
1 parent 25413b6 commit 866442e

7 files changed

Lines changed: 259 additions & 119 deletions

File tree

.github/plugin/marketplace.json

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,49 @@
11
{
2-
"name" : "awesome-backseat-driver",
3-
"metadata" : {
4-
"description" : "Clojure AI context plugins for GitHub Copilot — agents, skills, and workflows for REPL-first interactive programming with Calva Backseat Driver.",
5-
"version" : "1.0.6",
6-
"pluginRoot" : "./plugins"
2+
"name": "awesome-backseat-driver",
3+
"metadata": {
4+
"description": "Clojure AI context plugins for GitHub Copilot — agents, skills, and workflows for REPL-first interactive programming with Calva Backseat Driver.",
5+
"version": "1.0.6",
6+
"pluginRoot": "./plugins"
77
},
8-
"owner" : {
9-
"name" : "BetterThanTomorrow"
8+
"owner": {
9+
"name": "BetterThanTomorrow"
1010
},
11-
"plugins" : [ {
12-
"name" : "clojure",
13-
"source" : "clojure",
14-
"description" : "REPL-first Clojure development — agent and skill for any dialect and runtime.",
15-
"version" : "0.1.0"
16-
}, {
17-
"name" : "clojure-editor",
18-
"source" : "clojure-editor",
19-
"description" : "Subagent for editing Clojure files using Backseat Driver structural editing tools.",
20-
"version" : "0.1.0"
21-
}, {
22-
"name" : "babashka",
23-
"source" : "babashka",
24-
"description" : "Babashka scripting and bb.edn task skills for idiomatic Babashka development.",
25-
"version" : "0.1.0"
26-
}, {
27-
"name" : "epupp",
28-
"source" : "epupp",
29-
"description" : "Browser tampering and userscript development with Epupp (ClojureScript/Scittle in the browser).",
30-
"version" : "0.1.0"
31-
}, {
32-
"name" : "joyride",
33-
"source" : "joyride",
34-
"description" : "Joyride scripting for VS Code — ensures Joyride extension skills are loaded before working with Joyride.",
35-
"version" : "0.1.0"
36-
} ]
11+
"plugins": [
12+
{
13+
"name": "clojure",
14+
"source": "clojure",
15+
"description": "REPL-first Clojure development — agent and skill for any dialect and runtime.",
16+
"version": "0.1.0"
17+
},
18+
{
19+
"name": "clojure-editor",
20+
"source": "clojure-editor",
21+
"description": "Subagent for editing Clojure files using Backseat Driver structural editing tools.",
22+
"version": "0.1.0"
23+
},
24+
{
25+
"name": "babashka",
26+
"source": "babashka",
27+
"description": "Babashka scripting and bb.edn task skills for idiomatic Babashka development.",
28+
"version": "0.1.0"
29+
},
30+
{
31+
"name": "epupp",
32+
"source": "epupp",
33+
"description": "Browser tampering and userscript development with Epupp (ClojureScript/Scittle in the browser).",
34+
"version": "0.1.0"
35+
},
36+
{
37+
"name": "squint",
38+
"source": "squint",
39+
"description": "Squint ClojureScript development — compilation, REPL workflow, debugging, and tooling for squint.edn projects.",
40+
"version": "0.1.0"
41+
},
42+
{
43+
"name": "joyride",
44+
"source": "joyride",
45+
"description": "Joyride scripting for VS Code — ensures Joyride extension skills are loaded before working with Joyride.",
46+
"version": "0.1.0"
47+
}
48+
]
3749
}

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Changes to Awesome Backseat Driver
44

55
## [Unreleased]
66

7+
- Add Squint Clojure dialect skill
8+
79
## [1.0.5] - 2026-05-05
810

911
- Add Clojure dialects section to `clojure` skill, plus first stab at dialect references

plugins/clojure/skills/clojure/SKILL.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ Clojure runs on multiple hosts with different semantics. Identify the dialect be
194194

195195
### Dialect Detection
196196

197+
These are indicators, not rules — use common sense, and verify with the human if uncertain:
198+
197199
| Indicator | Dialect |
198200
|---|---|
199201
| `deps.edn` or `project.clj`, JVM classpath | JVM Clojure |
@@ -204,7 +206,9 @@ Clojure runs on multiple hosts with different semantics. Identify the dialect be
204206
| Scittle `<script>` tags, browser SCI REPL | Scittle (SCI in browser) |
205207
| `nbb.edn`, `#!/usr/bin/env nbb` | nbb (SCI on Node.js) |
206208

207-
When a project uses multiple dialects (e.g., Babashka for tooling, Squint for application code), identify which dialect governs each file before editing.
209+
When a project uses multiple dialects (e.g., Babashka for tooling, Squint for application code), identify which dialect governs each file before planning or editing.
210+
211+
Dedicated skills exist for **Squint** and **Babashka** — load them for operational depth. This section provides cross-dialect awareness that is always available.
208212

209213
### Cross-Dialect Divergence
210214

@@ -234,7 +238,8 @@ Load these from `references/` when the task needs operational depth:
234238
- [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.
235239
- [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.
236240
- [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`.
241+
- **Squint skill** — Full Squint development: compilation, REPL workflow, debugging, function availability, and core library gaps. Load when: writing Squint code or working with `squint.edn` projects.
242+
- [squint-dialect.md](references/squint-dialect.md) — Quick reference of essential Squint semantic differences. Covers the base when the Squint skill is not loaded. Points to the full Squint skill for operational depth.
238243

239244
## S5 — Invariants
240245

Lines changed: 14 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,19 @@
1-
# Squint Dialect Reference
1+
# Squint Dialect Quick Reference
22

3-
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.
3+
Essential Squint differences for agents that have loaded the Clojure skill but not the dedicated **Squint skill**. For full coverage — compilation workflow, REPL development, debugging, function availability — **load the Squint skill**. Planning or writing Squint code without loading the Squint skill is very, very, unwise.
44

5-
## Keywords ARE Strings
5+
## Critical Semantic Differences
66

7-
Squint compiles keywords to plain JavaScript strings:
7+
- **Keywords are strings**: `:foo``"foo"`. `(= :status "status")` is `true`. No `keyword?`, no `name`.
8+
- **Data is JS-native**: maps are objects, vectors are arrays, sets are Sets. `assoc`/`update` return shallow copies (originals unchanged). `assoc!` does not exist.
9+
- **No `js->clj`/`clj->js`**: do not exist. Compile to bare calls that crash with `ReferenceError`.
10+
- **Async**: use `js-await` (convention). Bare `await` also works. `doseq` does not properly await — use `loop`/`recur`.
11+
- **`nil` is `null`**: `nil?` returns true for both `null` and `undefined`. `identical?` distinguishes them.
812

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
13+
## Frequent Pitfalls
1514

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)
47-
- `identical?` distinguishes them: `(identical? nil js/undefined)``false`
48-
- 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
15+
- Unrecognized symbols emit bare JS calls — silent at compile time, `ReferenceError` at runtime
16+
- `sequential?` returns `true` for strings — use `vector?` to distinguish
17+
- Set literals callable inline but not from variables — use `contains?`
18+
- `(.-my-prop obj)` converts hyphens to underscores — use `aget` for literal hyphenated property names
19+
- No auto-resolved keywords (`::key` is a compiler error)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name" : "squint",
3+
"description" : "Squint ClojureScript development — compilation, REPL workflow, debugging, and tooling for squint.edn projects.",
4+
"version" : "0.1.0",
5+
"keywords" : [ "clojure", "squint", "clojurescript", "repl", "interactive-programming", "calva", "backseat-driver" ],
6+
"author" : {
7+
"name" : "BetterThanTomorrow"
8+
},
9+
"repository" : "https://github.com/BetterThanTomorrow/awesome-backseat-driver",
10+
"license" : "MIT",
11+
"skills" : [ "./skills/squint" ]
12+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
name: squint
3+
description: "Squint ClojureScript development — writing Squint code, compilation, REPL workflow, debugging, and tooling. Use when: working with Squint projects, planning, writing, or reviewing Squint code, .cljs files compiled to .mjs, Squint REPL sessions, debugging compiled output, checking whether clojure.core functions exist in Squint, or setting up Squint builds. Use whenever you are using Backseat Driver tools with Squint."
4+
---
5+
6+
# Squint Skill
7+
8+
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. The Clojure skill's S3 Dialects section provides cross-dialect comparison; this skill provides full Squint operational depth.
9+
10+
## Compilation Model
11+
12+
Squint compiles `.cljs` files to ES modules (`.mjs`) via configuration in `squint.edn`:
13+
14+
```clojure
15+
{:paths ["src"]
16+
:output-dir "lib"
17+
:extension "mjs"}
18+
```
19+
20+
Key implications:
21+
- `require` compiles to ES `import` — circular dependencies cause the same issues as in JS
22+
- Unrecognized symbols emit bare JavaScript calls — silent at compile time, crashes at runtime
23+
- This is the most common source of `ReferenceError: X is not defined` errors
24+
- Always check compilation output when a runtime error seems impossible from the source
25+
26+
## REPL and Development Workflow
27+
28+
Use `clojure_list_sessions` to find the Squint REPL session (typically `squint-repl`). Test pure functions and verify assumptions in the REPL before committing to files.
29+
30+
Development cycle:
31+
1. Squint watch (`npx squint watch` or project-specific watcher) for continuous compilation
32+
2. REPL for incremental development of pure functions
33+
3. Compilation check for single files when needed
34+
4. Check watcher output after edits — zero-warnings baseline
35+
36+
## Language Semantics
37+
38+
### Keywords ARE Strings
39+
40+
Squint compiles keywords to plain JavaScript strings:
41+
42+
- `:foo``"foo"`, `:foo/bar``"foo/bar"`
43+
- `(= :loaded "loaded")``true` — they are the same value
44+
- `str` works naturally: `(str "prefix-" :status)``"prefix-status"`
45+
- `keyword?` does not exist — there is no distinct keyword type to test for
46+
47+
### JS-Native Data Structures
48+
49+
Maps are JS objects, vectors are JS arrays, sets are JS Sets. No persistent data structures:
50+
51+
- `assoc`, `conj`, `update` return shallow copies — the original is not mutated (REPL-verified)
52+
- Nested objects are shared — deep mutations on nested values affect both original and copy
53+
- `(= a b)` on maps/vectors is reference equality, not structural
54+
- `assoc!` does NOT exist — use `assoc`
55+
56+
### Async: `^:async` + `js-await`
57+
58+
- Mark functions with `^:async` metadata — they return Promises
59+
- Both `js-await` and bare `await` work (REPL-verified). Convention: use `js-await` in Squint code for clarity, since bare `await` is the SCI convention.
60+
- No top-level `js-await` — must be inside an `^:async` function
61+
- `doseq` does NOT properly await `js-await` calls — iterations fire concurrently
62+
- Use `loop`/`recur` for sequential async iteration
63+
64+
```clojure
65+
(defn ^:async process-items! [items]
66+
(loop [remaining items]
67+
(when (seq remaining)
68+
(js-await (handle-item! (first remaining)))
69+
(recur (rest remaining)))))
70+
```
71+
72+
### No `js->clj` / `clj->js`
73+
74+
These functions do not exist. They compile to unqualified bare calls (`js__GT_clj()`) that crash at runtime with `ReferenceError`. Data is already JS-native — no conversion needed.
75+
76+
### `nil` is `null`
77+
78+
Squint compiles `nil` to JavaScript `null` (REPL-verified):
79+
80+
- `nil?` returns `true` for both `null` and `undefined` (uses JS loose equality)
81+
- `identical?` distinguishes them: `(identical? nil js/undefined)``false`
82+
- APIs that return `undefined` test as `nil?` but are not `identical?` to `nil`
83+
84+
## Function Availability
85+
86+
Several `clojure.core` functions are missing or behave differently in Squint:
87+
88+
| Function | Status | Workaround |
89+
|---|---|---|
90+
| `name` | does not exist | keywords are already strings — use directly |
91+
| `keyword` | does not exist | use the string directly |
92+
| `keyword?` | does not exist | no distinct keyword type |
93+
| `sequential?` | `true` for strings | use `vector?` to distinguish |
94+
| `js->clj` / `clj->js` | do not exist | data is already JS-native |
95+
96+
Set callability: inline set literals work (`(#{:a :b} :a)``"a"`) but sets bound to vars are not callable (REPL-verified). Use `(contains? s :a)` for consistent behavior.
97+
98+
No auto-resolved keywords — `::my-key` is a compiler error. Spell out the namespace.
99+
100+
### Checking Whether a Function Exists
101+
102+
When uncertain, query the Squint runtime directly:
103+
104+
```bash
105+
node -e "import('squint-cljs/core.js').then(m => console.log(typeof m.someFn))"
106+
```
107+
108+
`undefined` means the function does not exist. The Squint source at `src/squint/core.js` in the [Squint repo](https://github.com/squint-cljs/squint) is the definitive reference for available core functions.
109+
110+
When Squint does not recognize a symbol, it emits an unqualified JS call. The compiler does not error — but the generated code crashes at runtime. `refer` for missing functions silently succeeds but does not make them available.
111+
112+
## Property Access and Interop
113+
114+
- `(.-my-prop obj)` converts hyphens to underscores → `obj.my_prop`
115+
- Use `(aget obj "my-prop")` for literal property names with hyphens
116+
- `(:key obj)` works (compiles to string lookup on JS object)
117+
- Bare namespace requires fail — always use vector form: `(:require [lib :as alias])`
118+
- `js/` prefix works for global access: `js/document`, `js/console.log`, `js/fetch`
119+
120+
## Idiomatic Style
121+
122+
Prefer idiomatic Clojure functions over JavaScript interop when an equivalent exists. Squint supports most of `clojure.string` and the core library:
123+
124+
```clojure
125+
;; Prefer
126+
(string/includes? s "needle")
127+
(string/replace s #"old" "new")
128+
(mapv transform items)
129+
130+
;; Over
131+
(.includes s "needle")
132+
(.replace s "old" "new")
133+
(.map items transform)
134+
```
135+
136+
JS interop is appropriate for host-specific APIs (DOM, Node.js, browser APIs) where no Clojure equivalent exists.
137+
138+
## Debugging Compiled Output
139+
140+
When a runtime error does not match the source:
141+
142+
1. Read the compiled `.mjs` file to see what Squint generated
143+
2. Search for unqualified function calls (e.g., `name(...)` instead of `squint_core.name(...)`)
144+
3. Trace `ReferenceError: X is not defined` back to missing core functions
145+
4. Check that the intended Clojure function is actually available in Squint's core.js
146+
147+
## Invariants
148+
149+
- `js-await` by convention in Squint code (distinguishes from SCI's `await`)
150+
- Never use `js->clj`/`clj->js` — data is already JS-native
151+
- Check compiled output on mysterious runtime errors — the compiler does not catch missing functions
152+
- `contains?` for set membership — do not call sets as functions from variables
153+
- Prefer Clojure functions over JS interop when both are available
154+
- Zero compilation warnings — address root causes

0 commit comments

Comments
 (0)