Imported from GustavEkberg/abraxxas (
AGENTS.md). Install upstream withnpx skills add GustavEkberg/abraxxas. Copyright stays with the author.
PROJECT KNOWLEDGE BASE
Generated: 2026-01-17 Commit: 1753789 Branch: main
OVERVIEW
Next.js 16 App Router application with Effect-TS service architecture, Drizzle ORM (PostgreSQL/Neon), better-auth authentication, nuqs URL state management, and Tailwind CSS 4.
CRITICAL RULES
- Use
pnpmexclusively - not npm or yarn - Run
pnpm tscbefore finishing - ensure types pass - Run
pnpm lintto check for errors - fix any issues - Run
pnpm test:runto verify tests pass - fix failures before committing
Effect-TS Rules (Enforced by ESLint)
| Rule | Description |
|---|---|
local/no-disable-validation |
NEVER use { disableValidation: true } |
local/no-catch-all-cause |
NEVER use Effect.catchAllCause - catches defects |
local/no-schema-from-self |
NEVER use *FromSelf schemas (use standard variants) |
local/no-schema-decode-sync |
NEVER use sync decode/encode (throws exceptions) |
local/prefer-option-from-nullable |
Use Option.fromNullable() instead of ternary |
@typescript-eslint/no-explicit-any |
NEVER use any type |
@typescript-eslint/consistent-type-assertions |
NEVER use as type casts |
See specs/EFFECT_BEST_PRACTICES.md for detailed explanations and alternatives.
SPECIFICATIONS
Before implementing any feature, consult specs/README.md.
- Specs describe intent; code describes reality. Check the codebase first before assuming something is/isn't implemented.
- Use specs as guidance. Follow patterns, types, and architecture defined in relevant specs.
STRUCTURE
init/
├── app/ # Next.js App Router pages
│ ├── (auth)/ # Auth route group (login, OTP, logout)
│ ├── (dashboard)/ # Empty - future dashboard
│ └── api/ # API routes (auth catch-all, example)
├── components/ui/ # Modified shadcn/ui + custom components (see AGENTS.md)
├── lib/
│ ├── services/ # Effect-TS service layer (see AGENTS.md)
│ ├── core/ # Domain logic (each subfolder has own errors)
│ ├── next-effect/ # Effect-TS/Next.js adapter
│ ├── schemas/ # Validation schemas
│ ├── layers.ts # AppLayer composition
│ └── utils.ts # Utilities (cn helper)
├── instrumentation.ts # Server-side Sentry + OTel
└── instrumentation-client.ts # Client-side PostHog + Sentry
WHERE TO LOOK
| Task | Location | Notes |
|---|---|---|
| Add server action | lib/core/[domain]/*-action.ts |
One action per file, see DATA_ACCESS spec |
| Add domain function | lib/core/[domain]/*.ts |
Pure Effect functions for business logic |
| Add new service | lib/services/[name]/ |
Follow lib/services/AGENTS.md pattern |
| Add dynamic page | app/*/page.tsx |
See PAGE_PATTERNS spec for Suspense pattern |
| Add API route | app/api/[route]/route.ts |
Only for webhooks/external APIs |
| Add UI component | components/ui/ |
Uses Base UI, not Radix |
| Add tests | lib/core/[domain]/*.test.ts |
Colocated with source, use @effect/vitest |
| Database schema | lib/services/db/schema.ts |
Drizzle ORM |
| Auth flow | app/(auth)/ |
better-auth + OTP email |
| Service dependencies | lib/layers.ts |
AppLayer merges all services |
| Error types | lib/core/errors/index.ts |
Shared domain errors |
| File uploads | lib/core/file/*-action.ts |
S3 signed URLs pattern |
| URL state (filters) | app/*/search-params.ts |
nuqs/server imports only, see NUQS spec |
| Project management | lib/core/project/ |
CRUD actions and domain functions |
| Task management | lib/core/task/ |
CRUD actions, execution, ownership checks |
| Comment management | lib/core/comment/ |
User and agent comments |
| Session tracking | lib/core/session/ |
OpenCode session lifecycle management |
| Token encryption | lib/core/crypto/ |
AES-256-GCM encryption for GitHub tokens |
| Manifest management | lib/core/manifest/ |
Sprite-based PRD execution lifecycle |
| Manifest components | components/manifest/ |
ManifestCard, CreateManifestDialog |
| Manifest webhook | app/api/webhooks/manifest/ |
HMAC-signed webhook from manifest sprites |
CODE MAP
| Symbol | Type | Location | Role |
|---|---|---|---|
AppLayer |
Layer | lib/layers.ts:10 |
Merged service layer for Effect pipelines |
NextEffect.runPromise |
Function | lib/next-effect/index.ts |
Handles redirects outside Effect context |
Auth |
Service | lib/services/auth/live-layer.ts |
Authentication (sign in/up/out, sessions) |
Db |
Service | lib/services/db/live-layer.ts |
Database (returns Drizzle client) |
Email |
Service | lib/services/email/live-layer.ts |
Resend email sending |
S3 |
Service | lib/services/s3/live-layer.ts |
AWS S3 file operations |
Sprites |
Service | lib/services/sprites/live-layer.ts |
Sprites.dev API (create, exec, destroy) |
Telegram |
Service | lib/services/telegram/live-layer.ts |
Telegram bot notifications |
Activity |
Service | lib/services/activity/live-layer.ts |
Activity logging via Telegram |
encryptToken |
Function | lib/core/crypto/encrypt.ts |
AES-256-GCM encryption for sensitive tokens |
decryptToken |
Function | lib/core/crypto/encrypt.ts |
AES-256-GCM decryption for sensitive tokens |
createAgentComment |
Function | lib/core/comment/create-agent-comment.ts |
Internal Effect function for agent comments |
getLatestSession |
Function | lib/core/session/get-latest-session.ts |
Fetch most recent session by taskId |
CONVENTIONS
Code Style (Prettier)
- No semicolons
- No trailing commas
- Single quotes, 2-space indent, max 100 chars
File Naming
- All files use kebab-case -
search-params.ts,post-list.tsx,live-layer.ts - Server actions end in
-action.ts-delete-post-action.ts - URL state definitions -
search-params.tsin the route directory
Effect-TS Service Pattern
// Services use static layer/Live properties for v4 compatibility
export class ServiceName extends Effect.Service<ServiceName>()('@app/ServiceName', {
effect: Effect.gen(function* () {
/* ... */
})
}) {
static layer = this.Default
static Live = this.layer.pipe(Layer.provide(ConfigLive))
}
Configuration
- Always use
Config.string('VAR')orConfig.redacted('SECRET') - Never use
process.envdirectly with throws
Required Environment Variables:
| Variable | Type | Purpose |
|---|---|---|
SPRITES_TOKEN |
secret | Sprites.dev API authentication token |
ENCRYPTION_KEY |
secret | AES-256-GCM key for GitHub token encryption |
SPRITE_TIMEOUT_MS |
string | Optional timeout for sprite operations (ms) |
OPENCODE_SETUP_REPO_URL |
string | GitHub repo URL for opencode commands/skills |
Observability
- All service methods:
Effect.withSpan('Service.method') - Error logging:
Effect.tapError() - Span attributes:
Effect.annotateCurrentSpan()
Imports
- Use
@/path alias for project imports - No barrel files - import directly from source files
- Import services from
live-layer.tsdirectly
ANTI-PATTERNS (THIS PROJECT)
| Pattern | Correct Approach |
|---|---|
| API routes for CRUD operations | Server actions (lib/core/[domain]/*-action.ts) |
| Streaming files through server | S3 signed URLs (client uploads directly to S3) |
process.env.X with throws |
yield* Config.string('X') |
router.push() for logout |
window.location.href = '/' (layout cache issue) |
Barrel files (index.ts re-exports) |
Import from live-layer.ts directly |
Effect.runPromise() in pages |
NextEffect.runPromise() (handles redirects) |
Layer dependencies option |
Layer.provide() externally (v4 compat) |
| Multiple services per directory | One service per directory |
| Multiple actions per file | One action per file ending in -action.ts |
useState for shareable UI state |
nuqs URL state (app/*/search-params.ts) |
Import parseAs* from nuqs |
Import from nuqs/server in search-params.ts |
| Direct data fetch in page component | Suspense + Content pattern (see PAGE_PATTERNS spec) |
| Nested Suspense with async components | Single Content component fetches all data |
Missing export const dynamic |
Add export const dynamic = 'force-dynamic' for auth |
UNIQUE STYLES
Next.js + Effect Integration
Pages use NextEffect.runPromise() which catches RedirectError and calls redirect() outside the Effect context. This is required because Next.js redirects must be called outside try-catch.
UI Components
Uses Base UI (@base-ui/react) primitives instead of Radix UI. Components are shadcn-styled but built on a different foundation. See components/ui/AGENTS.md.
Service Dependency Hierarchy
AppLayer
├── Auth.Live → Email.Live
├── Db.Live
├── S3.Live
├── Sprites.Live
├── Telegram.Live
├── Activity.Live → Telegram.Live
└── TelemetryLayer
Data Access Patterns
See specs/DATA_ACCESS_PATTERNS.md for full details. Summary:
| Operation | Pattern | Location |
|---|---|---|
| Read data for pages | RSC | app/*/page.tsx |
| Create/Update/Delete | Server Action | lib/core/[domain]/*-action.ts |
| File upload | S3 signed URL | lib/core/file/get-upload-url-action.ts |
| File download | S3 signed URL | lib/core/file/get-download-url-action.ts |
| External webhooks | API Route | app/api/webhooks/*/route.ts |
Server Action Pattern:
// lib/core/post/delete-post-action.ts
'use server'
export const deletePostAction = async (postId: Post['id']) => {
return await NextEffect.runPromise(
Effect.gen(function* () {
const session = yield* getSession()
yield* deletePost(postId)
}).pipe(
Effect.withSpan('action.post.delete'),
Effect.provide(AppLayer),
Effect.scoped,
Effect.matchEffect({
onFailure: error => /* handle errors */,
onSuccess: () => Effect.sync(() => revalidatePath('/posts'))
})
)
)
}
NOTES
- No CI/CD configured - deployment via Vercel auto-deploy
- React Compiler enabled - automatic memoization (experimental)
- PostHog proxied - requests via
/ph/*rewrites to bypass ad-blockers - Drizzle beta - using
1.0.0-beta.11, may have breaking changes - Effect v4 migration: services designed for easy
Effect.Service→ServiceMap.Servicetransition
SUBDIRECTORY DOCS
lib/services/AGENTS.md- Effect-TS service architecture patternscomponents/ui/AGENTS.md- UI component patterns and customizations