Files
illusory-mapp/AGENTS.md

8.7 KiB

AGENTS.md

This document defines the laws, principles, and rule sets that govern this codebase. Any agent or developer making changes has to adhere to these rules.

Before starting off — Read the README.md file to understand the project's goals and objectives.


Agent Rules (Override Everything)

  1. No testing by yourself — All testing is done by the team.
  2. No assumptions about code or domain logic — Always confirm and be sure before making changes.
  3. No running scripts — Do not run build, dev, test, or migrate scripts unless explicitly approved.

More rules are only to be added by the human, in case such a suggestion becomes viable.


1. Project Overview

  • Monorepo: Turbo repo
  • Package Manager: pnpm
  • Language: TypeScript everywhere
  • Node: >= 24

Applications

App Purpose
apps/main SvelteKit UI — the primary user-facing application
apps/processor Hono web server — intended for asynchronous processing (jobs, workers). Currently minimal; structure is evolving.

Packages

Packages live under packages/ and are standalone, modular pieces consumed by apps:

Package Purpose
@pkg/logic Domain logic (DDD, controllers, repositories)
@pkg/db Drizzle schema, database access
@pkg/logger Logging, getError for error construction
@pkg/result Result type, ERROR_CODES, tryCatch
@pkg/keystore Redis instance (sessions, 2FA, etc.)
@pkg/settings App settings / env config

Data Stores

  • PostgreSQL (Drizzle) — Primary relational data (auth, users, accounts, etc.)
  • Redis (Valkey) — Sessions, 2FA verification state (via @pkg/keystore)

Additional stores (NoSQL DBs, R2, etc.) may be introduced later. Follow existing domain patterns when adding new data access.


2. The Logic Package: DDD + Layered Architecture

The @pkg/logic package contains all domain logic. It follows:

  1. Domain-Driven Design (DDD) — Bounded contexts as domains
  2. Layered architecture — Clear separation of concerns
  3. Result-style error handling — Errors as values; avoid try-catch in domain code

Domain Structure

Each domain is a folder under packages/logic/domains/:

domains/
  <domain-name>/
    data.ts        # Types, schemas (Valibot)
    repository.ts  # Data access
    controller.ts  # Use cases / application logic
    errors.ts      # Domain-specific error constructors (using getError)

The logic package is pure domain logic — no HTTP routes or routers. API exposure is handled by the main app via SvelteKit remote functions. Auth uses config.base.ts with better-auth. Add new domains as needed; mirror existing patterns.

Path Aliases (logic package)

  • @/*./*
  • @domains/*./domains/*
  • @core/*./core/*

Flow Execution Context

Domain operations receive a FlowExecCtx (fctx) for tracing and audit:

type FlowExecCtx = {
    flowId: string;
    userId?: string;
    sessionId?: string;
};

3. Error Handling: Result Pattern (neverthrow)

Errors are values, not exceptions. The codebase uses Result-style handling.

Current Conventions

  1. Logic package — Uses neverthrow (ResultAsync, okAsync, errAsync) for async operations that may fail.
  2. @pkg/result — Provides Result<T, Err>, ERROR_CODES, and tryCatch(). The Result type is legacy; the project may move toward Effect. Use ERROR_CODES for consistent error codes.
  3. getError() — From @pkg/logger. Use at boundaries when converting a thrown error to an Err object:
    return getError({ code: ERROR_CODES.XXX, message: "...", description: "...", detail: "..." }, e).
  4. Domain errors — Each domain has an errors.ts that exports error constructors built with getError. Use these instead of ad-hoc error objects.
  5. Check before use — Always check result.isOk() / result.isErr() before using result.value; never assume success.

Err Shape

type Err = {
    flowId?: string;
    code: string;
    message: string;
    description: string;
    detail: string;
    actionable?: boolean;
    error?: any;
};

4. Frontend (Main App)

The main app is a SvelteKit application with a domain-driven UI structure.

Structure

  • Routes: File-based routing under src/routes/. Layout groups (e.g. (main), auth) wrap related pages.
  • Domain-driven UI: Feature code lives under src/lib/domains/<domain>/ — each domain has its own folder with view models, components, and remote functions.
  • View Model (VM) pattern: Domain logic and state for a screen live in *.vm.svelte.ts classes. VMs hold reactive state ($state), orchestrate remote function calls, and expose methods. Pages import and use a VM instance.

SvelteKit Remote Functions

The main app uses SvelteKit remote functions as the primary API layer — replacing Hono routers in the logic package. Each domain has a *.remote.ts file that exposes query (reads) and command (writes) functions, called directly from VMs. Auth context and FlowExecCtx are built inside each remote function from event.locals via helpers in $lib/core/server.utils.

Naming convention: *SQ for queries, *SC for commands.

Global Stores

Shared state (user, session, breadcrumbs) lives in $lib/global.stores.ts.

Conventions

  • Pages are thin: they mount a VM, render components, and wire up lifecycle.
  • VMs own async flows, polling, and error handling for their domain.
  • VMs call remote functions directly; remote functions invoke logic controllers.
  • UI components under $lib/components/ are shared; domain-specific components live in $lib/domains/<domain>/.

5. Processor App

The processor is a Hono server intended for background workers and async jobs. Its structure is still evolving.

Current State

  • Minimal Hono server; no BullMQ or job processors yet.
  • When workers are added, processing logic should live under src/domains/<domain>/ and call into @pkg/logic controllers and repositories.
  • HTTP routes will expose internal APIs (e.g. task status, webhooks), secured (e.g. API key), for use by the main app or external services.
  • Async work should be triggered by calling the processor HTTP API, not by importing job queues in the main app or logic package.

Conventions (when implemented)

  • The worker processes jobs; do not block it with long-running HTTP logic.
  • Job payloads should be validated before processing.
  • Follow existing domain patterns for controllers and repositories.

6. Observability

The stack uses OpenTelemetry end-to-end for traces, logs, and metrics, shipped to a local SigNoz instance (via OTel Collector) in development.

How it fits together

  • apps/main bootstraps the OTel SDK in instrumentation.server.ts (auto-instrumentation via @opentelemetry/sdk-node). SvelteKit's tracing and instrumentation experimental flags wire this into the request lifecycle.
  • @pkg/logger ships Winston logs to OTel via OpenTelemetryTransportV3 — logs are correlated with active traces automatically.
  • @pkg/logic/core/observability.ts provides two tracing helpers for domain code:
    • traceResultAsync — wraps a ResultAsync operation in an OTel span. Use this in repositories and controllers.
    • withFlowSpan — lower-level span wrapper for non-Result async code.
  • Both helpers accept fctx and stamp spans with flow.id, flow.user_id, and flow.session_id for end-to-end trace correlation.

Convention

When adding new domain operations in repositories or controllers, wrap them with traceResultAsync. Keep span names consistent and descriptive (e.g. "user.getUserInfo"). Do not add ad-hoc spans outside these helpers.


7. Validation & Schemas

  • Valibot is used for schema validation in the logic package and in remote function input.
  • Domain data types are defined in data.ts per domain.
  • Use v.InferOutput<typeof Schema> for TypeScript types.
  • Remote functions pass Valibot schemas to query() and command() for input validation.

8. Package Naming

  • Apps: @apps/* (e.g. @apps/main, @apps/processor)
  • Packages: @pkg/* (e.g. @pkg/logic, @pkg/db, @pkg/logger)