Skip to content

Latest commit

 

History

History
435 lines (343 loc) · 24.4 KB

AGENTS.md

File metadata and controls

435 lines (343 loc) · 24.4 KB

AGENTS.md

Repository instructions for AI coding agents working on TripBot. This file applies to the entire repository unless a more specific AGENTS.md exists below the file being edited.

Instruction priority

  1. Follow the user's current request.
  2. Follow the nearest applicable AGENTS.md.
  3. Preserve established repository behavior and conventions.
  4. Prefer the smallest complete change that solves the requested problem.

If instructions conflict or a requested operation could affect production data, Discord users, credentials, or an external service, stop and ask before taking the risky action.

Non-negotiable rules

  • Use Node.js with npm for dependency installation and package command execution. Do not use Bun, Yarn, or pnpm.
  • Prefer existing package.json scripts when they perform the required task without creating unrelated changes.
  • Add or update Vitest tests for new or changed behavior as part of the change, without waiting to be asked. Follow the existing layout and mocks in src/vitest/, and run the relevant tests before handing off.
  • Do not edit generated Prisma clients under src/prisma/*/generated/.
  • Do not edit archive, legacy, placeholder, Matrix, or Telegram code (including the archived IRC bot under src/irc/archive/) unless the task specifically targets it.
  • Never commit .env, tokens, passwords, private URLs, production identifiers, database dumps, or user data.
  • Never run a production migration, deploy Discord commands, push a branch, or mutate an external service unless the user explicitly requested that operation.
  • Preserve unrelated user changes. Never discard, reset, overwrite, or reformat files outside the requested scope.
  • Do not use broad auto-fix commands against the whole repository. They can create large unrelated diffs.

Project summary

TripBot is a strict TypeScript application whose primary runtime is Node.js. It contains:

  • a Discord.js bot;
  • an Express API;
  • a PostgreSQL TripBot database accessed through Prisma;
  • a read-oriented Moodle MariaDB integration with its own Prisma schema;
  • Docker-based local services;
  • Vitest tests, covering the API, Discord command, global business-logic, and IRC layers.

TripBot provides harm-reduction and community tooling. Treat changes to drug information, interactions, dosage text, crisis resources, moderation, privacy, and user records as high-impact. Preserve established wording and data sources; do not invent medical claims or silently weaken safety checks.

Toolchain and repository reality

  • Required Node version: 22.14.0 from .nvmrc; package.json allows Node >=22.14.0.
  • Package manager: npm.
  • Application runtime: Node.js.
  • TypeScript target: ES2022.
  • Module format: CommonJS for application code.
  • Type checking: strict.
  • Style: ESLint with Airbnb TypeScript and SonarJS rules.
  • Database toolkit: Prisma 7 with PostgreSQL and MariaDB adapters.
  • Test runner: Vitest.
  • Dependency lockfile: package-lock.json.
  • Package scripts, Docker files, hooks, and CI workflows use npm/npx.

Initial workflow

Before editing:

  1. Run git status --short --branch and note pre-existing changes.
  2. Read this file and any more specific AGENTS.md in the target subtree.
  3. Read the relevant implementation, its callers, adjacent types, and existing tests before choosing a fix.
  4. Check package.json, tsconfig.json, .eslintrc, and the relevant Prisma schema only when the task touches them.
  5. Keep the requested scope narrow. Do not turn a targeted fix into cleanup or modernization work.

After editing:

  1. Review git diff -- <changed paths> for correctness and accidental churn.
  2. Run the smallest relevant verification set from this document.
  3. Run git diff --check.
  4. Confirm git status --short contains only intended files or clearly identify pre-existing changes.
  5. Report what changed, what was verified, and any verification that could not be run.

Installing dependencies

For an unchanged checkout, perform the reproducible lockfile install used by CI and Docker:

npm ci

Avoid dependency changes unless the task requires them. If a dependency must be added, removed, or upgraded:

  • use npm install <package>, npm install --save-dev <package>, or npm uninstall <package>;
  • commit both package.json and package-lock.json when both change;
  • do not hand-edit dependency versions in only one of those files;
  • run npm ci after the change to confirm the lockfile is reproducible.

Do not create or commit bun.lock, yarn.lock, or pnpm-lock.yaml.

Common Node.js/npm commands

Use npm run <script> for repository scripts and npx <binary> for locally installed command-line tools.

Read-only checks

# Type-check without writing build output
npx tsc --noEmit --pretty false

# Lint only files touched by the task
npx eslint --ext .ts,.js path/to/file.ts

# Lint the repository without auto-fixing
npx eslint --ext .ts,.js .

# Run the active Vitest suite
npm run tripbot:test

# Run one existing test file (also works outside the active include globs)
npx vitest run ./path/to/file.test.ts

# Run with coverage
npx vitest run --coverage

Use targeted lint and tests first. Repository-wide lint may expose unrelated pre-existing failures.

Build

Prisma clients must exist before a full type-check or build:

npx prisma generate --schema ./src/prisma/tripbot/schema.prisma
npx prisma generate --schema ./src/prisma/moodle/schema.prisma
npx tsc
npx tsc-alias
npx tsc --project tsconfig.prisma.json

Build output goes to build/ and must not be committed.

Local environment and database

Create a local .env from .env.example, fill in only the values needed for the task, and never commit it.

# Start only the local PostgreSQL service
npm run db:start

# Inspect its logs
npm run db:logs

# Validate and format the default TripBot schema
npx prisma validate
npx prisma format

# Apply already-committed TripBot migrations to the configured local database
npx prisma migrate deploy

prisma.config.ts selects the TripBot schema by default. It selects Moodle only when PRISMA_SCHEMA=moodle. In PowerShell, scope that variable carefully and remove it after the command:

$env:PRISMA_SCHEMA = 'moodle'
npx prisma generate
Remove-Item Env:PRISMA_SCHEMA

Database commands can mutate data. Use only a disposable/local database unless the user explicitly authorizes another target. Do not run prisma db push, prisma migrate reset, or destructive SQL as routine verification.

Running services

  • The development bot runs inside the tripbot devcontainer (.devcontainer/devcontainer.json, the tripbot service in docker-compose.yml), not on the host. Its postStartCommand runs npm run tripbot:start in a tmux session. Hostnames such as tripbot_database only resolve on the Docker network, so starting the bot on the host fails to reach the database. Run bot scripts from the devcontainer terminal, or from the host with docker exec -it tripbot npm run tripbot:restart (likewise tripbot:logs, tripbot:stop).
  • The container keeps its own node_modules volume. After a dependency change, run npm ci inside the container (docker exec tripbot npm ci) or rebuild the devcontainer; a host install does not reach it.
  • The dev database uses the named volume tripbot_database_data, so it persists across rebuilds. Removing that volume (docker volume rm tripbot_database_data) wipes local data; only do so when the user asks.
  • Use npm run <script> for the scripts defined in package.json.
  • tripbot:start, tripbot:dev, and tripbot:restart assume tmux, so they are not native Windows development commands.
  • db:generate and related scripts use POSIX inline environment-variable syntax. On Windows PowerShell, use explicit npx prisma commands and $env:PRISMA_SCHEMA as shown above.
  • Docker Compose is the preferred portable way to start the database service.

Deploying Discord commands changes the registered commands for a real application. Only when explicitly requested and the target environment is confirmed, use:

npm run tripbot:deployCommands

Repository map

src/start.ts                         Process entry point; starts API, database setup, and Discord
src/api/                             Express application, middleware, v1 and v2 routes
src/discord/discord.ts               Discord client startup
src/discord/commands/global/         Commands available across supported Discord contexts
src/discord/commands/guild/          Guild-oriented Discord commands
src/discord/commands/archive/        Archived commands; leave untouched unless requested
src/discord/events/                  Discord event handlers
src/discord/utils/                   Command deployment, context, embeds, and Discord helpers
src/irc/irc.ts                       IRC connection to TripSit's Ergo servers
src/irc/bridge.ts                    IRC side of the chat bridge (RELAYMSG, send queue)
src/global/bridge/                   Platform-independent chat bridge core and channel list
src/discord/@types/                  Discord command contracts and type augmentation
src/global/commands/                 Platform-independent business logic, conventionally g.*
src/global/utils/                    Shared utilities, environment, logging, experience, and database updates
src/global/@types/                   Global declarations and shared ambient types
src/prisma/tripbot/schema.prisma      Primary PostgreSQL schema
src/prisma/tripbot/migrations/        Primary database migration history
src/prisma/tripbot/client.ts          PostgreSQL Prisma client adapter
src/prisma/moodle/schema.prisma       Moodle MariaDB schema
src/prisma/moodle/client.ts           Moodle Prisma client adapter
src/prisma/*/generated/               Generated clients; ignored and never edited manually
vitest.config.mts                     Active Vitest configuration
src/vitest/                           Shared test infrastructure (Discord + Prisma mocks)
src/discord/tests/                    Discord command tests (mirrors commands/{global,guild})
src/global/tests/                     Global business-logic (g.*) tests
src/irc/tests/                        IRC connection and bridge tests
src/docker/                           Container build, startup, and wait scripts
assets/                               Runtime data, fonts, and images
build/                                Generated TypeScript output; ignored

Inactive or partially maintained areas such as Matrix, Telegram, archive, legacy, placeholder, and any not-yet-migrated flat files directly under src/discord/tests/ are excluded by TypeScript, ESLint, CI, or Vitest patterns. Do not assume a repository-wide command validates those areas. src/irc/ (outside archive/) is active code: it is type-checked, linted, tested, and covered by CI.

Architecture rules

Keep shared logic separate from Discord UI

The intended command flow is:

Discord interaction -> src/discord/commands/.../d.* -> src/global/commands/g.* -> Prisma/external service
  • Keep parsing, embeds, buttons, modals, permissions, and interaction lifecycle in d.* command files.
  • Put reusable business logic in src/global/commands/g.* when it can be platform-independent.
  • Do not make global command logic depend on Discord interaction objects without a strong existing precedent.
  • Reuse existing helpers rather than duplicating database or formatting logic.

Discord command discovery

src/discord/commands/index.ts dynamically loads .ts/.js modules from the global and guild command folders and registers an exported object that has a .data property.

When adding or changing a command:

  • start from the nearest real command, not only a template;
  • use the SlashCommand, MessageCommand, or UserCommand contract from src/discord/@types/commandDef.ts;
  • keep the command builder name and exported command object coherent;
  • place it in global/ or guild/ according to its intended Discord contexts;
  • set contexts and integration types deliberately; do not broaden availability accidentally;
  • preserve permission, role, guild, and environment checks;
  • log command context using the established commandContext pattern where adjacent commands do so;
  • defer or reply exactly once, then use editReply/followUp consistently;
  • use MessageFlags.Ephemeral where the surrounding code does, especially for private or sensitive responses;
  • return the boolean required by the command type;
  • redeploy command definitions only when the user explicitly requests it.

If options, subcommands, contexts, integration types, or descriptions change, state in the handoff that Discord command registration must be redeployed.

IRC (Ergo)

TripSit's IRC network runs Ergo (ergochat): irc.tripsit.io for development and irc.tripsit.me for production, selected by NODE_ENV unless IRC_SERVER overrides it. Ergo is a single daemon with built-in services, so do not give Anope, Atheme, or other separate-services instructions; confirm commands with /msg NickServ HELP or /msg ChanServ HELP on the server when unsure.

  • The bot uses irc-framework (src/irc/irc.ts), connects with TLS on 6697, and authenticates with SASL PLAIN using IRC_USERNAME/IRC_PASSWORD. It only connects when IRC_PASSWORD is set. When IRC_OPER_NAME/IRC_OPER_PASSWORD are set it sends OPER after registering.
  • The Ergo server config (ircd.yaml, ircd.db) lives on the IRC hosts, not in a repository; the tripsit/irc repo only holds its docker-compose.yml. Give the user config snippets to apply rather than assuming file access.
  • Relevant server capabilities on irc.tripsit.io: draft/message-redaction (deletes, via REDACT), draft/relaymsg with separator / (RELAYMSG lets the bridge post as name/d-style nicks), message-tags, echo-message, draft/multiline, and draft/chathistory. There is no message-edit capability, so IRC cannot receive true edits. Re-check with CAP LS 302 before relying on a capability.
  • Ergo supports the IRCv3 capabilities the bridge relies on (sasl, account-tag, extended-join, server-time). Use the NickServ account from account-tag/extended-join as the IRC identity, never the nick alone.
  • Registering an account: while using the nick, /msg NickServ REGISTER <password> [email]. If email verification is enabled, finish with /msg NickServ VERIFY <account> <code>. An oper with the accreg capability can instead run /msg NickServ SAREGISTER <account> <password>, which skips verification.
  • Channel privileges come from ChanServ, not from oper status: /msg ChanServ REGISTER #channel, then persistent modes with /msg ChanServ AMODE #channel +o <account> (Ergo uses AMODE, not FLAGS/ACCESS).
  • Do not give the bot a full oper block. It uses the narrow tripbot oper class (nofakelag, relaymsg); add a capability only when a feature needs it (for example ban for network bans) and have a human apply the change.

Chat bridge (Discord, IRC, Matrix)

TripBot replaces Matterbridge for relaying chat between platforms.

  • src/global/bridge/bridge.ts routes a BridgeMessage to every other platform with a registered BridgeTarget (registerBridgeTarget). Platform adapters own formatting and delivery: src/discord/utils/chatBridge.ts (webhook named TripSit Chat Bridge, separate from the guild-to-guild Tripsit Bridge in utils/bridge.ts) and src/irc/bridge.ts. Matrix has room IDs in the channel list but is deliberately not bridged yet.
  • src/global/bridge/bridge.gateways.ts is the channel list ported from Matterbridge. Discord entries name an env.config.ts key, never a raw ID, so production and development resolve separately; an empty key value means the channel is not bridged in that environment. IRC joins every listed channel unless IRC_CHANNELS overrides it.
  • Behavior intentionally mirrors the old Matterbridge config: relayed IRC nicks are name/<label> via RELAYMSG (falling back to <name/label> text), 400-byte lines sent one second apart with a 30-line queue, markdown stripped for IRC, server nicknames on the Discord side, (edited) appended to relayed edits, @everyone/@here neutralized, and the old bot nicks ignored. Keep loop prevention intact: adapters must ignore their own output.
  • Discord deletes are relayed to IRC with REDACT, using the msgids from echo-message echoes of the relayed lines. Each IRC channel has its own send queue.
  • Relayed message content is user content: never log it.

Globals and aliases

The codebase intentionally exposes values such as db, log, f, environment data, and Discord state through global declarations. Check src/global/@types/ and startup/logging code before replacing an established global with a new import or dependency-injection pattern.

Source aliases are defined in tsconfig.json:

  • @global/* -> src/global/*
  • @discord/* -> src/discord/*
  • @database/* -> src/prisma/*
  • @db/tripbot -> generated TripBot client
  • @db/moodle -> generated Moodle client

Application builds require tsc-alias because runtime aliases point into build/. Follow the import style of nearby files and do not introduce a second alias scheme.

Express API

  • src/api/app.ts configures middleware and mounts /api/tripsit, /api/v1, and /api/v2.
  • Put route handling in *.routes.ts and database/query helpers in *.queries.ts where that pattern already exists.
  • Preserve rate limiting, Helmet, body-size limits, authentication middleware, centralized not-found handling, and error handling.
  • Validate and normalize request input at the boundary.
  • Do not return secrets, raw internal errors, private moderation data, or more user data than the existing contract.
  • Forward unexpected async errors to the established error handler rather than swallowing them.
  • Keep v1 compatibility unless the task explicitly authorizes a breaking API change.

Prisma and database changes

  • TripBot uses PostgreSQL; Moodle uses MariaDB/MySQL. Do not apply one schema's assumptions to the other.
  • Generated clients live beside each schema and are gitignored. Regenerate; never hand-edit them.
  • Import the primary client through the existing db global or src/prisma/tripbot/client.ts pattern.
  • Import Moodle through src/prisma/moodle/client.ts.
  • For a TripBot schema change, update src/prisma/tripbot/schema.prisma, create a named migration in src/prisma/tripbot/migrations/, format/validate, regenerate, and inspect the SQL.
  • Migration names must describe the change; never leave CHANGE_ME.
  • Do not rewrite or delete committed migrations unless the user explicitly asks and understands the compatibility cost.
  • Avoid db push for changes intended to ship; use migrations.
  • Preserve unique constraints, indexes, nullability, defaults, relation behavior, and existing data compatibility.
  • Treat the large Moodle schema as externally governed. Do not create TripBot migrations for Moodle or casually format the entire schema.

TypeScript and style conventions

  • Keep TypeScript strict; do not weaken tsconfig.json or add broad suppressions to make a change compile.
  • Prefer precise types and narrow unions over any, non-null assertions, and unsafe casts.
  • Follow nearby code style: two-space indentation, single quotes, semicolons, trailing commas in multiline structures, and a preferred maximum line length of 120.
  • Use async/await consistently and await operations whose completion affects behavior.
  • Handle nullable Discord/API/database values explicitly.
  • Keep exported names, default exports, filenames, and command names consistent with adjacent modules.
  • Preserve existing public behavior unless the request is explicitly a behavior change.
  • Avoid drive-by renames, import sorting, comment cleanup, or formatting unrelated code.
  • Add comments only for non-obvious constraints or reasoning; do not narrate straightforward code.
  • Do not introduce a new abstraction for one call site unless it materially improves correctness.
  • Prefer the existing logger over console.log; never log tokens, authorization headers, raw credentials, or sensitive user content.

Domain and safety constraints

  • Harm-reduction output must be conservative, factual, and consistent with the project's established data source.
  • Do not invent dose ranges, interaction severities, contraindications, crisis contacts, or medical conclusions.
  • Do not remove disclaimers, escalation language, privacy controls, moderation checks, or rate limits as “cleanup.”
  • Treat moderation actions, bans, appeals, reports, AI moderation, and role checks as security-sensitive.
  • Treat Discord IDs and public configuration differently from secrets, but do not expose identifiers unnecessarily.
  • Preserve user privacy in logs, API responses, errors, tests, fixtures, and screenshots.

Verification matrix

Choose checks proportional to the change; do not claim checks you did not run.

Change Minimum verification
Documentation only Review rendered/plain Markdown, git diff --check
TypeScript logic Targeted ESLint, npx tsc --noEmit --pretty false, new or updated tests for the change
Discord command Type-check, targeted ESLint, relevant existing test if configured, inspect interaction lifecycle
Command definition/options Discord-command checks plus note that deployment is required; do not deploy automatically
API route/query Type-check, targeted ESLint, targeted API Vitest file
Prisma schema prisma format, prisma validate, generate both affected clients, type-check, inspect migration SQL
Migration Schema checks plus apply to a disposable local database and verify data compatibility
Dependency/tooling npm ci, type-check, relevant tests, and confirm package-lock.json is coherent
Docker/runtime Build or start only the affected service, inspect logs, stop only containers started for the task

vitest.config.mts (repo root) matches and collects coverage across the API, Discord command, and global business-logic layers — see its test.include/test.exclude and test.coverage blocks for exact scope. A handful of legacy Discord test files directly under src/discord/tests/ predate the current mock scaffold and are excluded until migrated; treat them as reference only. Run a relevant existing test with npx vitest run <path> and report failures honestly. New tests go in the matching tests/ folder so the existing include globs pick them up. Test folders (src/vitest/, src/discord/tests/, src/global/tests/, src/irc/tests/) each hold a small tsconfig.json that extends tsconfig.vitest.json so editors type-check them with Vitest globals; add one when creating a new test folder.

If dependencies or generated Prisma clients are unavailable, say so clearly. Do not disguise an environment/setup failure as a source-code failure.

Git and change hygiene

  • Work on the current branch unless the user asks for a new one.
  • Do not commit, amend, rebase, merge, push, force-push, or open a pull request unless explicitly asked.
  • Never use destructive Git commands to clean the workspace.
  • Keep commits, when requested, focused and use the repository's concise imperative style.
  • Check both git diff and git diff --cached before committing.
  • Do not include generated build/, coverage, .env, generated Prisma clients, logs, caches, or local database data.
  • For PR conflict work, inspect the actual canonical PR and its real base before resolving conflicts; do not assume a fork's origin is the authoritative upstream.
  • Before opening a pull request from a feature branch, pull the latest uat (the usual PR base) into it first so the branch is up to date and the push won't be rejected as non-fast-forward. Prefer branching fresh from uat over rebasing an existing shared/pushed branch, since rewriting history on a branch others may have pulled is riskier.

Definition of done

A task is complete only when:

  • the requested behavior or artifact is fully implemented;
  • the diff is scoped and contains no accidental edits;
  • new or changed behavior has tests, and applicable types, lint rules, tests, schema checks, or runtime checks pass, or limitations are reported;
  • no secrets, generated output, local state, or unrelated lockfile changes were introduced;
  • externally visible follow-up work, such as a Discord command deployment or production migration, is called out but not performed without authorization;
  • the final handoff states the changed files, verification performed, and any remaining risk or blocker.