Skip to content

Studio Backend — Agent Context

Architecture

  • Runtime: Node.js 22.x, Express 5.x, TypeScript
  • Database: MongoDB (dual-DB: Studio + Rendarigo), Redis (caching + RBAC)
  • Realtime: Socket.IO (legacy, deprecated for new flows) + JaduSpine/Centrifugo (new)
  • Monitoring: Sentry (errors + logs)
  • Testing: Vitest, MongoMemoryServer (shared instance, unique DB per file)
  • Linting: Biome (format + lint), Lefthook (pre-commit + pre-push)
  • CI: GitHub Actions (test + coverage on PR, build check on staging→main)

Commands

npm run dev              # nodemon + ts-node (auto-reload)
npm run build            # tsc → dist/
npm test                 # vitest run (all tests, ~12s)
npx vitest run tests/path/to/file.test.ts  # single test file
npm run lint             # biome lint (check only)
npm run lint:staged      # biome lint staged files (auto-fix)
npm run format:staged    # biome format staged files (auto-fix)

Git Hooks (Lefthook)

pre-commit (runs on every commit): 1. biome-format-staged — auto-formats + re-stages 2. biome-lint-staged — blocks commit on lint errors 3. no-console-log — blocks console.log (only when CHECK_CONSOLE_LOG=1)

commit-msg: If changing python_eval/ or src/promptTemplates/, message must include PROMPTS_TESTED=true or PROMPTS_TESTED=skip.

pre-push: tsc build must pass.

Project Structure

docs/                             # Feature design + integration docs (see "Docs" below)
plans/                            # Implementation plans
src/
├── [feature]/                    # Feature modules (router, controller, service, validator, middleware)
├── models/                       # MongoDB models (singleton per collection)
├── shared/                       # Types, constants, helpers, middleware, errorTypes
├── thirdPartyServices/           # External API integrations (B2, ElevenLabs, Replicate, FalAI, etc.)
├── utilityServices/              # Standalone utility services
├── webhooks/                     # Webhook handlers for external service callbacks
├── logger.ts                     # Winston logger with Sentry transports
├── instrument.ts                 # Sentry SDK initialization
├── index.ts                      # App entry point
├── router.ts                     # Main router
├── mongoDBClient.ts              # MongoDB connection management
├── redisClient.ts                # Redis singleton
└── socketService.ts              # Socket.IO singleton (legacy)

Docs

  • Feature design notes and frontend integration guides live in docs/ (kebab-case filenames, e.g. docs/asset-gen-failed-auto-retry.md). Check there before re-deriving a design from code.
  • A source file that implements a documented design carries a @see docs/<file>.md pointer in its header comment.
  • Some source comments reference docs that only exist in the parent studio/ workspace (studio/docs/superpowers/specs/); if a docs/ path in a comment is missing here, that is where to look.

Logging System

Overview

Single Winston logger (src/logger.ts) with 3 transports:

Transport What it does Levels
Console Dev: colorized. Prod: JSON all
SentryLogsTransport Sends to Sentry Logs Explorer error, warn, info
SentryIssuesTransport Auto-creates Sentry Issues when err field present error, warn

How Sentry Issues are Created

The SentryIssuesTransport checks for err instanceof Error. If true, it calls Sentry.captureException() with the error object and forwards errorType as a Sentry tag.

  • logger.error(msg, { err }) → Sentry Issue with severity error
  • logger.warn(msg, { err }) → Sentry Issue with severity warning
  • No err field (or err is not an Error) → NO Sentry Issue, log only

Required Fields

Every logger.error() and logger.warn() call MUST include:

logger.error('Descriptive message', {
  err: error,                              // MUST be Error instance
  errorType: ErrorType.SomeSpecificError,  // MUST be from ErrorType
  // ...additional context
});

Rules

  1. err MUST be an Error instance — never a string, never a plain object
  2. errorType MUST come from src/shared/errorTypes.ts — autocomplete will guide you
  3. Do NOT use sentryTags — errorType is auto-forwarded as a Sentry tag by the transport
  4. Do NOT use console.log/warn/error — pre-commit hook blocks it
  5. For info-level logs, errorType is not required (they don't create Issues)
  6. Expected retries or fallbacks log at info, not warn — warn requires err and creates a Sentry Issue. Only the final failure is logged with err

ErrorType System

Error types live in src/shared/errorTypes.ts, grouped by domain:

Group Domain
GuruErrors Agentic guru, LLM chat, GoToVersion
MinimaticsErrors Minimatics pipeline
MiniStoryErrors MiniStory feature
AssetGenerationErrors Asset gen jobs, quality checks
WorkbenchErrors Workbench operations, sequences
ShotImageEditErrors Shot image edit guru
SketchErrors Sketch analysis
PoseMakerErrors PoseMaker
ThirdPartyErrors External services (B2, ElevenLabs, Replicate, FalAI, etc.)
DirectorialPreferenceErrors Directorial preferences
InfraErrors DB, Redis, sockets, unhandled exceptions
SharedErrors Auth, credits, validation, activities, configuration
JaduCutErrors JaduCut editor, voice generation
WebhookErrors Webhook signature/processing

To add a new error type: add it to the appropriate domain group. The merged ErrorType const at the bottom auto-includes all groups.

In dev, passing an unknown errorType string triggers a console.warn — this tells you to register it in the file.

Request Logging

src/shared/middleware/requestLogger.middleware.ts auto-logs every request on completion: - Attaches req.requestId (from x-request-id header or generated UUID) - Attaches req.log — child logger with { requestId, method, path } context - On response finish: logs statusCode + durationMs at appropriate level (5xx=error, 4xx=warn, else=info)

Use req.log in controllers/services when you have access to req for per-request context:

req.log.info('Processing payment', { orderId });

Log Levels

Level When to use
error Operation failed, requires attention. Usually paired with err field
warn Degraded state, retries, fallbacks. May or may not have err
info Significant business events (server started, job completed, config loaded)
debug Developer-only detail (not sent to Sentry, dev-only)

What Reaches Sentry in Production

Destination What gets there
Sentry Logs Explorer error + warn + info (structured log lines)
Sentry Issues Only error/warn WITH err field (actual exceptions)
Console/stdout Everything at or above LOG_LEVEL

debug logs never reach Sentry. In prod, LOG_LEVEL defaults to info.

Code Conventions

  • Feature-based folder structure: [feature].router.ts, [feature].controller.ts, [feature].service.ts, [feature].validator.ts
  • Models are singletons (one class per collection, open/closed principle)
  • camelCase for folders, files, classes, functions, variables (e.g. src/workbench/magicSelect/, not magic-select/)
  • Zod for request validation
  • Static service classes for business logic
  • Prefer Error subclasses over generic errors
  • For independent items in a batch, use Promise.allSettled so one failure doesn't sink the others
  • To cap how many async calls run at once (e.g. requests to an external service), use createConcurrencyLimiter from src/shared/concurrencyLimiter.ts instead of chunking into fixed-size batches or writing a new limiter
  • Prefer putting throwaway B2 files under a dedicated key prefix with a bucket lifecycle (expiry) rule, not the shared images/ prefix
  • Pre-commit: Biome format → Biome lint → no-console-log check
  • Pre-push: TypeScript build check

Biome Config Gotchas

  • lineWidth: 180 (not default 80)
  • noExplicitAny: off — any is allowed
  • noConsole: warn — use logger, not console
  • useNodejsImportProtocol: warn — use node:http not http
  • noUnusedImports / noUnusedVariables: warn — auto-fixable by lint:staged
  • organizeImports: on — imports auto-sorted on save/commit

Testing

  • Framework: Vitest (globals enabled — no need to import describe/it/expect)
  • Shared MongoMemoryServer (one mongod, unique DB name per test file via crypto.randomBytes)
  • Global setup: tests/globalSetup.ts writes mongod URI to tests/.mongo-uri
  • Setup files: tests/mockers/jaduAuth.setup.ts
  • Test helpers: tests/testHelpers.ts — beforeAll() connects + corrupts env, afterAll() drops DB + disconnects
  • Mockers: tests/mockers/ (reusable mock objects)
  • testTimeout: 15000 (15s per test)
  • Env corruption: TestHelpers.corruptEnv() prefixes all env vars with TEST_CORRUPTED_ to prevent real third-party calls

Writing Tests

import TestHelpers from '../testHelpers';

describe('MyFeature', () => {
  beforeAll(() => TestHelpers.beforeAll());
  afterAll(() => TestHelpers.afterAll());

  it('does something', async () => {
    const db = TestHelpers.getDb();
    // ...
  });
});

Environment

Key env vars: - SENTRY_ENVIRONMENT / NODE_ENV — controls log format + Sentry environment - LOG_LEVEL — minimum log level (default: debug dev, info prod) - CENTRIFUGO_API_URL / CENTRIFUGO_API_KEY — JaduSpine realtime - JADU_AUTH_SERVER_URL / JADU_AUTH_APP_ID — JaduAuth token verification - GITHUB_PKG_TOKEN — for private @scenarix/* packages (required for npm install, see .npmrc) - See .env.local for full list

CI

Workflow Trigger Does
test.yml Push to main/staging, PRs Vitest + coverage report on PR
staging_to_main_validation.yml PR staging→main tsc build check
staging-to-main-pr.yml Weekly + manual Auto-creates release PR
sentry_release.yml — Sentry release tracking

CI uses Node 24 and requires SCENARIX_GITHUB_PKG_TOKEN secret for private packages.