Haraka Agent Skills & AI Rules

GitHub

A fast, highly extensible, and event driven SMTP server

5,608 stars JavaScript JavaScript 1 file Β· ~6,659 tokens #dkim#haraka#javascript#mta#nodejs#smtp#spf
RAW Rules
34 Discovered Rules β€’ ~14,341 Estimated Tokens
Quick:
Loading skill content...

AI Agent Protocol & Instructions

AGENTS.md root-instruction ~2,183 tokens
Raw
<!-- agents-version: 3 -->

# AGENTS.md

Shared instructions for every coding agent in this repo. The tool-specific files
(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.
If a package-level instruction file exists, it is authoritative for that package.

## Repository model

- This file is shared across every Haraka repository. It describes the whole family β€” not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.
- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.
- The family:
  - `Haraka` β€” core SMTP server (has `./run_tests`).
  - `haraka-plugin-<name>` β€” optional plugins; may depend on each other (e.g. bounce β†’ spf).
  - `haraka-config` β€” config loader with hot-reload.
  - `haraka-results`, `haraka-notes` β€” per-connection result / note tracking.
  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` β€” shared libraries.
  - `@haraka/email-address` β€” the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).
  - `@haraka/eslint-config`, `haraka-test-fixtures`.

## Working agreement

- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.
- Preserve compatibility; break it only for an explicit, stated reason.
- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.

## Source control

- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.
- create diffs; the human reviews, commits, and pushes.
- No trailers on commit messages or PR bodies.
- When asked to generate a PR:
  - keep it succinct, terse, and DRY
  - Post a link to it in the console
- Document changes relative to main in CHANGELOG.md
  - one clear concise entry per change, < 50 chars
  - prefer Conventional Commit format, imperative mood.
  - Rationale belongs in the code or PR, not the bullet.

## Coding standards

- Target current Node LTS; prefer ES2024 over legacy patterns.
- Existing code is CommonJS (`require`/`exports`) β€” match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).
- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` β†’ `require('node:fs')`).
- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.
- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.

## Concision

- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.
- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.
- Cut scaffolding, keep facts.

Never write these:

- **Announcements.** "One question:", "Two reasons:", "The point is:", "Here's the thing:". Say the thing. If you announce a count, the count must be right β€” so don't announce one.
- **Justification tails.** A clause after a comma or dash that argues for the clause before it: "…and a guess there is worse than none", "…which is the failure this exists to prevent".
- **The contrast tic.** "X, not Y" and "X rather than Y" where Y is obvious. State X.
- **Clauses doing an adjective's job.** "a setting nobody has written" is "an unwritten setting"; "a copy nobody has opened" is "an unopened copy"; "still has to" is "must". A state with a name takes the name.
- **Closing morals.** A final sentence restating the paragraph's point.

## Comments

- Keep only WHY comments β€” a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.
- Delete WHAT comments that restate the code.
- When a rename makes a comment redundant, delete it.
- Never narrate history or audit findings.
- Never count the code β€” counts rot.
- Prefer self-documenting code: a better name beats any comment.

## Haraka plugins

- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.
- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.
- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.
- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early β€” return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).
- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line β€” don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.
- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:
  ```js
  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())
  ```
- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam β€” a swappable function whose default is the real implementation β€” so tests run without mocks.
- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.

## Testing

- Test real behavior and observable outcomes β€” `results`, return codes, emitted headers, side effects β€” not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.
- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test β€” that yields green tests proving nothing.
- For bug fixes, add a failing test first, then fix.
- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.
- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).
- Run the package's `lint`, `prettier`, and `format` before handoff.

## Commands (run inside the target package)

- Test: `npm test`. Single file: `node --test test/path/to/file.js`.
- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.
- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.
- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.
- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.

## Package script parity

- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:
  ```jsonc
  "test:coverage": "node --test --experimental-test-coverage",
  "test:coverage:lcov": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info"
  ```

## Repo badges

- Code climate is deprecated. Update with qlty.sh instead.
- The canonical format for badges should be:
  - Top of README.md:
    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]
  - Bottom of README.md:
    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg
    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml
    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg
    - [cov-url]: https://codecov.io/github/haraka/<name>
    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg
    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>