---
title: "Contribution guide"
description: "Set up the repository, run the tests and learn how to work on a provider."
canonical_url: "https://forges.link/contributing/guidelines"
---
# Contribution guide

> Set up the repository, run the tests and learn how to work on a provider.

We welcome contributions to `forges` of any size and skill level. Whether you want to fix a typo, report a bug, add support for another forge or improve the documentation, you are helping everyone who builds on the library, and we are happy to guide you through your first pull request. By taking part, you agree to the [Code of Conduct](https://github.com/danielroe/forges/blob/main/CODE_OF_CONDUCT.md).

<tip>

New to contributing? The [first-contributions](https://github.com/firstcontributions/first-contributions) project walks you through making your first pull request.

</tip>

## Quick guide

### Prerequisites

You need Node.js 22.18 or later and [pnpm](https://pnpm.io). The repository pins the versions that it is developed with in `devEngines`, so pnpm can download them for you.

### Setting up your local repo

Fork the repository, clone your fork and install the dependencies from the root of the project. `forges` is a pnpm workspace that also contains the docs, the playground and the examples, so always run `pnpm install` at the top level.

```sh
git clone https://github.com/<your-username>/forges.git
cd forges
pnpm install
pnpm test:unit
```

If the unit tests pass, you have a working base to start from.

The playground follows the Nuxt nightly through its `5x` tag. When you change a dependency of the root package, install with `pnpm install --filter .`, so that pnpm leaves the playground's Nuxt version in `pnpm-lock.yaml` as it is.

### Development

For active development, `pnpm dev` runs the unit tests in watch mode and re-runs the ones that your changes affect. These are the commands you will use most often:

<table>
<thead>
  <tr>
    <th>
      Command
    </th>
    
    <th>
      What it does
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        pnpm dev
      </code>
    </td>
    
    <td>
      Runs the unit tests in watch mode
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm build
      </code>
    </td>
    
    <td>
      Builds the package into <code>
        dist
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm build:docs
      </code>
    </td>
    
    <td>
      Builds the docs site. The docs tests and the link check need it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test
      </code>
    </td>
    
    <td>
      Runs the unit tests, the capability check, the type check and knip
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:unit
      </code>
    </td>
    
    <td>
      Runs the unit tests once
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:docs
      </code>
    </td>
    
    <td>
      Tests the built docs site. Build the docs first
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:links
      </code>
    </td>
    
    <td>
      Checks the links of the Markdown and the built docs site with lychee. Build the docs first
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:types
      </code>
    </td>
    
    <td>
      Type-checks <code>
        src
      </code>
      
      , <code>
        test
      </code>
      
       and <code>
        scripts
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:capabilities
      </code>
    </td>
    
    <td>
      Fails for a capability whose declared level does not match what the tests verify against the forge
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:knip
      </code>
    </td>
    
    <td>
      Finds unused files, exports and dependencies
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm test:size
      </code>
    </td>
    
    <td>
      Reports browser bundle sizes, and checks Lite tree-shaking, the budget of the provider-free bundle and the sizes the docs quote. Run <code>
        pnpm build
      </code>
      
       first; <code>
        --update-docs
      </code>
      
       updates those sizes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm lint
      </code>
      
      , <code>
        pnpm lint:fix
      </code>
    </td>
    
    <td>
      Lints, and fixes what it can
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm capability-matrix
      </code>
    </td>
    
    <td>
      Updates the capability matrix and the other tables of the docs
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm capability-diff [ref]
      </code>
    </td>
    
    <td>
      Shows how the capabilities differ from <code>
        ref
      </code>
      
      , <code>
        main
      </code>
      
       by default
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm docs:api
      </code>
    </td>
    
    <td>
      Updates the API reference. <code>
        --list
      </code>
      
       shows what lacks a description, and <code>
        --check
      </code>
      
       fails for it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm record-fixtures <forge>
      </code>
    </td>
    
    <td>
      Records responses from a live forge
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm recorded-coverage
      </code>
    </td>
    
    <td>
      Lists the capabilities that no recording of a live forge exercises
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm dev:docs
      </code>
    </td>
    
    <td>
      Starts the docs site
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm dev:playground
      </code>
    </td>
    
    <td>
      Starts the playground
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pnpm check:all
      </code>
    </td>
    
    <td>
      Runs lint, tests, a build and the size check
    </td>
  </tr>
</tbody>
</table>

### Testing your changes

There are a few ways to check that a change works as you expect:

1. Write a test and run it. This is the best choice for a specific fix. The unit tests live in `test/unit`, and `pnpm dev` runs them as you edit.
2. Run one of the examples. The projects in `examples/` use the local source of `forges`, so you can see the effect of your change in a real program. For example, `pnpm --filter @forges-examples/inbox-cli start` starts the inbox CLI.
3. Try it in the playground. `pnpm dev:playground` starts a Nuxt app that uses the local source of `forges`.

For a small change a test is usually enough. For a bigger one, it can help to try it in the playground or an example as well.

### Running tests

```sh
# run the unit tests once
pnpm test:unit
# run a single test file
pnpm vitest run --project unit test/unit/<name>.test.ts
# run the unit tests, the capability check, the type check and knip
pnpm test
# run every check that we run on each commit
pnpm check:all
```

The tests of the docs site run against a build of it. To run them, build the docs first with `pnpm build:docs`, and then run `pnpm test:docs`.

## Project layout

<table>
<thead>
  <tr>
    <th>
      Path
    </th>
    
    <th>
      Content
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        src/<forge>/
      </code>
    </td>
    
    <td>
      One provider each: its factory, normalisation and webhook handling
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        src/model.ts
      </code>
      
      , <code>
        src/provider.ts
      </code>
      
      , <code>
        src/define.ts
      </code>
    </td>
    
    <td>
      The shared model, the provider interface and <code>
        defineForgeProvider()
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        src/kit.ts
      </code>
    </td>
    
    <td>
      The <code>
        forges/kit
      </code>
      
       entry: what provider authors import
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        src/fake/
      </code>
      
      , <code>
        src/testing/
      </code>
    </td>
    
    <td>
      The in-memory forge and the test helpers
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        test/
      </code>
    </td>
    
    <td>
      Unit tests, recorded fixtures and the shared recording steps
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        examples/
      </code>
    </td>
    
    <td>
      Small, tested projects that use the library
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        docs/
      </code>
    </td>
    
    <td>
      The documentation site
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        playground/
      </code>
    </td>
    
    <td>
      A Nuxt app for trying the library by hand
    </td>
  </tr>
</tbody>
</table>

## Working on a provider

To add a forge or an operation, start with [Write a provider](https://forges.link/contributing/write-a-provider). A few things are worth knowing before you begin.

Capabilities are verified by tests. Each provider declares what its forge supports, and a capability may only be `true`, `'experimental'` or `'emulated'` if a test verifies it against the forge's own responses: a recording of the live forge, or a fixture hand-authored from the forge's documentation. For an operation that no such test verifies yet, use `'unverified'`. `pnpm test:capabilities` fails when a declaration and the tests disagree in either direction. Use `'experimental'` only for an API that the forge marks unstable or in preview.

The same goes for anonymous providers: an anonymous provider may only declare a verified level for an operation that a test verifies without credentials. Record with `--anonymous`, and declare `false` without credentials for a read that the forge refuses.

Fixtures should come from real forges. For a new read, record a fixture with `pnpm record-fixtures <forge>` instead of writing one by hand. [Record fixtures](https://forges.link/contributing/record-fixtures) explains how.

Some of the documentation is generated. After you change the capabilities of a provider, run `pnpm capability-matrix` to update the capability tables in the docs. A test fails when they are out of date.

Providers also run in browsers. `forges` is meant to work in browsers as well as in Node.js, and it has no runtime dependencies to keep the bundle small. For this reason, providers don't import Node.js built-in modules, and adding a runtime dependency is something to discuss in an issue first. Source files are imported with the `.ts` extension, so that `node scripts/<name>.ts` runs without a build.

Finally, credentials must never leak: errors, warnings and `skipped` reasons must not contain one.

## Documentation

The documentation is a [Docus](https://docus.dev) site in `docs/`. Start it with `pnpm dev:docs`. The pages are Markdown files in `docs/content`.

Some of the text in the docs is generated, so don't edit it by hand:

- Text between `<!-- <marker>:start -->` and `<!-- <marker>:end -->` comments comes from `pnpm capability-matrix`.
- The pages of the [API reference](https://forges.link/reference/overview) and the [provider kit](https://forges.link/contributing/provider-kit) come from the comments on exported declarations in `src`. To change one of them, change the comment and run `pnpm docs:api`. `pnpm docs:api --list` shows what still has no description, and `--check` fails for it. `scripts/api-docs/config.ts` decides where a symbol appears, and a test fails for an export that no page selects.
- The steps in the credentials picker on [Configure from the environment](https://forges.link/guides/environment) live in `scripts/credential-steps.ts`. To verify a step, check it against the documentation of the forge, or against its source repository when the site is unavailable. Then add the URLs to `sources` and the date to `verified`. For a fact about `forges` itself, cite the path of the source file or page. The picker marks steps without `verified` as unverified, and `pnpm test:unit` lists them as to-dos.

### Comments on exports

Comments on exported declarations become the API reference, so write them in the style of the rest of the reference:

- Describe what the code does now. Leave out history and plans.
- Start a method or function with a verb in the third person: "Lists the threads of a repository." Describe a property with a noun phrase, and a boolean with "Whether…" when it needs a sentence.
- Leave a property without a comment when its name says it all, such as `title` or `createdAt`. Every export and every method needs one.
- Put a default in `@default`, without backticks: `@default 'open'`. Write a default that isn't a literal in the sentence.
- Describe a parameter with `@param name Description` when its name and type don't say what it is. Add `@returns` for a value that needs explaining, and `@throws` for an error that a caller should handle.
- Give the main entry points an `@example` with a fenced `ts` block. Keep it to the shortest code that works.
- Link with `{@link Name}`. A name that the reference doesn't document becomes plain code.

The pages leave out `raw`, `payload` and the `...Raw` properties of the data model, because the introduction of the [data model reference](https://forges.link/reference/data-model) describes them once.

## Commit messages

Commits follow [Conventional Commits](https://www.conventionalcommits.org). Releases are cut from the commit messages, so the type decides the version bump. When a change concerns a single forge, use the provider as the scope:

```text
feat(github): adopt asynchronous direct merges
fix(gitlab): read nested group paths
docs: explain webhook verification
```

The title of a pull request uses the same format.

## Pull requests

Keep a pull request to one change, so it is easy to review. Before you push, run `pnpm check:all`, which runs the same checks that we run on every commit to `forges`. In the description, explain what changed and why, and link the issue that it closes. If you used AI tools, read the [AI policy](https://forges.link/contributing/ai-policy) first, because the description must be in your own words.

## Reporting a problem

Open an [issue](https://github.com/danielroe/forges/issues) with steps to reproduce the problem. If it only happens on one forge, name the forge, and for a self-hosted forge, tell us its version.


## Sitemap

See the full [sitemap](https://forges.link/sitemap.md) for all pages.
