Skip to content

Contributing

Myrtille is built in the open under the Apache License 2.0, with no feature behind a paywall. Every contribution follows the same rules as the autonomous agents that build it. CONTRIBUTING.md at the root of the repository is the reference.

There is no contributor licence agreement. Every commit is signed off under the Developer Certificate of Origin, which certifies that you wrote the change or have the right to submit it under the licence of the project:

Terminal window
git commit -s -m "feat(adapter-coolify): detect started deployments"

The line Signed-off-by: Your Name <you@example.com> must match the author of the commit. CI rejects a commit without it.

The specification lives in docs/ and is the source of truth. Start with docs/README.md, then the architecture decisions in docs/02-adr/. Accepted decisions are binding: a change that contradicts one needs a new ADR first, written from the template docs/02-adr/ADR-0000-gabarit.md.

A courier wraps one external tool. It is the easiest way in, and it never touches the town or the mail: it describes its capabilities, starts and stops, reconciles the full truth of its tool, turns incoming webhooks into events of the catalogue, and runs the actions it declares. The contract is the Courier interface of @myrtillehq/core, and every courier passes its contract kit:

import { describeCourierContract } from '@myrtillehq/core/testing';
// From packages/adapter-webhook/src/webhook-courier.test.ts
describeCourierContract('webhook', {
create: () => new WebhookCourier({ id: 'ops-hooks', fetch: fakeFetch() }),
context: { settings: SETTINGS, secrets: { [SIGNING_SECRET]: SECRET }, instance: 'main' },
minCitizens: 2,
inbound: [inbound(JSON.stringify({ time: RECEIVED, citizen: 'tiktok-generator', state: 'running' }))],
declaredAction: (citizens) => ({
id: 'contract-run-1',
type: 'task.run',
citizenId: citizens.find((c) => c.actions.includes('task.run'))?.id ?? TIKTOK,
params: {},
dryRun: false,
requestedBy: { kind: 'user', id: 'user-1', role: 'operator' },
}),
executions: (courier) => (courier as WebhookCourier).sent,
});

create builds a fresh courier for each test, backed by recorded fixtures; context gives a placeholder for every credential the manifest declares; inbound gives recorded webhooks when the courier receives some; executions counts the side effects of the courier, to prove that an action runs once.

The kit checks that the courier:

  • declares a valid manifest, with a placeholder for every credential;
  • starts and stops, can be stopped twice, and does nothing once stopped;
  • reconciles valid citizens with unique ids;
  • emits only valid events of the catalogue that its manifest declares;
  • gives a webhook sent again the same idempotency key, and distinct events distinct keys;
  • rejects a malformed webhook with a Myrtille error;
  • runs a declared action, refuses an undeclared one, and runs the same action id only once;
  • never leaks a credential in its events, citizens, results, logs or errors, in any encoding.

Tests use recorded fixtures, never live calls, and secrets in fixtures are placeholders such as tok_EXAMPLE. A courier allowlists every field it keeps from the answers of its tool. Its package, @myrtillehq/adapter-<id>, depends only on @myrtillehq/core, and its README has three sections: “What you need on the tool side”, “Minimum permissions” and “Known limits”. Read packages/adapter-webhook for a small courier and packages/adapter-coolify for a complete one.

  • One change, one branch, one pull request, with a title in Conventional Commits.
  • Fill in the template: what, why (the requirement and ADR identifiers), how, the output of the checks, risks and rollback.
  • Add a changeset (pnpm changeset) when a published package changes.
  • Never commit a secret, a .env file or a real token.
  • Every string of the interface lives in apps/town/locales/en.json and fr.json: English by default, French complete.
  • Pull requests are squash-merged after review and green checks.

You need Node 22.19 or later on the 22 line, or 24.6 or later, pnpm through corepack enable, and gitleaks on your PATH. At the root of the repository:

Terminal window
pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm e2e
pnpm check:deps
pnpm check:secrets
pnpm check:docs

pnpm dev runs the demo town Quillmoor on your machine: the town hall in mock mode on http://127.0.0.1:7420 (the demo account and its password are printed on the console), the town on http://127.0.0.1:5173 and this site on http://localhost:4321. The first run of pnpm e2e needs pnpm exec playwright install --with-deps chromium.

The project follows the Contributor Covenant, in CODE_OF_CONDUCT.md.