Files
illusory-mapp/AGENTS.md

8.2 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; So don't reach for it primarily.
  3. Use ERROR_CODES for consistent error codes.
  4. 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).
  5. Domain errors — Each domain has an errors.ts that exports error constructors built with getError. Use these instead of ad-hoc error objects.
  6. 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;
    // Flexible, but more "defined base fields" in the future
};

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 work and async jobs. Its structure is still evolving and it is to be updated soon.

When logic is added, processing logic should live under src/domains/<domain>/ and call into @pkg/logic controllers and repositories.


6. Observability

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

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)