wulf-pulse/CLAUDE.md

30 KiB

Pulse — Repo Guide for Claude

Pulse is an internal PSA management dashboard for Wulf Consulting. It syncs Autotask data into Postgres and adds dashboards, workflows, and analytics around it. Single Next.js 16 app — not a monorepo.

README.md covers the human-facing overview. Trust this file for the details that matter to coding decisions. For deeper context:

  • ARCHITECTURE.md — runtime topology, data flow, workers, analyzer pipeline, invariants. Read before touching workers, sync, or the analyzer.
  • DESIGN.md — design tokens, navigation IA, component vocabulary, layout rules, and the working backlog for nav/visual cleanup. Read before touching pages or shared UI components.

Stack

  • Next.js 16 + React 19 (App Router, reactCompiler: true, output: 'standalone')
  • TypeScript strict, path alias @/*
  • Postgres 16 via pg (no ORM), Redis (caching), Better Auth 1.4
  • Tailwind 4, shadcn/ui (components/ui/), recharts, sonner, lucide
  • Forms: react-hook-form + Zod resolver — admin/auth forms only
  • Runs on port 3100 (Docker exposes 3100; BETTER_AUTH_URL=http://localhost:3100)

Layout

  • app/ — App Router pages + app/api/**/route.ts handlers
  • lib/services/ — integration clients, sync services, scheduler (~50 files)
  • lib/types/<domain>.ts — shared types (autotask, sync, veeam, workflow, …)
  • lib/auth.ts, lib/auth-utils.ts, lib/permissions.ts — auth wiring
  • components/ui/ — shadcn primitives; sibling dirs are feature components
  • migrations/NNN_*.sql — numbered SQL, applied in alphabetical order on Postgres init. Use IF NOT EXISTS + ON CONFLICT DO NOTHING for seed data.
  • docs/ — long-form integration/sync guides; reference these, don't duplicate.
  • scripts/ — one-off ops scripts, not tests.

Database

  • All columns are snake_case. API responses are camelCase — handlers transform manually (no ORM).
  • Use the postgresClient singleton from lib/services/postgres-client.ts: postgresClient.query(sql, params), .transaction(), .upsert(), .bulkUpsert().
  • Audit columns convention: created_at, updated_at, synced_at, is_deleted, deleted_at.
  • Adding a migration: next number, IF NOT EXISTS, no destructive ops on existing data without a guard. Postgres init applies them on first boot only — for an existing DB, run via scripts/apply-migrations (check first; behavior varies).

API routes

  • Pattern: app/api/<resource>/route.ts exporting GET/POST/etc.
  • No Zod validation in route handlers today. Validate inputs explicitly when it matters; don't add a framework just to validate one field.
  • Errors: try/catch, return NextResponse.json({ error, message }, { status }). Convention: 503 for missing/bad config, 401/403 from auth helpers, 500 for runtime.
  • Auth in API routes: import from lib/auth-utils.ts:
    const { session, error } = await requireAuth();          // or requireAdmin() / requireSuperAdmin() / requirePermission(resource, action)
    if (error) return error;
    
    middleware.ts only verifies a session cookie exists — role checks happen here.
  • No 'use server' actions in this codebase. Everything is API routes called from client components via fetch.

Frontend

  • Most pages are 'use client' with useState/useEffect/fetch('/api/...'). No SWR/react-query — don't introduce one for one-off fetches; match the surrounding code.
  • Server components are fine for static shells; data calls live on the client today.
  • Toasts: sonner. Tables: @tanstack/react-table via components/admin/DataTable.tsx. Modals: components/admin/DetailModal.tsx. Navigation: components/navigation/app-navigation.tsx.

External integrations

All clients live in lib/services/ with a factory + is<Name>Configured() helper. Examples: getAutotaskClient(), getMsgraphClient(), getDattoRmmClient(), getVeeamClient(). Credentials always come from env vars; clients throw if missing.

Service Env prefix
Autotask AUTOTASK_* (incl. AUTOTASK_WEBHOOK_SECRET)
MS Graph (app) MSGRAPH_* (specific tenant, not common)
Microsoft OAuth (login) MICROSOFT_*
Datto RMM DATTO_RMM_*
Veeam VSPC VEEAM_VSPC_*
Auvik / Addigy / IT Glue / Mimecast / S1 / Duo / Zoom / QBO / Zabbix / Salesbldr <NAME>_*
Anthropic ANTHROPIC_API_KEY (analyzer pipeline + ai-triage-service.ts)
OpenRouter OPENROUTER_API_KEY (alternate analyzer provider, opt-in per request)
Backblaze B2 B2_* (LogLift evidence storage)
Postgres / Redis POSTGRES_* or DATABASE_URL, REDIS_URL

Sync & scheduling

  • lib/services/entity-sync.ts — per-entity Autotask → Postgres sync (incremental via lastTrackedModificationDateTime when supported, else full upsert).
  • lib/services/sync-scheduler.ts — node-cron singleton. Self-initializes on first server-side import (side effect at the bottom of the file). Schedules live in DB, admin-editable at /admin.
  • Webhooks (/api/webhooks/..., /api/zabbix/webhook, /api/rmm/loglift) are public per middleware.ts; they verify HMAC or a shared header themselves.
  • Analyzer worker (lib/services/analyzer/worker.ts) and RMM Overshell worker (lib/services/rmm/worker.ts) auto-start on import in production. Same side-effect-import caveat as the sync scheduler.

Auth

  • Better Auth with magic link + TOTP 2FA + Microsoft OAuth. Roles: user, admin, super-admin. Tables created in migration 012.
  • Default admin bootstrapped from DEFAULT_ADMIN_EMAIL via lib/bootstrap.ts.
  • middleware.ts redirects unauth'd page requests to /auth/sign-in. Public routes (webhooks, sync, health, mobile, openclaw, kiosk, legal, qbo callbacks) are hardcoded there — add to that list when introducing a new public endpoint.

Build / run / verify

  • Dev: npm run devhttp://localhost:3100
  • Build: npm run build (turbopack via Next 16)
  • Type check: npx tsc --noEmit --pretty
  • Tests: npm test (vitest) — covers lib/services/analyzer/**, lib/services/rmm/**, lib/services/b2/**, and lib/services/analyzer/ link-discovery.test.ts. Other parts of the codebase have no tests — if you touch them, type-check is the only safety net. No CI yet; tests are local-only.
  • Docker: docker compose up from repo root. Postgres applies migrations/*.sql on init only (existing volumes won't re-run them).

Conventions to follow

  • Files: kebab-case. Components: PascalCase exports from kebab-case files.
  • Don't introduce ORMs, server actions, or alternative state libraries unless asked — match the existing pattern.
  • New SQL: numbered migration; never edit a committed one.
  • Long-form per-feature documentation belongs in docs/. Don't duplicate it here.

Operator config

  • Integration disable — two sources, merged:
    • INTEGRATIONS_DISABLED env var (legacy / bootstrap fallback). Comma- or space-separated keys with aliases (sentinelones1, dattodatto_rmm, it-glueitglue, ms-graphmsgraph). Set in .env and restart.
    • /admin/integrations UI backed by the integration_settings table (migration 081). Toggle without a container restart; takes effect within the 5-minute health cache (PATCH clears the cache immediately). Audit columns capture disabled_by (session email), disabled_at, and an optional disabled_reason. In both cases live auth checks still run (logs surface the underlying state); the UI ignores the result for disabled integrations.

Watch out for

  • A .env file is committed to the repo. Treat secrets as potentially real; don't log/echo them, and flag this if it comes up.
  • Duplicate migration numbers exist (002, 004, 009) — alphabetical apply order.
  • Sync scheduler, analyzer worker, and RMM worker all auto-start as side effects of being imported on the server. Don't eager-import them from hot paths or shared utilities.
  • Analyzer LLM provider is per-request (anthropic | openrouter). The idempotency content_hash is provider-scoped — the same ticket can have one Claude row and one OpenRouter row.
  • Analyzer cost ceiling: Stage 4 (Opus) skipped above $2.00 estimated cost; the analysis is flagged for human review.
  • IT Glue results destined for an LLM must go through lib/services/analyzer/itglue-search.ts (redacted). Don't pipe raw client output into a prompt.

Useful existing docs

  • ARCHITECTURE.md — runtime, data flow, workers, analyzer pipeline (read first)
  • DESIGN.md — UI tokens, nav IA, component conventions, current cleanup backlog
  • AUTOTASK_API_GUIDE.md, ADDIGY_API_GUIDE.md — credential setup
  • POSTGRES_SYNC_SETUP.md, DOCKER_README.md
  • PULSE_DATABASE_SKILL.md — diagnostic queries
  • docs/ — sync behavior, webhook setup, workflow editor, analyzer runbook, RMM Overshell + LogLift specs, IT Glue audit spec, per-integration guides

Project

Pulse Mobile Shell Redesign

A ground-up redesign of Pulse's /mobile/* shell — the manager-on-the-go view of the existing Pulse PSA dashboard. It replaces the current mobile layout, swaps the standalone /mobile/nav page for a Sheet drawer, restyles Dashboard / Tickets / Finance, and adds two new mobile surfaces (Analyzer feed, Engagement). Built on the existing Pulse codebase — same routes, same data, phone-first layouts.

Core Value: A manager can open Pulse on their phone and, in under 30 seconds, see the state of the business and triage tickets — without ever needing to switch to desktop for read-only awareness.

Constraints

  • Tech stack: Next.js 16 App Router, React 19, Tailwind 4, shadcn/ui — match existing Pulse conventions (no new state libraries, no SWR/react-query, no ORM, no Zod in API routes unless required)
  • Routes: Keep all existing /mobile/* paths. Replace files in place. No new top-level routes outside /mobile/.
  • Build order: Each spec step ships independently — no big-bang merge. Phase boundaries should let each step land on master cleanly.
  • No service worker / no offline: don't introduce next-pwa or a custom SW in this iteration.
  • Auth: existing Better Auth + middleware handles /mobile/*. No new auth surface.
  • Data sources: reuse existing endpoints where possible; add /api/mobile/* only when an existing list endpoint doesn't return the needed shape (e.g., analyzer feed).

Technology Stack

Languages

  • TypeScript 5 - Entire codebase, strict mode enabled
  • JavaScript/JSX - React components via TypeScript with JSX support
  • SQL - PostgreSQL migrations and queries
  • Bash - Build and deployment scripts

Runtime

  • Node.js (version inferred from package.json compatibility)
  • Next.js 16.1.1 running on port 3100
  • npm (lockfile: package-lock.json)

Frameworks

  • Next.js 16.1.1 - App Router with output: 'standalone' for Docker, React Compiler enabled
  • React 19.2.3 - Server and client components, React Compiler active
  • Better Auth 1.4.10 - Authentication with magic link, TOTP 2FA, Microsoft OAuth
  • Tailwind CSS 4.1.18 - Utility-first styling
  • shadcn/ui (via Radix UI primitives) - Component library: components/ui/
  • Recharts 3.7.0 - Charts and graphs (analytics/dashboards)
  • Lucide React 0.562.0 - Icon library
  • Sonner 2.0.7 - Toast notifications
  • cmdk 1.1.1 - Command palette component
  • react-hook-form 7.70.0 - Form state management (admin/auth only)
  • Zod 4.3.5 - Type-safe schema validation
  • @hookform/resolvers 5.2.2 - Form resolver for Zod
  • @tanstack/react-table 8.21.3 - Headless table library with sorting, pagination, search
  • react-markdown 10.1.0 - Markdown rendering
  • remark-gfm 4.0.1 - GitHub-flavored markdown support
  • date-fns 4.1.0 - Date manipulation and formatting
  • react-day-picker 9.13.0 - Calendar date picker
  • clsx 2.1.1 - Conditional className utility
  • tailwind-merge 3.4.0 - Merge Tailwind class conflicts
  • class-variance-authority 0.7.1 - CSS-in-JS variant management
  • next-themes 0.4.6 - Dark mode theme switching

Testing & Build

  • vitest 4.1.5 - Unit and integration testing runner
  • Run: npm test (run once), npm run test:watch (watch mode)
  • Test coverage for: lib/services/analyzer/**, lib/services/rmm/**, lib/services/b2/**, lib/services/analyzer/link-discovery.test.ts
  • Turbopack (via Next.js 16) - Fast bundler for development and production
  • ESLint 9.39.2 - Linting (with eslint-config-next 16.1.1)
  • TypeScript - Type checking via npx tsc --noEmit --pretty

Key Dependencies

  • pg 8.11.0 - PostgreSQL client (no ORM); queries via postgresClient singleton
  • ioredis 5.9.0 - Redis client for caching; optional (graceful fallback if REDIS_URL unset)
  • node-cron 4.2.1 - Job scheduler for sync tasks and workflows
  • @anthropic-ai/sdk 0.91.1 - Anthropic Claude API client for AI Ticket Analyzer pipeline
  • Better Auth ecosystem packages - OAuth, 2FA, session management
  • nodemailer 7.0.12 - Email delivery for magic link auth
  • babel-plugin-react-compiler 1.0.0 - React Compiler for optimized renders
  • shadcn 4.6.0 - CLI tool for adding shadcn/ui components
  • baseline-browser-mapping 2.10.8 - Browser compatibility mapping
  • tw-animate-css 1.4.0 - Tailwind animation utilities

Configuration

  • BETTER_AUTH_URL - Base URL for auth (e.g., http://localhost:3100 or https://pulse.wulfconsulting.cloud)
  • BETTER_AUTH_SECRET - Secret key for session signing
  • DATABASE_URL or POSTGRES_* - PostgreSQL connection (host, port, db, user, password)
  • REDIS_URL - Redis connection (e.g., redis://localhost:6380 in Docker compose)
  • SESSION_TIMEOUT_SECONDS - Session TTL (default: 86400 / 24 hours)
  • MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET, MICROSOFT_TENANT_ID - Microsoft OAuth for login (tenant is specific, not 'common')
  • DEFAULT_ADMIN_EMAIL - Bootstrap admin account email
  • Integration env vars: prefixed by service (e.g., AUTOTASK_*, DATTO_RMM_*, MSGRAPH_*, etc.) — see INTEGRATIONS.md
  • Path alias: @/* maps to project root for cleaner imports
  • Target: ES2017
  • Strict mode enabled
  • Config: tsconfig.json
  • File: next.config.ts
  • Standalone output for Docker deployment
  • React Compiler enabled
  • Image domains: configurable (currently empty)
  • npm run build → Next.js standalone app in .next/
  • npm run start → Starts production server on port 3100

Platform Requirements

  • Node.js 18+ (inferred from Next.js 16 compatibility)
  • npm 8+
  • PostgreSQL 16 (local or Docker)
  • Redis 7 (optional, enables caching)
  • Docker & Docker Compose (for full stack)
  • Docker with docker-compose.yml provided
  • Postgres 16 container (applies migrations on init)
  • Redis 7 container on port 6380 (custom, not 6379)
  • Next.js app container on port 3100
  • Traefik integration available (via labels in docker-compose.yml) for routing at pulse.wulfconsulting.cloud
  • Container-based: standalone Next.js image with migrations applied via Postgres init
  • Volume mounts for data persistence: redis_data, postgres_data
  • Environment configuration via .env.local file mounted read-only

Conventions

Naming Patterns

  • kebab-case for all files and directories (e.g., postgres-client.ts, invite-user-form.tsx, entity-sync.ts)
  • Nested directories use kebab-case (e.g., lib/services/analyzer/, components/admin/users/)
  • camelCase for all functions (e.g., getAutotaskClient(), transformCompany(), extractExplicitFromText())
  • Factory functions prefixed with get (e.g., getAutotaskClient(), getDattoRmmClient())
  • Helper functions suffixed with descriptive intent (e.g., relTime(), deriveSigningKey())
  • Private/internal functions prefixed with underscore: _INTERNALS objects expose internals for test access
  • camelCase for all variables (e.g., isLoading, setData, ticketNumber)
  • Constants in UPPER_SNAKE_CASE (e.g., MAX_EXPLICIT_LINKS, TICKET_NUMBER_REGEX)
  • Database column names are always snake_case (e.g., company_name, is_active, created_at)
  • PascalCase for all type names (e.g., ClassificationRule, WorkflowExecution, TicketData)
  • Single-letter generics are acceptable (e.g., queryEntity<T>())
  • Union types as literal strings (e.g., type RuleType = 'branch_routing' | 'ticket_type')
  • PascalCase exported from kebab-case files (e.g., export InviteUserForm from invite-user-form.tsx)
  • Page components: export default function ComponentName() at end of file
  • Form components: follow [Resource]Form naming (e.g., InviteUserForm, SignInForm, UserForm)

Code Style

  • TypeScript strict mode enabled ("strict": true in tsconfig.json)
  • No explicit formatter config (ESLint handles style)
  • Indentation: 2 spaces (inferred from existing code)
  • ESLint: eslint.config.mjs with Next.js config (eslint-config-next/core-web-vitals, eslint-config-next/typescript)
  • No additional custom rules beyond Next.js defaults
  • Type checking: npx tsc --noEmit --pretty (must pass before commit)

Import Organization

  • Configured as "@/*": ["./*"] in tsconfig.json
  • Use @/lib/..., @/components/..., @/app/... always
  • Never use relative paths like ../../../ for imports

Error Handling

  • All async functions use try/catch blocks
  • API routes: catch errors and return NextResponse.json({ error, message }, { status })
  • Standard status codes: 500 for runtime errors, 503 for missing/bad config, 401/403 from auth helpers
  • Error messages: include error instanceof Error ? error.message : 'fallback message'

Logging

  • console.error() for exceptions caught in try/catch (usually in API routes and services)
  • Include context: console.error('Failed to fetch companies:', error)
  • No console.log() for debugging (remove before commit per linter checks)

Comments

  • Explain why, not what (code shows the what)
  • Non-obvious logic or business rules
  • Performance-critical sections
  • Workarounds or hacks (mark with // HACK: or // NOTE:)
  • Used sparingly on complex functions
  • Example from lib/services/analyzer/link-discovery.ts:
  • Not required for simple getters/setters or obvious functions

Function Design

  • Keep functions focused: one responsibility per function
  • Aim for <50 lines for page components, <30 for utilities
  • Complex operations broken into smaller helpers
  • Prefer object parameters for >3 arguments
  • Don't use any — use specific types
  • Use Partial<T> for optional object shapes
  • Async functions always return Promise<T> explicitly
  • Prefer null over undefined for missing values
  • Use discriminated unions for success/error returns in critical paths (see analyzer pipeline)

Module Design

  • Prefer export at declaration point rather than grouped re-exports
  • One main export per file (exception: barrel files in components/ui/)
  • Internal utilities prefixed with underscore: _INTERNALS object for test access
  • components/ui/index.ts exports all shadcn primitives
  • lib/types/ has domain-specific barrel files (e.g., lib/types/workflow.ts, lib/types/autotask.ts)
  • Avoid deep nesting — import from files, not directories unless barrel exists

Database Transformations

Shared Components & Libraries

  • shadcn primitives live in components/ui/
  • Feature-specific components in sibling directories (e.g., components/dashboard/, components/admin/)
  • Icons: Always use lucide-react (e.g., import { Loader2, Send } from 'lucide-react')
  • Use @tanstack/react-table via components/admin/DataTable.tsx wrapper
  • Example: <DataTable columns={columns} data={data} />
  • Use components/admin/DetailModal.tsx for entity details
  • Follows card + tabs pattern (formatted/raw)
  • Use components/navigation/app-navigation.tsx (NavigationMenu from Radix)
  • Dropdowns prefer @radix-ui/react-dropdown-menu over submenus
  • Use sonner library: import { toast } from 'sonner'
  • Patterns: toast.success(), toast.error(), toast.info()
  • Use react-hook-form + Zod for validation
  • Only in admin/auth forms — NOT in every page
  • Pattern: useForm() with zodResolver(), then <Form> wrapper from shadcn
  • Use recharts for data visualization (e.g., <BarChart>, <LineChart>)

What NOT to Introduce

  • No ORMs (Prisma, TypeORM, etc.) — use postgresClient singleton and manual transforms
  • No server actions ('use server') — use API routes called via fetch() from clients
  • No additional state libraries (SWR, react-query, TanStack Query) — match local useState + fetch pattern
  • No change to authentication (Better Auth is final)
  • No editing of committed migrations — always create new numbered ones
  • Keeps codebase lean and explicit
  • Reduces abstraction overhead
  • Makes data flow (DB → API → Client) visible

Migrations

Architecture

Pattern Overview

  • App Router pages ('use client') fetch data via API routes using fetch()
  • Three auto-starting background workers via side-effect imports (sync-scheduler, analyzer, RMM overshell)
  • Service layer in lib/services/ with factories + configuration helpers
  • Postgres 16 as system of record (snake_case columns), Redis for caching only
  • Manual transformation from snake_case DB columns to camelCase API responses
  • Better Auth 1.4 for sessions + magic link + TOTP 2FA + Microsoft OAuth

Layers

  • Purpose: Accept HTTP requests, validate auth, delegate to services, return JSON responses
  • Location: app/api/*/route.ts, app/*/page.tsx
  • Contains: Next.js route handlers (GET/POST/PATCH/DELETE), page components
  • Depends on: Auth via lib/auth-utils.ts, services via lib/services/
  • Used by: Client-side fetch calls from UI components
  • Purpose: Sync data from external APIs, run background jobs, execute workflows, store results in Postgres
  • Location: lib/services/*.ts (~50 files) plus subdirectories (analyzer/, rmm/, llm/, b2/)
  • Contains: Integration clients (autotask, datto-rmm, itglue, veeam, msgraph, etc.), sync logic, job executors, pipeline orchestration
  • Depends on: Postgres client, external API clients, environment configuration
  • Used by: API routes (sync endpoints, webhook handlers) and background workers
  • Purpose: Query, upsert, and manage state in Postgres
  • Location: lib/services/postgres-client.ts (singleton) + migrations/NNN_*.sql
  • Contains: Connection pool, query builder methods (query(), transaction(), upsert(), bulkUpsert()), migration definitions
  • Depends on: PostgreSQL 16 connection string from env
  • Used by: All services that read/write data
  • Purpose: Poll for work and execute sync/analysis/RMM tasks without blocking HTTP requests
  • Location: lib/services/sync-scheduler.ts, lib/services/analyzer/worker.ts, lib/services/rmm/worker.ts
  • Contains: node-cron scheduler, polling loops with exponential backoff, state machine handlers
  • Depends on: Postgres client, service layer integrations
  • Used by: Auto-start on module import (side effects); HTTP routes that need them running will import the module to start them
  • Purpose: Render pages, dialogs, tables, charts, and handle client-side state
  • Location: components/ui/ (shadcn primitives), components/*/ (feature-specific), app/*/page.tsx
  • Contains: React components, hooks for fetch + state, sonner toasts, recharts visualizations
  • Depends on: API routes via fetch(), client auth via Better Auth SDK
  • Used by: Next.js pages and other components
  • Purpose: Define TypeScript interfaces and database schema
  • Location: lib/types/<domain>.ts, migrations/NNN_*.sql
  • Contains: Entity types (ticket, company, analysis, rmm_execution, etc.), sync request shapes, API response envelopes
  • Depends on: (none — they define contracts)
  • Used by: Services, API routes, components

Data Flow

  • HTTP requests: Stateless; session from Better Auth cookie
  • Background jobs: State in Postgres (status columns: queuedin_flightcomplete / failed)
  • Caching: Redis (optional, used for integration health checks, cache duration varies)
  • Authorization: Checked in route handlers via requireAuth() / requireAdmin() / requirePermission() from lib/auth-utils.ts

Key Abstractions

  • Purpose: Lazy-load integration clients with configured credentials; provide is<Name>Configured() helper to check env vars
  • Examples: lib/services/autotask-factory.ts, lib/services/datto-rmm-factory.ts, lib/services/msgraph-factory.ts
  • Pattern: Export getAutotaskClient(), getDattoRmmClient(), getMsgraphClient() with caching; throw if credentials missing; bundled with is<Name>Configured() for upstream checks
  • Why: Decouples client initialization from route handlers; allows conditional feature gates per env
  • Purpose: Single connection pool for all Postgres queries; auto-lazy-initializes; provides ORM-like query builder
  • Examples: postgresClient.query(), postgresClient.transaction(), postgresClient.upsert(), postgresClient.bulkUpsert()
  • Pattern: Private constructor, static getInstance(), pool initialized on first use
  • Why: Prevents connection leaks; provides consistent interface across ~50 services
  • Purpose: Incremental/full sync of any entity type from external API → Postgres
  • Examples: lib/services/entity-sync.ts (Autotask), lib/services/itglue-sync-service.ts, lib/services/veeam-sync-service.ts
  • Pattern: Reads lastTrackedModificationDateTime from last_sync table; queries external API; batches upsert via bulkUpsert()
  • Why: Codifies the "last sync timestamp + incremental pull + batch insert" pattern across integrations
  • Purpose: Orchestrate multi-stage LLM analysis with fallbacks, cost guards, and persistence
  • Examples: lib/services/analyzer/pipeline.ts, lib/services/analyzer/stages/*.ts
  • Pattern: Each stage returns structured output (gap analysis, category, cost estimate); cost guard checks ceiling before Stage 4; all stages persisted to analyzer_stage_executions
  • Why: Allows cost control (skip expensive Opus above $2.00), idempotency (content_hash), and debugging (inspect each stage's output)
  • Purpose: Auto-start in production, poll for work, claim rows with FOR UPDATE SKIP LOCKED, execute, persist result
  • Examples: lib/services/analyzer/worker.ts, lib/services/rmm/worker.ts, lib/services/sync-scheduler.ts
  • Pattern: Side-effect import auto-starts on module load; exponential backoff if no work; row locking for multi-instance safety (analyzer), or single-instance gate (sync scheduler)
  • Why: Keeps background work out of HTTP request path; analyzer is safe to scale (row locking); sync scheduler should run on one instance only
  • Purpose: Define three roles (user, admin, super-admin) with per-resource permissions
  • Examples: lib/auth.ts (auth config), lib/permissions.ts (permission matrix), lib/auth-utils.ts (runtime checks)
  • Pattern: Route handler calls requireAdmin() / requirePermission(), which decode session and check role; middleware only verifies session cookie exists
  • Why: Separates auth (middleware) from authorization (route handler); role-based gates are checked at the point of use

Entry Points

  • Location: app/*/page.tsx
  • Triggers: Browser navigation to any route except public paths
  • Responsibilities: Render page shell with AppNavigation + PageHeader, fetch data from API, render client-side components, handle toast/dialog interactions
  • Location: app/api/*/route.ts
  • Triggers: fetch() from client, external webhooks (Autotask, Zabbix, RMM), scheduler HTTP calls
  • Responsibilities: Validate auth/webhook signature, delegate to service layer, return JSON response with appropriate status (200, 401, 403, 503, 500)
  • Location: app/api/webhooks/autotask, app/api/zabbix/webhook, app/api/rmm/loglift
  • Triggers: External systems (Autotask, Zabbix, OpenClaw) POST events
  • Responsibilities: Verify HMAC or custom header, parse event, enqueue jobs or upsert data, return 200 (even on error so Autotask doesn't deactivate)
  • Location: app/api/sync/*, app/api/datto-rmm/sync, app/api/itglue/sync, app/api/veeam/sync, etc.
  • Triggers: lib/services/sync-scheduler.ts fires HTTP POST at configured times
  • Responsibilities: Call sync service, update last_sync timestamp, return 200 on success or error message
  • Location: lib/services/sync-scheduler.ts (cron), lib/services/analyzer/worker.ts (2s poll), lib/services/rmm/worker.ts (5s poll)
  • Triggers: Auto-starts as side effect of module import; runs indefinitely in production
  • Responsibilities: Poll for work from DB, claim row, execute, persist result, handle errors + logging

Error Handling

  • Webhook handlers — Return 200 even if processing fails; log error so ops can investigate via audit log
  • API route handlerstry/catch, return NextResponse.json({ error, message }, { status }) with conventions:
  • Analyzer pipeline — Cost ceiling guard at $2.00; above that, Stage 4 (Opus) skipped, analysis flagged with needs_review=true
  • Sync services — Incremental sync errors log + re-trigger on next schedule; full sync errors persist last_error to sync_schedules table
  • Background workers — Stale jobs reset on worker boot (no recovery); exponential backoff on empty polls; errors logged with job ID for manual inspection

Cross-Cutting Concerns

Project Skills

No project skills found. Add skills to any of: .claude/skills/, .agents/skills/, .cursor/skills/, or .github/skills/ with a SKILL.md index file.

GSD Workflow Enforcement

Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.

Use these entry points:

  • /gsd-quick for small fixes, doc updates, and ad-hoc tasks
  • /gsd-debug for investigation and bug fixing
  • /gsd-execute-phase for planned phase work

Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.

Developer Profile

Profile not yet configured. Run /gsd-profile-user to generate your developer profile. This section is managed by generate-claude-profile -- do not edit manually.