This commit is contained in:
2026-01-31 20:58:33 -08:00
parent 3489fb19d5
commit 310906f2ae
5 changed files with 638 additions and 0 deletions

View File

@@ -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 <port> "<clojure-code>"
```
## 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

View File

@@ -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 <PORT> "(+ 1 2 3)"
```
**For multiple expressions (single line):**
```bash
clj-nrepl-eval -p <PORT> "(def x 10) (+ x 20)"
```
**Alternative: Using heredoc (may require permission approval for multiline commands):**
```bash
clj-nrepl-eval -p <PORT> <<'EOF'
(def x 10)
(+ x 20)
EOF
```
**Alternative: Via stdin pipe:**
```bash
echo "(+ 1 2 3)" | clj-nrepl-eval -p <PORT>
```
### 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 <PORT> "(require '[my.namespace :as ns] :reload)"
```
**Test a function after requiring:**
```bash
clj-nrepl-eval -p <PORT> "(ns/my-function arg1 arg2)"
```
**Check if a file compiles:**
```bash
clj-nrepl-eval -p <PORT> "(require 'my.namespace :reload)"
```
**Multiple expressions:**
```bash
clj-nrepl-eval -p <PORT> "(def x 10) (* x 2) (+ x 5)"
```
**Complex multiline code (using heredoc):**
```bash
clj-nrepl-eval -p <PORT> <<'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 <PORT> --timeout 5000 "(long-running-fn)"
```
**Reset the session (clears all state):**
```bash
clj-nrepl-eval -p <PORT> --reset-session
clj-nrepl-eval -p <PORT> --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 <PORT> "(+ 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 <PORT> "(require '[my.ns :as ns] :reload)"
```
4. Test function:
```bash
clj-nrepl-eval -p <PORT> "(ns/my-fn ...)"
```
5. Iterate: Make changes, re-require with `:reload`, test again

View File

@@ -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
```

154
AGENTS.md Normal file
View File

@@ -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.<feature>.<sub-feature>`
- 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

123
CLAUDE.md Normal file
View File

@@ -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 <port> "<clojure-code>"`
With timeout (milliseconds)
`clj-nrepl-eval -p <port> --timeout 5000 "<clojure-code>"`
The REPL session persists between evaluations - namespaces and state are maintained.
Always use `:reload` when requiring namespaces to pick up changes.