# 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

## Pull Requests on Gitea

This project uses **gitea story-basking.ts net** as the primary remote for PRs. Use `tea` (the Gitea CLI) to create and manage pull requests. The gitea remote is the one you push to, NOT origin and NOT deploy.

**When opening a PR**, load and follow the **gitea-tea** skill. In short:
- Target branch is always `master`
- Use `tea pulls create -r notid/integreat -b master --title "..." --description "..."`

### 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
