agentleFS
automagik-siautomagik-si/omni/.claude/CLAUDE.md

omni

CLAUDE.md70 starsChanged 5 days agoApache-2.0View on GitHub

Code patterns, refactoring guidelines, and distributed documentation standards. For workflow, agents, and commands see @AGENTS.md. The API auto-migrates on startup. Schema changes are hand-written SQL migrations — do NOT run drizzle-kit generate (it is broken here; see the comprehensive reference below for why). CRITICAL: drizzle-kit push and migrateDb() are incompatible. Push creates tables without journal entries. The API's auto-migrate then crashes with "relation already exists". Never use push in CI or production. Never: - Edit, delete, or renumber a migration…

  • Deletes or force-pushes

1 other project uses a version of this.

Adopted by

Most recently taken up by

  • Other versions1 project uses a version of this.

    Word for word · 1

    1. automagik-dev/omni★ 70100% same

    Edited · 0

    None found.

    Wrote their own version · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository6 other files it gives its agents.

The file

Show its rules and text

We split this CLAUDE.md into the rules it gives an agent

26 rules. About 0 are common, 17 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈17

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Add a migration without a journal entry, or vice versa

    line 104
  2. Very specific:

    Use drizzle-kit push in CI (use pg_isready for readiness)

    line 106
  3. Very specific:

    When Biome reports noExcessiveCognitiveComplexity or suggests extracting helpers:

    line 110
  4. Distinctive:

    Reuse if exists: - Import and use the existing helper

    line 136
  5. Distinctive:

    Extend if close: - If a helper does 80% of what you need, extend it

    line 137
  6. Distinctive:

    Create if truly new: - Only create new helpers when no suitable option exists

    line 138
  7. Distinctive:

    Compose small functions: - Prefer many small functions over one large one

    line 145
  8. Distinctive:

    If you catch yourself about to run a prohibited command, STOP and use the Bun equivalent.

    line 173
  9. Very specific:

    Test runs default to LOG_LEVEL=silent (suites intentionally exercise noisy warn/error paths).

    line 271
  10. Distinctive:

    When to use raw commands: Only for edge cases not covered by make targets (e.g., specific bun flags, one-off debugging).

    line 299
  11. Very specific:

    Use drizzle-kit push in CI or production

    line 401
  12. Very specific:

    Run drizzle-kit generate or regenerate meta/ snapshots (frozen at 0026.

    line 404
  13. Very specific:

    All commits must follow the format: type(scope): description

    line 459
  14. Very specific:

    Don't use non-conventional commit messages (all commits must be type(scope): description)

    line 520
  15. Distinctive:

    Don't create nightly branches (deprecated — use feature PR to dev flow)

    line 521
  16. Very specific:

    Don't use npm/yarn/pnpm (use Bun exclusively)

    line 522
  17. Distinctive:

    Don't mix channel logic in core (channels are plugins)

    line 523
  18. Distinctive:

    Don't skip event publishing for state changes

    line 524
  19. Distinctive:

    Don't use raw SQL (use Drizzle)

    line 525
  20. Distinctive:

    Don't create REST endpoints without OpenAPI docs

    line 526
  21. Distinctive:

    Don't skip Zod validation on external inputs

    line 527
  22. Distinctive:

    Don't hardcode channel-specific behavior in core

    line 528
  23. Very specific:

    Don't use any types

    line 529
  24. Distinctive:

    Don't leave uncommitted work

    line 530
  25. Distinctive:

    Don't stop without pushing

    line 531
  26. Distinctive:

    Don't bypass make commands for common tasks

    line 532

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

Omni v2 - Technical Reference

Code patterns, refactoring guidelines, and distributed documentation standards. For workflow, agents, and commands see @AGENTS.md.

Code Patterns

Event Publishing

// Always publish events for state changes
await eventBus.publish({
  type: 'message.received',
  payload: {
    instanceId,
    channelType: 'whatsapp',
    message,
  },
  metadata: {
    correlationId: generateId(),
    timestamp: Date.now(),
  },
});

Zod Schema First

// Define schema once, derive types
export const MessageSchema = z.object({
  id: z.string().uuid(),
  content: z.string(),
  sender: PersonReferenceSchema,
  timestamp: z.number(),
});

export type Message = z.infer<typeof MessageSchema>;

Channel Plugin Pattern

// Channel plugins implement the ChannelPlugin interface
export const whatsappPlugin: ChannelPlugin = {
  id: 'whatsapp-baileys',
  name: 'WhatsApp (Baileys)',

  async initialize(config) { /* ... */ },
  async sendMessage(instanceId, message) { /* ... */ },
  async handleWebhook(req) { /* ... */ },

  events: {
    'message.received': handleIncoming,
    'message.sent': handleOutgoing,
  },
};

Error Handling

// Use typed errors with context
import { OmniError, ErrorCode } from '@omni/core';

throw new OmniError({
  code: ErrorCode.CHANNEL_NOT_CONNECTED,
  message: 'WhatsApp instance not connected',
  context: { instanceId },
  recoverable: true,
});

Database Schema Changes

The API auto-migrates on startup. Schema changes are hand-written SQL migrations — do NOT run drizzle-kit generate (it is broken here; see the comprehensive reference below for why).

# 1. Edit schema source of truth
vim packages/db/src/schema.ts

# 2. Hand-write an additive, idempotent migration with a header comment
#    (copy the style of 0043/0044/0052 in packages/db/drizzle/)
vim packages/db/drizzle/NNNN_short_name.sql

# 3. Append a journal entry by hand (idx = prev + 1, when = prev + 100000000)
vim packages/db/drizzle/meta/_journal.json

# 4. Verify against the contract gate
make verify-migrations

# 5. Commit migration + journal + schema together
git add packages/db/drizzle/ packages/db/src/schema.ts

CRITICAL: drizzle-kit push and migrateDb() are incompatible. Push creates tables without journal entries. The API's auto-migrate then crashes with "relation already exists". Never use push in CI or production.

Never:

  • Edit, delete, or renumber a migration file that exists on the base branch (deployed migrations are immutable — the migrator tracks them by content hash)
  • Add a migration without a journal entry, or vice versa
  • Squash migrations without a journal fix script
  • Use drizzle-kit push in CI (use pg_isready for readiness)

Refactoring Guidelines

When Biome reports noExcessiveCognitiveComplexity or suggests extracting helpers:

Before Creating New Helpers

ALWAYS search for existing utilities first:

rg "function (format|parse|transform|validate|convert)" packages/
rg "export (function|const)" packages/*/src/utils/
rg "export" packages/core/src/utils/

Common Utility Locations

TypeCheck Here First
String/formattingpackages/core/src/utils/
Validation helperspackages/core/src/schemas/
Date/time utilitiespackages/core/src/utils/
ID generationpackages/core/src/identity/
Error helperspackages/core/src/errors/
Channel-specificpackages/channel-*/src/utils/

When Extracting Functions

  1. Search first - grep/glob for similar existing functions
  2. Reuse if exists - Import and use the existing helper
  3. Extend if close - If a helper does 80% of what you need, extend it
  4. Create if truly new - Only create new helpers when no suitable option exists
  5. Place correctly - Shared in @omni/core, package-specific in local utils/

Refactoring Strategies

  • Early returns - Reduce nesting by returning early for edge cases
  • Guard clauses - Handle invalid states at the top of functions
  • Compose small functions - Prefer many small functions over one large one
  • Single responsibility - Each function should do one thing well

Distributed Documentation

Each package/directory should have its own CLAUDE.md with package-specific patterns, local conventions, and integration notes.

packages/core/CLAUDE.md      # Core package patterns
packages/api/CLAUDE.md       # API conventions
packages/channel-*/CLAUDE.md # Channel-specific notes

Bun Ecosystem (Mandatory Compliance)

This project uses Bun exclusively. You MUST follow these rules without exception.

TaskMUST UseNEVER Use
Install packagesbun install, bun addnpm install, yarn add, pnpm add
Run scriptsbun run <script>npm run, yarn, pnpm run
Execute binariesbunx <cmd>npx, yarn dlx, pnpm dlx
Run TypeScriptbun <file.ts>node, ts-node, tsx
Run testsbun testjest, vitest, npm test
Watch modebun --watchnodemon, ts-node-dev

If you catch yourself about to run a prohibited command, STOP and use the Bun equivalent.


Tech Stack (Locked Decisions)

CategoryUse ThisNot This
RuntimeBunNode.js
LanguageTypeScript (strict)JavaScript
HTTP FrameworkHonoExpress, Fastify
Type-safe APIREST + OpenAPI-generated SDKsGraphQL
Database ORMDrizzlePrisma, TypeORM
DatabasePostgreSQLMySQL, MongoDB
Event BusNATS JetStreamRedis Pub/Sub, RabbitMQ
ValidationZodJoi, Yup
MonorepoTurborepoNx, Lerna
Process ManagerPM2Forever, systemd

Project Structure

omni-v2/
├── packages/
│   ├── core/           # Events, identity, schemas (shared)
│   ├── api/            # HTTP API (Hono + REST + OpenAPI)
│   ├── channel-sdk/    # Plugin SDK for channel developers
│   ├── channel-*/      # Official channel implementations
│   ├── cli/            # LLM-optimized CLI
│   └── sdk/            # Auto-generated TypeScript SDK
├── docs/               # Documentation
├── scripts/            # Build, deploy, SDK generation
├── .claude/            # AI workflow (agents, commands, hooks, skills)
└── .genie/             # Genie workspace (wishes, brainstorms, state)

Where to Put Things

WhatWhereWhy
Event definitionspackages/core/src/events/Single source of truth
Zod schemaspackages/core/src/schemas/Shared validation
Database schemapackages/core/src/db/Drizzle schema
API endpointspackages/api/src/routes/HTTP handlers
API schemaspackages/api/src/schemas/openapi/OpenAPI contract and generated SDK types
Channel pluginspackages/channel-*/Isolated per channel
Shared typespackages/core/src/types/TypeScript interfaces
CLI commandspackages/cli/src/commands/LLM-optimized CLI

Commands: Use Make First

ALWAYS check make help before running raw commands. The Makefile wraps common tasks with proper setup, environment loading, and error handling.

Quick Reference

TaskCommand
Full setupmake setup
Start devmake dev
Run checksmake check
Lintmake lint
Typecheckmake typecheck
Generate SDKmake sdk-generate
Restart APImake restart-api
Run CLImake cli ARGS="--help"
Install CLI globallymake cli-link

Development

make dev          # Start all services + API
make dev-api      # Start just the API
make dev-services # Start PostgreSQL + NATS + API via PM2

Quality Checks

make check        # All checks: typecheck + lint + test
make check-all    # check + the real-PostgreSQL gate, run CONCURRENTLY (fastest full validation)
make typecheck    # TypeScript only
make lint         # Biome linter
make lint-fix     # Auto-fix lint issues
make test         # All tests (per-package via turbo — unchanged packages replay their cached green run)
make test-sweep   # All tests in ONE process (CI parity; use to hunt cross-file state leaks)
make test-api     # API package tests only
make test-file F=<path>  # Specific test file
make test-pg-gate       # Every real-PostgreSQL suite on a fresh disposable cluster (never skips)
make test-pg-gate-warm  # Same gate against a kept-warm cluster (fast dev loop; state in .pg-gate-warm.json)
make pg-gate-warm-stop  # Destroy the kept-warm cluster

Test runs default to LOG_LEVEL=silent (suites intentionally exercise noisy warn/error paths). Re-enable logs for a run with make test TEST_LOG_LEVEL=debug. Tests that assert on log output must configureLogging({ level: ... }) themselves rather than rely on the ambient threshold.

Individual Services

make restart-api     # Restart API only
make restart-nats    # Restart NATS only
make restart-pgserve # Restart PostgreSQL only
make logs-api        # View API logs

CLI

make cli ARGS="--help"    # Run CLI from source
make cli-build            # Build CLI package
make cli-link             # Build + link globally (omni command)

SDK

make sdk-generate   # Generate SDK from OpenAPI spec

When to use raw commands: Only for edge cases not covered by make targets (e.g., specific bun flags, one-off debugging).


Database & Migrations (Comprehensive Reference)

The API auto-migrates on startup via migrateDb() in packages/api/src/index.ts. Schema changes flow through Drizzle migrations, NOT drizzle-kit push.

drizzle-kit push and migrateDb() are INCOMPATIBLE. Push creates tables without migration journal entries. Migrate then crashes with "relation already exists". NEVER use drizzle-kit push in CI, production, or any pipeline that also runs migrateDb(). db-push is a local dev convenience ONLY.

Why migrations are hand-written (do not "fix" this)

drizzle-kit generate is dead in this repo: the meta/*_snapshot.json files stop at 0026 while migrations continue past 0055. Generate diffs schema.ts against the LAST snapshot, so it compares against a ~30-migration-old state and proposes recreating the world (or hangs on interactive rename prompts). Regenerating snapshots is riskier than living without them — the API auto-migrates on boot and is intolerant of journal drift, and 12+ deployed migrations already follow the hand-written precedent. Do not attempt to resurrect generate or add new snapshots.

Schema Change Workflow (hand-written precedent — 0043/0044/0052)

# 1. Edit schema source of truth
vim packages/db/src/schema.ts

# 2. Hand-write the migration SQL (next free number, snake_case name)
vim packages/db/drizzle/0056_short_name.sql

# 3. Append a journal entry to packages/db/drizzle/meta/_journal.json:
#    {"idx": 56, "version": "7", "when": <prev when + 100000000>,
#     "tag": "0056_short_name", "breakpoints": true}

# 4. Verify against the contract gate (also runs in make check and CI)
make verify-migrations

# 5. Test — restart API (auto-migrates on boot)
pm2 restart omni-v2-api

# 6. Commit migration + journal + schema together
git add packages/db/drizzle/ packages/db/src/schema.ts
git commit -m "feat(db): add <description>"

Migration SQL rules (enforced by scripts/verify-migration-contract.ts):

  • Header comment first. Open with a -- comment block: what the migration adds, why, the issue/PR reference, and a line like -- Hand-written following the 0043/0044/0052 precedent (additive, idempotent).
  • Additive + idempotent. ADD COLUMN IF NOT EXISTS, CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS. The boot migrator may race across replicas and re-run against half-migrated databases.
  • No destructive statements — DROP TABLE, DROP COLUMN, ALTER COLUMN ... TYPE, TRUNCATE fail the gate. If one is genuinely required, add a header line -- destructive: <justification>; the gate then passes and the justification lands in the diff for review.
  • No explicit BEGIN/COMMIT. The boot migrator runs each file on a pooled postgres-js connection that rejects raw transaction control.
  • when timestamps must strictly increase — the migrator SILENTLY SKIPS entries whose when is out of order (see packages/db/src/migrate.ts).
  • schema.ts and the migration ship in the same PR. Escape hatches for the rare exceptions: // no-migration-needed: <why> on a changed schema.ts line (type-only change), or -- no-schema-change: <why> in the migration header (data-only/backfill migration).

Parallel-worktree numbering collisions

Multiple in-flight branches will often claim the same next number (e.g. two PRs both adding 0056_*). The contract gate fails the merge with a duplicate idx / non-sequential journal. Whoever merges second renumbers their OWN migration: rename the .sql file to the next free number, update the journal entry's tag + idx, and bump when above the new previous entry. Never renumber the migration that already landed.

Make Targets

make verify-migrations  # Static migration contract gate (also in make check + CI)
make db-push            # Push schema directly (DEV ONLY, no journal)
make db-studio          # Open Drizzle Studio
make db-fix-journal     # Fix journal after migration consolidation

Key Files

FileRole
packages/db/src/schema.tsSchema source of truth
packages/db/drizzle/*.sqlMigration SQL files
packages/db/drizzle/meta/_journal.jsonMigration journal
packages/db/src/migrate.tsProgrammatic runner
packages/api/src/index.ts:302Auto-migrate on startup
scripts/verify-migration-contract.tsStatic contract gate (make check + CI)
scripts/verify-schema-drift.tsLive-DB drift audit — a DIFFERENT tool

NEVER

  • Use drizzle-kit push in CI or production
  • Edit, delete, or renumber migration files that have been deployed (they are immutable once on the base branch — the gate enforces byte-identity)
  • Run drizzle-kit generate or regenerate meta/ snapshots (frozen at 0026; see "Why migrations are hand-written")
  • Squash migrations without a journal fix script
  • Mix push and migrate in the same environment

Data Migrations

make migrate-messages-dry  # Dry run: events → messages
make migrate-messages      # Live migration

Git Workflow — AI-First with Public Verification

The Simple Rule

New work = PR to dev. Fixes = direct commit to dev.

main (public verification) <── dev <── feature PRs (auto-merge)
              ▲               |
          human PR             └── direct commits (fixes/hotfixes)

Promotion is a carry-exact, direct dev → main PR that a human merges. dev is the integration branch; main is the protected public verification branch, and the version is never re-bumped during promotion.

The main workflow is verification-only and checks the already-published immutable candidate without building, publishing, retagging, or changing a runtime. hml.omni.khal.ai is the legacy HML endpoint. This change neither mutates nor cleans it up. The endpoint and its HML runtime and configuration files remain legacy/reference-only, not an active public branch, tag, channel, or gate. Production authority is separate/private and lives outside this public repository.

Branch Roles

BranchPurposeWho commitsProtection
mainRead-only verification of the reviewed public candidateHuman merges dev → main promotion PRPR-only, all checks required
devIntegrationAgent + PRsDirect commits OK, PRs need checks
feat/*New featuresWorktrees, PR to devAuto-merge when green
fix/*Bug fixesDirect on dev or worktree—

Promotion (dev → main)

Open when dev is ahead of main and green. Carry-exact (no version re-bump). A human merges after CI passes; the protected main path then verifies the fixed candidate and existing public artifacts without publishing anything.

Conventional Commits (Required)

All commits must follow the format: type(scope): description

TypeWhen
featNew feature
fixBug fix
choreMaintenance, deps, config
docsDocumentation only
refactorCode restructuring
testAdding/fixing tests
ciCI/CD changes

Examples:

feat(api): add batch message endpoint
fix(lint): resolve biome warnings in channel-telegram
chore(merge): resolve conflicts with main
ci(rolling-pr): update workflow permissions

Enforcement: commitlint via husky (local) + GitHub Actions (CI gate)

Zero-Tolerance Quality

  • No warnings — biome strict mode, --error-on-warnings
  • No skips — monitor test output for .skip patterns
  • No failures — CI must be green; fix red states immediately

Agent Responsibilities

  1. Monitor all gates — zero tolerance for warnings/skips/failures
  2. Fix issues — direct commit to dev with conventional format
  3. Resolve conflicts — git merge origin/main (merge commits, not rebase)
  4. Notify human — label + channel message when rolling PR is green

Public Release Boundary

  • Integration versioning happens on dev.
  • Omni's DSH plugins (apps/omni-channels-dsh, apps/omni-canvas-dsh) carry the Omni version of their last change: a cut moves a plugin only when its folder changed since its version's tag. Each declares the oldest Omni it needs in engines.omni, checked against that release's API (scripts/plugin-compat.ts); a minimum above the repo's version means "the release this change is cut in".
  • A PR to dev labelled no-release skips the dev release: version.yml auto-version does not run, so no bump, tag, npm next, dev image or release candidate follows; the next normal merge's bump carries the change. Use it for docs-only, docs/rfcs and .genie-only PRs. A PR that changes code, workflows, packages or plugins must never carry it. The label only skips; an unlabelled merge behaves exactly as before.
  • The reviewed version carries unchanged into main.
  • The public main path only verifies existing immutable artifacts.
  • Production release and runtime decisions belong to the separate/private authority.

Technical Never Do

  • Don't code on main — it is the protected public verification branch, rolling PR only. Use dev for development, feat/* for features. If git branch --show-current returns main, STOP immediately.
  • Don't use non-conventional commit messages (all commits must be type(scope): description)
  • Don't create nightly branches (deprecated — use feature PR to dev flow)
  • Don't use npm/yarn/pnpm (use Bun exclusively)
  • Don't mix channel logic in core (channels are plugins)
  • Don't skip event publishing for state changes
  • Don't use raw SQL (use Drizzle)
  • Don't create REST endpoints without OpenAPI docs
  • Don't skip Zod validation on external inputs
  • Don't hardcode channel-specific behavior in core
  • Don't use any types
  • Don't leave uncommitted work
  • Don't stop without pushing
  • Don't bypass make commands for common tasks

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.