From 310906f2ae916a5de61984c3371706a732510b63 Mon Sep 17 00:00:00 2001 From: Bryce Date: Sat, 31 Jan 2026 20:58:33 -0800 Subject: [PATCH] clauding --- .claude/commands/clojure-nrepl.md | 105 ++++++++++++++ .claude/skills/clojure-eval/SKILL.md | 174 ++++++++++++++++++++++++ .claude/skills/clojure-eval/examples.md | 82 +++++++++++ AGENTS.md | 154 +++++++++++++++++++++ CLAUDE.md | 123 +++++++++++++++++ 5 files changed, 638 insertions(+) create mode 100644 .claude/commands/clojure-nrepl.md create mode 100644 .claude/skills/clojure-eval/SKILL.md create mode 100644 .claude/skills/clojure-eval/examples.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.claude/commands/clojure-nrepl.md b/.claude/commands/clojure-nrepl.md new file mode 100644 index 00000000..c9c2d59b --- /dev/null +++ b/.claude/commands/clojure-nrepl.md @@ -0,0 +1,105 @@ +--- +description: Info on how to evaluate Clojure code via nREPL using clj-nrepl-eval +--- + +When you need to evaluate Clojure code you can use the +`clj-nrepl-eval` command (if installed via bbin) to evaluate code +against an nREPL server. This means the state of the REPL session +will persist between evaluations. + +You can require or load a file in one evaluation of the command and +when you call the command again the namespace will still be available. + +## Example uses + +You can evaluate clojure code to check if a file you just edited still compiles and loads. + +Whenever you require a namespace always use the `:reload` key. + +## How to Use + +The following evaluates Clojure code via an nREPL connection. + +**Discover available nREPL servers:** +```bash +clj-nrepl-eval --discover-ports +``` + +**Evaluate code (requires --port):** +```bash +clj-nrepl-eval --port "" +``` + +## Options + +- `-p, --port PORT` - nREPL port (required) +- `-H, --host HOST` - nREPL host (default: 127.0.0.1) +- `-t, --timeout MILLISECONDS` - Timeout in milliseconds (default: 120000) +- `-r, --reset-session` - Reset the persistent nREPL session +- `-c, --connected-ports` - List previously connected nREPL sessions +- `-d, --discover-ports` - Discover nREPL servers in current directory +- `-h, --help` - Show help message + +## Workflow + +**1. Discover nREPL servers in current directory:** +```bash +clj-nrepl-eval --discover-ports +# Discovered nREPL servers: +# +# In current directory (/path/to/project): +# localhost:7888 (clj) +# localhost:7889 (bb) +# +# Total: 2 servers +``` + +**2. Check previously connected sessions (optional):** +```bash +clj-nrepl-eval --connected-ports +# Active nREPL connections: +# 127.0.0.1:7888 (clj) (session: abc123...) +# +# Total: 1 active connection +``` + +**3. Evaluate code:** +```bash +clj-nrepl-eval -p 7888 "(+ 1 2 3)" +``` + +## Examples + +**Discover servers:** +```bash +clj-nrepl-eval --discover-ports +``` + +**Basic evaluation:** +```bash +clj-nrepl-eval -p 7888 "(+ 1 2 3)" +``` + +**With timeout:** +```bash +clj-nrepl-eval -p 7888 --timeout 5000 "(Thread/sleep 10000)" +``` + +**Multiple expressions:** +```bash +clj-nrepl-eval -p 7888 "(def x 10) (* x 2) (+ x 5)" +``` + +**Reset session:** +```bash +clj-nrepl-eval -p 7888 --reset-session +``` + +## Features + +- **Server discovery** - Use --discover-ports to find all nREPL servers (Clojure, Babashka, shadow-cljs, etc.) in current directory +- **Session tracking** - Use --connected-ports to see previously connected sessions +- **Automatic delimiter repair** - fixes missing/mismatched parens before evaluation +- **Timeout handling** - interrupts long-running evaluations +- **Persistent sessions** - State persists across invocations + diff --git a/.claude/skills/clojure-eval/SKILL.md b/.claude/skills/clojure-eval/SKILL.md new file mode 100644 index 00000000..36d58650 --- /dev/null +++ b/.claude/skills/clojure-eval/SKILL.md @@ -0,0 +1,174 @@ +--- +name: clojure-eval +description: Evaluate Clojure code via nREPL using clj-nrepl-eval. Use this when you need to test code, check if edited files compile, verify function behavior, or interact with a running REPL session. +--- + +# Clojure REPL Evaluation + +## When to Use This Skill + +Use this skill when you need to: +- **Verify that edited Clojure files compile and load correctly** +- Test function behavior interactively +- Check the current state of the REPL +- Debug code by evaluating expressions +- Require or load namespaces for testing +- Validate that code changes work before committing + +## How It Works + +The `clj-nrepl-eval` command evaluates Clojure code against an nREPL server. **Session state persists between evaluations**, so you can require a namespace in one evaluation and use it in subsequent calls. Each host:port combination maintains its own session file. + +## Instructions + +### 0. Discover and select nREPL server + +First, discover what nREPL servers are running in the current directory: + +```bash +clj-nrepl-eval --discover-ports +``` + +This will show all nREPL servers (Clojure, Babashka, shadow-cljs, etc.) running in the current project directory. + +**Then use the AskUserQuestion tool:** + +- **If ports are discovered:** Prompt user to select which nREPL port to use: + - **question:** "Which nREPL port would you like to use?" + - **header:** "nREPL Port" + - **options:** Present each discovered port as an option with: + - **label:** The port number + - **description:** The server type and status (e.g., "Clojure nREPL server in current directory") + - Include up to 4 discovered ports as options + - The user can select "Other" to enter a custom port number + +- **If no ports are discovered:** Prompt user how to start an nREPL server: + - **question:** "No nREPL servers found. How would you like to start one?" + - **header:** "Start nREPL" + - **options:** + - **label:** "deps.edn alias", **description:** "Find and use an nREPL alias in deps.edn" + - **label:** "Leiningen", **description:** "Start nREPL using 'lein repl'" + - The user can select "Other" for alternative methods or if they already have a server running on a specific port + +IMPORTANT: IF you start a REPL do not supply a port let the nREPL start and return the port that it was started on. + +### 1. Evaluate Clojure Code + +> Evaluation automatically connects to the given port + +Use the `-p` flag to specify the port and pass your Clojure code. + +**Recommended: Pass code as a command-line argument:** +```bash +clj-nrepl-eval -p "(+ 1 2 3)" +``` + +**For multiple expressions (single line):** +```bash +clj-nrepl-eval -p "(def x 10) (+ x 20)" +``` + +**Alternative: Using heredoc (may require permission approval for multiline commands):** +```bash +clj-nrepl-eval -p <<'EOF' +(def x 10) +(+ x 20) +EOF +``` + +**Alternative: Via stdin pipe:** +```bash +echo "(+ 1 2 3)" | clj-nrepl-eval -p +``` + +### 2. Display nREPL Sessions + +**Discover all nREPL servers in current directory:** +```bash +clj-nrepl-eval --discover-ports +``` +Shows all running nREPL servers in the current project directory, including their type (clj/bb/basilisp) and whether they match the current working directory. + +**Check previously connected sessions:** +```bash +clj-nrepl-eval --connected-ports +``` +Shows only connections you have made before (appears after first evaluation on a port). + +### 3. Common Patterns + +**Require a namespace (always use :reload to pick up changes):** +```bash +clj-nrepl-eval -p "(require '[my.namespace :as ns] :reload)" +``` + +**Test a function after requiring:** +```bash +clj-nrepl-eval -p "(ns/my-function arg1 arg2)" +``` + +**Check if a file compiles:** +```bash +clj-nrepl-eval -p "(require 'my.namespace :reload)" +``` + +**Multiple expressions:** +```bash +clj-nrepl-eval -p "(def x 10) (* x 2) (+ x 5)" +``` + +**Complex multiline code (using heredoc):** +```bash +clj-nrepl-eval -p <<'EOF' +(def x 10) +(* x 2) +(+ x 5) +EOF +``` +*Note: Heredoc syntax may require permission approval.* + +**With custom timeout (in milliseconds):** +```bash +clj-nrepl-eval -p --timeout 5000 "(long-running-fn)" +``` + +**Reset the session (clears all state):** +```bash +clj-nrepl-eval -p --reset-session +clj-nrepl-eval -p --reset-session "(def x 1)" +``` + +## Available Options + +- `-p, --port PORT` - nREPL port (required) +- `-H, --host HOST` - nREPL host (default: 127.0.0.1) +- `-t, --timeout MILLISECONDS` - Timeout (default: 120000 = 2 minutes) +- `-r, --reset-session` - Reset the persistent nREPL session +- `-c, --connected-ports` - List previously connected nREPL sessions +- `-d, --discover-ports` - Discover nREPL servers in current directory +- `-h, --help` - Show help message + +## Important Notes + +- **Prefer command-line arguments:** Pass code as quoted strings: `clj-nrepl-eval -p "(+ 1 2 3)"` - works with existing permissions +- **Heredoc for complex code:** Use heredoc (`<<'EOF' ... EOF`) for truly multiline code, but note it may require permission approval +- **Sessions persist:** State (vars, namespaces, loaded libraries) persists across invocations until the nREPL server restarts or `--reset-session` is used +- **Automatic delimiter repair:** The tool automatically repairs missing or mismatched parentheses +- **Always use :reload:** When requiring namespaces, use `:reload` to pick up recent changes +- **Default timeout:** 2 minutes (120000ms) - increase for long-running operations +- **Input precedence:** Command-line arguments take precedence over stdin + +## Typical Workflow + +1. Discover nREPL servers: `clj-nrepl-eval --discover-ports` +2. Use **AskUserQuestion** tool to prompt user to select a port +3. Require namespace: + ```bash + clj-nrepl-eval -p "(require '[my.ns :as ns] :reload)" + ``` +4. Test function: + ```bash + clj-nrepl-eval -p "(ns/my-fn ...)" + ``` +5. Iterate: Make changes, re-require with `:reload`, test again + diff --git a/.claude/skills/clojure-eval/examples.md b/.claude/skills/clojure-eval/examples.md new file mode 100644 index 00000000..003b5aeb --- /dev/null +++ b/.claude/skills/clojure-eval/examples.md @@ -0,0 +1,82 @@ +# clj-nrepl-eval Examples + +## Discovery + +```bash +clj-nrepl-eval --connected-ports +``` + +## Heredoc for Multiline Code + +```bash +clj-nrepl-eval -p 7888 <<'EOF' +(defn greet [name] + (str "Hello, " name "!")) + +(greet "Claude") +EOF +``` + +### Heredoc Simplifies String Escaping + +Heredoc avoids shell escaping issues with quotes, backslashes, and special characters: + +```bash +# With heredoc - no escaping needed +clj-nrepl-eval -p 7888 <<'EOF' +(def regex #"\\d{3}-\\d{4}") +(def message "She said \"Hello!\" and waved") +(def path "C:\\Users\\name\\file.txt") +(println message) +EOF + +# Without heredoc - requires complex escaping +clj-nrepl-eval -p 7888 "(def message \"She said \\\"Hello!\\\" and waved\")" +``` + +## Working with Project Namespaces + +```bash +# Test a function after requiring +clj-nrepl-eval -p 7888 <<'EOF' +(require '[clojure-mcp-light.delimiter-repair :as dr] :reload) +(dr/delimiter-error? "(defn foo [x]") +EOF +``` + +## Verify Compilation After Edit + +```bash +# If this returns nil, the file compiled successfully +clj-nrepl-eval -p 7888 "(require 'clojure-mcp-light.hook :reload)" +``` + +## Session Management + +```bash +# Reset session if state becomes corrupted +clj-nrepl-eval -p 7888 --reset-session +``` + +## Common Workflow Patterns + +### Load, Test, Iterate + +```bash +# After editing a file, reload and test in one command +clj-nrepl-eval -p 7888 <<'EOF' +(require '[my.namespace :as ns] :reload) +(ns/my-function test-data) +EOF +``` + +### Run Tests After Changes + +```bash +clj-nrepl-eval -p 7888 <<'EOF' +(require '[my.project.core :as core] :reload) +(require '[my.project.core-test :as test] :reload) +(clojure.test/run-tests 'my.project.core-test) +EOF +``` + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..089987ae --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,154 @@ +# Integreat Development Guide + +## Build & Run Commands + +### Build +```bash +lein build # Create uberjar +lein uberjar # Standalone JAR +npm install # Install Node.js dependencies (for frontend build) +``` + +### Development +```bash +docker-compose up -d # Start Datomic, Solr services +lein repl # Start Clojure REPL (nREPL on port 9000), typically one will be running for you already +lein cljfmt check # Check code formatting +lein cljfmt fix # Auto-format code +clj-paren-repair [FILE ...] # fix parentheses in files +clj-nrepl-eval -p PORT "(+ 1 2 3)" # evaluate clojure code + +``` + +Often times, if a file won't compile, first clj-paren-repair on the file, then try again. If it doesn't wor still, try cljfmt check. + +### Running the Application +```bash +INTEGREAT_JOB="" lein run # Default: port 3000 +# Or with custom port: +PORT=3449 lein run +``` + +## Test Execution +prefer using clojure-eval skill + +### Run All Tests +```bash +lein test # WORST CASE +``` + +### Run Specific Test Selectors +```bash +lein test :integration # WORST CASE +lein test :functional #WORST CASE +``` + +### Run Specific Test File +```bash +clj-nrepl-eval -p PORT "(clojure.test/run-tests 'auto-ap.import.plaid-test)" # preferred method +lein test auto-ap.import.plaid-test #WORST CASE +``` + +### Run Specific Test Function +```bash +lein test auto-ap.import.plaid-test/plaid->transaction #WORST CASE +``` + +## Code Style Guidelines + +### File Organization +- Namespaces follow `auto-ap.*` format +- Source files in `src/clj/auto_ap/` +- Test files in `test/clj/auto_ap/` +- Backend: Clojure 1.10.1 +- Frontend: alpinejs + TailwindCSS + HTMX + +### Import Formatting +- Imports must be sorted alphabetically within each group +- Standard library imports first (`clojure.core`, `clojure.string`, etc.) +- Third-party libraries second +- Internal library imports last +- Group related imports together + +**Example:** +```clojure +(ns auto-ap.example + (:require + [auto-ap.datomic :refer [conn pull-many]] + [auto-ap.graphql.utils :refer [limited-clients]] + [clojure.data.json :as json] + [clojure.string :as str] + [com.brunobonacci.mulog :as mu] + [datomic.api :as dc])) +``` + +### Naming Conventions +- Namespace names: `auto-ap..` +- File names: kebab-case (e.g., `ledger_common.clj`) +- Functions: lowercase with underscores, descriptive (e.g., `fetch-ids`, `process-invoice`) +- Database identifiers: use `:db/id` keyword, not string IDs +- Entity attributes: follow schema convention (e.g., `:invoice/status`) + +### Data Types & Schema +- Use keywords for schema accessors (e.g., `:invoice/date` instead of strings) +- Datomic pull queries use `[:db/id ...]` syntax +- Use `:db/ident` for schema keyword resolution +- Dates and times: use `inst` types (handled by `clj-time.coerce`) +- Numbers: use `double` for monetary values (stored as strings in DB, converted to double) + +### Error Handling +- Use `slingshot.slingshot` library for error handling +- Pattern: `(exception->notification #(throw ...))` to wrap errors with logging +- Pattern: `(notify-if-locked client-id invoice-date)` for business logic checks +- Throw `ex-info` with metadata for structured exceptions +- Use `throw+` for throwing exceptions that can be caught by `slingshot` + +**Example:** +```clojure +(exception->notification + (when-not (= :invoice-status/unpaid (:invoice/status invoice)) + (throw (ex-info "Cannot void an invoice if it is paid." + {:type :notification})))) +``` + +### Function Definitions +- Use `defn` for public functions +- Use `defn-` for private functions (indicated by underscore prefix) +- Use `letfn` for locally recursive functions +- Use `cond` and `condp` for conditional logic +- Use `case` for exhaustive keyword matching + +### Testing Patterns +- Use `clojure.test` with `deftest` and `testing` +- Group related tests in `testing` blocks +- Use `is` for assertions with descriptive messages +- Use `are` for parameterized assertions +- Fixtures with `use-fixtures` for setup/teardown + +**Example:** +```clojure +(deftest import-invoices + (testing "Should import valid invoice" + (is (= 1 (invoice-count-for-client [:client/code "ABC"])))) + (testing "Should not import duplicate invoice" + (is (thrown? Exception (sut/import-uploaded-invoice user invoices))))) +``` +look at the testing-conventions skill for more detail + +### Code Formatting & Documentation +- Use `lein cljfmt check` before committing, auto-fix with `lein cljfmt fix` +- Use `#_` for commenting out entire forms (not single lines) +- Use `comment` special form for evaluation examples +- Use `#_` to comment out library dependencies in project.clj + +### Logging +- Uses `com.brunobonacci.mulog` for structured logging +- Import with `(require [auto-ap.logging :as alog])` +- Use `alog/info`, `alog/warn`, `alog/error` for different log levels +- Use `alog/with-context-as` to add context to log messages + +## Linting Configuration +- Uses `clj-kondo` for static analysis +- Custom linters configured in `.clj-kondo/config.edn` +- Hooks for `mount.core/defstate` and `auto-ap.logging` in `.clj-kondo/hooks/` +- Run `clj-kondo --lint src/clj auto_ap/` for linting diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..6c3a0040 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,123 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Integreat is a full-stack web application for accounts payable (AP) and accounting automation. It integrates with multiple financial data sources (Plaid, Yodlee, Square, Intuit QBO) to manage invoices, bank accounts, transactions, and vendor information. + +**Tech Stack:** +- Backend: Clojure 1.10.1 with Ring (Jetty), Datomic database, GraphQL (Lacinia, in process of depracation) +- Frontend, two versions: + * ClojureScript with Reagent, Re-frame, (almost depracated) + * Server side: HTMX, TailwindCSS, alpinejs (current) +- Java: Amazon Corretto 11 (required for Clojure 1.10.1) + +## Development Commands + +**Build:** +```bash +lein build # Create uberjar +lein uberjar # Standalone JAR +npm install # Install Node.js dependencies (for frontend build) +``` + +**Development:** +```bash +lein build-dev # Figwheel dev build (hot reload for CLJS) +docker-compose up -d # Start Datomic, Solr services +lein repl # Start Clojure REPL (nREPL on port 9000) +lein test # Run all tests +lein test :integration # Run integration tests only +lein test :functional # Run functional tests only +lein cljfmt check # Check code formatting +lein cljfmt fix # Auto-format code +``` + +**Running the Application:** + +**As Web Server:** +```bash +INTEGREAT_JOB="" lein run # Default: port 3000 +# Or with custom port: +PORT=3449 lein run +``` + +**As Background Job:** +Set `INTEGREAT_JOB` environment variable to one of: +- `square-import-job` - Square POS transaction sync +- `yodlee2` - Yodlee bank account sync +- `plaid` - Plaid bank linking +- `intuit` - Intuit QBO sync +- `import-uploaded-invoices` - Process uploaded invoice PDFs +- `ezcater-upsert` - EZcater PO sync +- `ledger_reconcile` - Ledger reconciliation +- `bulk_journal_import` - Journal entry import +- (no job) - Run web server + nREPL + +## Architecture + +**Request Flow:** +1. Ring middleware pipeline processes requests +2. Authentication/authorization middleware (Buddy) wraps handlers +3. Bidi routes dispatch to handlers +4. SSR (server-side rendering) generates HTML with Hiccup for main views +5. For interactive pages, HTMX handles partial updates +6. Client-side uses Re-frame for state management + +**Multi-tenancy:** +- Client-based filtering via `:client/code` and `:client/groups` +- Client selection via `X-Clients` header or session +- Role-based permissions: admin, standard user, vendor + +**Key Directories:** +- `src/clj/auto_ap/` - Backend Clojure code +- `src/clj/auto_ap/server.clj` - Main entry point, job dispatcher, Mount lifecycle +- `src/clj/auto_ap/handler.clj` - Ring app, middleware stack +- `src/clj/auto_ap/datomic/` - Datomic schema and queries +- `src/clj/auto_ap/ssr/` - Server-side rendered page handlers (Hiccup templates) +- `src/clj/auto_ap/routes/` - HTTP route definitions +- `src/clj/auto_ap/jobs/` - Background batch jobs +- `src/clj/auto_ap/graphql/` - GraphQL type definitions and resolvers +- `src/cljs/auto_ap/` - Frontend ClojureScript for old, depracated version +- `test/clj/auto_ap/` - Unit/integration tests + +## Database + +- Datomic schema defined in `resources/schema.edn` +- Key entity patterns: + - `:client/code`, `:client/groups` for multi-tenancy + - `:vendor/*`, `:invoice/*`, `:transaction/*`, `:account/*` for standard entities + - `:db/type/ref` for relationships, many with `:db/cardinality :db.cardinality/many` + +## Configuration + +- Dev config: `config/dev.edn` (set via `-Dconfig=config/dev.edn`) +- Env vars: `INTEGREAT_JOB`, `PORT` +- Docker: Uses Alpine-based Amazon Corretto 11 image + +## Important Patterns + +- **Middleware stack** in `handler.clj`: route matching → logging → client hydration → session/auth → idle timeout → error handling → gzip +- **Client context** added by middleware: `:identity`, `:clients`, `:client`, `:matched-route` +- **Job dispatching** in `server.clj`: checks `INTEGREAT_JOB` env var to run specific background jobs or start web server +- **Test selectors**: namespaces ending in `integration` or `functional` are selected by `lein test :integration` / `lein test :functional` + +## Clojure REPL Evaluation + +The command `clj-nrepl-eval` is installed on your path for evaluating Clojure code via nREPL. + +**Discover nREPL servers:** + +`clj-nrepl-eval --discover-ports` + +**Evaluate code:** + +`clj-nrepl-eval -p ""` + +With timeout (milliseconds) + +`clj-nrepl-eval -p --timeout 5000 ""` + +The REPL session persists between evaluations - namespaces and state are maintained. +Always use `:reload` when requiring namespaces to pick up changes.