chore: add CLAUDE.md, .gitignore, and macOS setup target

ober

81a910fefa74bd22383ad96eb2d0b4eb6fa91152

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6f35aaf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+# Compiled artifacts
+*.so
+*.wpo
+*.fasl
+
+# Host-specific symlink created by `make setup` (target path differs per machine)
+lib/libjerboa_native.so
+
+# Editor / OS
+.DS_Store
+*.swp
+*~
+.idea/
+.vscode/
+
+# Test/bench scratch dirs
+/tmp/jerboa-db-test-*
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..3143637
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,36 @@
+# jerboa-db — Claude working rules
+
+## Always sync with origin at session start
+
+**Before doing anything in this repo**, fetch and inspect the remote. The
+project is actively developed across multiple parallel sessions/agents and
+local `master` is frequently behind `origin/master`. Working from a stale
+local tree wastes hours: the change you're about to make may already be on
+`origin`, or worse, you'll commit work premised on a project state that no
+longer exists.
+
+```bash
+git fetch origin
+git log --oneline HEAD..origin/master   # what's on the remote that we don't have
+git log --oneline origin/master..HEAD   # what's local-only
+```
+
+If `HEAD..origin/master` is non-empty:
+- Stop and read the new commit subjects before proceeding.
+- Pull (or rebase) into a clean tree before starting any new work.
+- Re-baseline assumptions about what's "done" — read the latest `jerboa-db.md`
+  status block and run `make test` against the *new* tree.
+
+This rule overrides any prior context summary you've been handed at session
+start. Summaries describe a snapshot in time; `origin/master` is the truth.
+
+## Before committing
+
+After running tests, double-check `git fetch origin` once more. If new commits
+landed during your work, **rebase, don't merge** — keep history linear, and
+re-run tests after the rebase before pushing.
+
+## Never force-push to master
+
+`master` is shared. If a push is rejected, the answer is to fetch and rebase,
+not `--force`.
diff --git a/Makefile b/Makefile
index 7689cbc..f3c041b 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,25 @@ CHEZ_EXT_DIR ?= $(HOME)/src
 CHEZ_EXT_LIBDIRS = $(CHEZ_EXT_DIR)/chez-lmdb:$(CHEZ_EXT_DIR)/chez-duckdb
 FULL_LIBDIRS = $(LIBDIRS):$(CHEZ_EXT_LIBDIRS)
 
-.PHONY: test test-cluster test-transport test-transport-tls test-migrate test-peer build clean check bench bench-quick mbrainz mbrainz-quick showcase
+.PHONY: test test-cluster test-transport test-transport-tls test-migrate test-peer build clean check bench bench-quick mbrainz mbrainz-quick showcase setup
+
+# One-time setup: create the libjerboa_native.so symlink that Jerboa's DuckDB
+# loader expects. On macOS the upstream artifact is libjerboa_native.dylib
+# but the loader looks for .so; the symlink bridges that gap.
+setup:
+	@if [ ! -e lib/libjerboa_native.so ]; then \
+		if [ -e $(JERBOA_DIR)/lib/libjerboa_native.dylib ]; then \
+			ln -sf $(JERBOA_DIR)/lib/libjerboa_native.dylib lib/libjerboa_native.so; \
+			echo "Symlinked lib/libjerboa_native.so -> $(JERBOA_DIR)/lib/libjerboa_native.dylib"; \
+		elif [ -e $(JERBOA_DIR)/lib/libjerboa_native.so ]; then \
+			ln -sf $(JERBOA_DIR)/lib/libjerboa_native.so lib/libjerboa_native.so; \
+			echo "Symlinked lib/libjerboa_native.so -> $(JERBOA_DIR)/lib/libjerboa_native.so"; \
+		else \
+			echo "WARNING: libjerboa_native not found in $(JERBOA_DIR)/lib; DuckDB will be unavailable"; \
+		fi; \
+	else \
+		echo "lib/libjerboa_native.so already present"; \
+	fi
 
 # Run the core test suite (in-memory, no FFI deps)
 test: