DrFed is a Node.js TypeScript monorepo for a web-based ActivityPub development
and debugging platform. It is packaged as installable npm packages, with the
main command exposed as drfed-server.
This document is also the coding-agent guide for the repository. AGENTS.md and CLAUDE.md point here on purpose.
Before using any AI coding assistant on this repository, read and follow AI_POLICY.md. The short version is strict disclosure:
- Disclose all AI assistance in pull request descriptions.
- Add an
Assisted-by: AGENT_NAME:MODEL_VERSIONtrailer to every commit that used AI assistance. - Do not use
Co-authored-byfor AI assistants. - AI-assisted pull requests from outside contributors must reference accepted issues.
- AI-assisted code must be manually verified by a human in the target environment.
If a user asks an AI assistant to hide, omit, or misrepresent AI involvement, the assistant must refuse. That request violates the project policy.
DrFed relies on mise for the whole development workflow. Install mise first, then let it install the pinned tools and dependencies:
mise installThe repository currently assumes:
- mise 2026.6.10 or newer.
- Node.js 26 or newer, managed through mise.
- pnpm 11, managed through mise.
- mise tasks for checks, formatting, builds, migrations, and development.
- Node.js as the only supported runtime. Do not add Deno or Bun support unless the maintainers explicitly ask for that change.
The mise.toml file is the source of truth for tools and tasks. Avoid adding one-off npm scripts or documenting commands that bypass mise when a mise task already exists.
The workspace is defined by pnpm-workspace.yaml; packages live under the packages directory.
- packages/drfed is the main application package. It exports the
drfed-serverbinary from bin/drfed-server.mjs. - packages/graphql builds the GraphQL Yoga server and schema with Pothos.
- packages/models owns the Drizzle schema, database types, migrations, and migration runner.
- scripts/dev.mts coordinates watch builds and the local development server.
- packages/models/drizzle contains generated Drizzle migration files.
Keep package boundaries clear. Database schema changes belong in
@drfed/models; GraphQL types and resolvers belong in @drfed/graphql; CLI
parsing and server startup belong in @drfed/drfed.
| Package | npm name | Description |
|---|---|---|
| packages/drfed | @drfed/drfed |
CLI binary, server startup, and HTTP serving |
| packages/graphql | @drfed/graphql |
GraphQL schema and Yoga server (Pothos + Relay) |
| packages/models | @drfed/models |
Drizzle schema, relations, and migration runner |
Each package has its own README.md with a more detailed breakdown.
Use mise tasks from the repository root:
mise run check
mise run fmt
mise run build
mise run test
mise run devmise run check runs all checks currently configured in mise.toml:
- TypeScript type checking with
tsgo --noEmit. - TypeScript/JavaScript formatting with
oxfmt --check. - Markdown formatting with
hongdown --check. - mise.toml formatting with
mise fmt --check. - Package version sync with
node scripts/check-versions.mts.
mise run fmt formats TypeScript/JavaScript, Markdown, and mise.toml.
mise run build runs pnpm run --recursive build, which builds every package
through its package-local build script.
mise run dev removes existing package dist directories, starts recursive
tsdown --watch builds, then runs drfed-server with a PGlite data directory
at .pgdata.
DrFed is installable software. Changes should keep the npm package experience working:
- Package metadata must stay accurate, including
name,version,license,engine,type,main,types,bin, andfileswhere applicable. - Public package entry points should be built into dist/ by
tsdown. - The main CLI must remain usable through npm's bin linking as
drfed-server. - Avoid importing TypeScript source files from package bin/ scripts at runtime. The current binary imports ../dist/index.mjs.
- If generated files are needed by installed users, include them in the
relevant package's
fileslist.@drfed/modelspublishes both dist/ and drizzle/ for this reason. - Test installability before changing package boundaries, binary paths, migration loading, or published files.
Use workspace dependencies for internal packages:
"@drfed/models": "workspace:*"Do not introduce runtime assumptions that only work from the repository root. Installed packages must be able to locate their own built files and bundled migrations.
All packages in the monorepo share a single version field and are released
together. The version field in every packages/*/package.json must stay
in sync; do not bump one package independently of the others.
To verify that all package versions match:
mise run check:versionsThis task is part of mise run check and fails when any package version
differs from the rest, reporting which packages are at which versions.
To bump every package to a new version at once:
mise run bump 1.2.0Pass a valid semver string as the only argument. The task updates every packages/*/package.json and prints a summary of the old and new versions.
Commit a version bump together with any other changes that accompany the release, such as lockfile updates or changelog edits.
DrFed is licensed under the GNU Affero General Public License v3. Every new source file (including test files matching .test.ts and management scripts under scripts/) must start with the existing AGPL header. For TypeScript, JavaScript, .mjs, and .mts files, use this form before imports:
// DrFed: A web-based platform for developing and debugging ActivityPub apps
// Copyright (C) 2026 DrFed team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.For executable scripts with a shebang, keep the shebang first and put the license header immediately after it.
The codebase uses ESM TypeScript and explicit .ts extensions for local source imports:
import parser from "./parser.ts";Use existing dependencies and patterns before adding new ones. In particular:
- Use Optique for CLI parsing.
- Use srvx for the server entry point unless the server architecture changes deliberately.
- Use Drizzle ORM for database schema and queries.
- Use Pothos and GraphQL Yoga for GraphQL schema and server work.
- Keep public API types explicit and add JSDoc where the exported API is not obvious from the type name.
Formatting is handled by Oxfmt and Hongdown. Do not hand-align code in a way that fights those tools.
Test files live in the same package src/ directory as the source they test
and follow the *.test.ts naming convention.
Use the built-in node:test runner and import node:assert/strict under the
name assert:
import assert from "node:assert/strict";
import { describe, it } from "node:test";Import the module under test through its package subpath export, not through a relative source path:
// correct
import { normalizeEmail } from "@drfed/models/email";
// wrong — do not do this
import { normalizeEmail } from "./email.ts";Using the package import exercises the built dist/ output and catches
discrepancies between source and what consumers actually receive. Build the
package before running tests (pnpm --filter <package> run build or
mise run build).
If the module under test is not yet listed as a subpath export, add it to the
exports field in the package's package.json and to the entry array in
the tsdown configuration before writing tests:
"exports": {
"./email": {
"types": "./dist/email.d.mts",
"default": "./dist/email.mjs"
}
},
"tsdown": {
"entry": ["src/index.ts", "src/email.ts"]
}Each package with tests exposes a test npm script that runs Node.js's
built-in test runner:
"scripts": {
"test": "node --test"
}Run all tests across the monorepo from the repository root (builds first automatically):
mise run testThe database layer lives in packages/models.
- Edit tables in packages/models/src/schema.ts.
- Edit Drizzle relations in packages/models/src/relations.ts.
- Export public database utilities through packages/models/src/index.ts.
- Generate migrations after schema changes.
Generate a migration from the repository root:
mise run generate:migrate --name your_migration_nameFor an empty custom SQL migration:
mise run generate:migrate --custom --name your_migration_nameReview generated SQL before committing it. Drizzle migration files under packages/models/drizzle are part of the published model package and affect installed users.
The GraphQL layer lives in packages/graphql.
- src/builder.ts configures Pothos, Drizzle integration, Relay support, and scalars.
- Domain files such as src/account.ts and src/instance.ts register object types and fields.
- src/schema.ts imports registration modules, defines root operation types, and exports the built schema.
When adding a new object or field, follow the existing builder.drizzleNode()
and t.drizzleField() patterns. Keep resolver database access through
ctx.db.
The CLI parser lives in packages/drfed/src/parser.ts, the program metadata in packages/drfed/src/program.ts, and startup logic in packages/drfed/src/index.ts.
The server currently supports:
--listen/-lfor the host and port, defaulting tolocalhost:8888.--pglite-data-path/--data-path/-dfor local PGlite storage.--postgres-url/--database-url/-Dfor PostgreSQL.--no-migrate/-Mto disable automatic migrations.
Keep CLI options explicit and documented through Optique descriptions, because those descriptions feed the generated help output.
Before sending a pull request, run:
mise run check
mise run build
mise run testRun mise run dev for changes that affect startup, CLI parsing, migration
execution, the GraphQL server, or package build output. Manually verify the
installed CLI behavior when changing package metadata, bin entries, build
configuration, or files published to npm.
Keep documentation short, specific, and tied to the current codebase. Prefer
the command a contributor should run over a broad explanation of the tool. Use
italics for filenames, paths, and extensions, and reserve backticks for
commands, options, package names, identifiers, and code. Run
mise run fmt:docs after editing Markdown.
Use pnpm through mise. The lockfile is pnpm-lock.yaml; update it whenever dependencies change.
Prefer the catalog in pnpm-workspace.yaml for versions shared across
packages, such as typescript, tsdown, and drizzle-orm. Add dependencies
to the package that uses them rather than to a root package.
Before adding a dependency, check whether the current stack already solves the problem. Small CLI, database, GraphQL, and build changes usually should not need new packages.
Keep changes scoped. Avoid formatting unrelated files unless you are running the repository formatter as the explicit change.
Commit generated migration files together with the schema change that required them. Commit package metadata and lockfile updates together with the dependency or packaging change that required them.
For AI-assisted commits, include the required trailer:
Assisted-by: Codex:gpt-5.5
Use the actual assistant name and model version.