# Configuration reference

Field-by-field reference for the current .changes/config.toml schema.

Source: https://semifold.noctisynth.org/docs/configuration/reference/
Language: en



Semifold accepts TOML configuration at `.changes/config.toml`. Field names use kebab-case. JSON configuration and unknown fields are not part of the supported configuration contract.

## `[branches]` [#branches]

| Field     | Type                             | Required | Meaning                                                                                                                |
| --------- | -------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `base`    | string                           | yes      | Branch that normal development and release preparation start from.                                                     |
| `release` | strict MiniJinja template string | yes      | Automation-maintained branch for prepared version changes and the release pull request; it must not resolve to `base`. |

The release branch is owned by the release automation: `smif ci` force-updates it and opens or refreshes a pull request from it to `base`. Semifold does not interpret `release = "main"` as a trunk-release strategy. A fixed release branch must differ from `base`, and a template must also render to a different branch. Matching fixed names are rejected when the configuration is loaded; a matching template result is rejected before version files, branches, or remote refs are changed.

A value without template syntax remains a fixed branch name. To derive a branch from the current version decision, use `release.plan.fingerprint`, `release.plan.common_version`, or read `release.plan.packages["<PackageId>"].next_version` through a stable `PackageId`:

```toml
[branches]
base = "main"
release = 'release/v{{ release.plan.packages["semifold"].next_version }}'
```

The template uses strict variable checks. Rendering fails instead of inventing an ambiguous branch when the referenced package is not in this release or the plan has no common version.

## `[release]` [#release]

Optional strict MiniJinja templates customize the commit created on the release branch and the title of its pull request:

```toml
[release]
commit-message = "chore(release): {{ release.plan.fingerprint }}"
pull-request-title = 'chore(release): {{ release.plan.packages["semifold"].next_version }}'
```

| Field                | Type                             | Default                         | Meaning                                                       |
| -------------------- | -------------------------------- | ------------------------------- | ------------------------------------------------------------- |
| `commit-message`     | strict MiniJinja template string | `chore(release): bump versions` | Complete Git commit message; multiline output is allowed.     |
| `pull-request-title` | strict MiniJinja template string | `chore(release): bump versions` | GitHub pull request title; output must be one non-empty line. |

Both templates expose only the same `release.*` context as `branches.release`. Omit the fields or the whole section to retain the defaults. `smif init` and `smif config sync` do not write this optional section automatically. Invalid templates fail before version files or changesets are modified.

## `[tags]` [#tags]

A map from changeset tag to changelog heading:

```toml
[tags]
feat = "New Features"
fix = "Bug Fixes"
```

Every tag used in a changeset must exist here. A tag does not choose a release channel.

## `[changelog]` [#changelog]

| Field                | Type   | Default           | Meaning                                          |
| -------------------- | ------ | ----------------- | ------------------------------------------------ |
| `template`           | string | built-in template | MiniJinja template for a complete release block. |
| `changeset-template` | string | built-in template | MiniJinja template for one changeset entry.      |

`smif init` writes the built-in templates so they can be discovered and customized. Keep the stable release marker required by Semifold when replacing a complete release template.

## `[packages.<PackageId>]` [#packagespackageid]

```toml
[packages.web]
path = "packages/web"
resolver = "nodejs"
publish = false
channel = "rc"
channel-bump = "minor"
depends-on = ["native-core"]
github-release = true
assets = [
  "dist/*.wasm",
  { path = "dist/cli", name = "semifold-linux-x64" },
]
```

| Field            | Type                                     | Default                                          | Meaning                                                                                                                          |
| ---------------- | ---------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `path`           | path                                     | required                                         | Package root relative to the repository.                                                                                         |
| `resolver`       | ecosystem ID                             | required                                         | Built-in adapter or registered plugin used for this package.                                                                     |
| `publish`        | boolean                                  | manifest or plugin discovery result              | Whether Semifold runs registry pre-checks and publish commands for this package. Omitted configuration does not write the field. |
| `channel`        | string                                   | `stable`                                         | Stable or named release channel such as `rc` or `beta`.                                                                          |
| `channel-bump`   | `preserve`, `patch`, `minor`, or `major` | none                                             | One-shot stable-base bump for the next transition into the named channel.                                                        |
| `depends-on`     | PackageId array                          | `[]`                                             | Supplemental internal dependency edges that are not represented by manifests.                                                    |
| `github-release` | boolean                                  | public package: `true`; private package: `false` | Overrides GitHub Release creation policy.                                                                                        |
| `assets`         | string or `{ path, name }` array         | `[]`                                             | Files or globs uploaded to the package's GitHub Release.                                                                         |

The legacy `version-mode` field remains readable for migration. New configuration should use `channel`.

`publish` is an ecosystem-independent explicit override. `publish = false` keeps Python, C++, or an internal tool out of the registry flow. `publish = true` overrides Rust `publish = false`, Node.js `private: true`, or a private marker returned by a plugin. The override changes Semifold's effective publishability only: it neither edits the native manifest nor bypasses restrictions enforced by tools such as `cargo publish` and `npm publish`.

The default `github-release` policy uses effective publishability after this override: publishable packages default to enabled and private packages to disabled. Explicit `github-release = true` or `false` still controls Forge behavior independently.

## `[plugins.<ecosystem-id>]` [#pluginsecosystem-id]

```toml
[plugins."com.example.game"]
path = "plugins/game.js"
sha256 = "64-lowercase-hex-characters"
allowed-origins = ["https://api.example.com"]
```

| Field             | Type               | Default  | Meaning                                                                                                                 |
| ----------------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `path`            | path               | required | Repository-relative path to one bundled ESM file.                                                                       |
| `sha256`          | string             | none     | Optional lowercase SHA-256 content pin.                                                                                 |
| `allowed-origins` | HTTPS origin array | `[]`     | Exact origins the plugin may access through `fetch`. Paths, wildcards, credentials, and non-HTTPS origins are rejected. |

The plugin's exported metadata separately declares `read-patterns`. Both the metadata declaration and host validation must allow a file read.

## `[resolver.<ecosystem-id>]` [#resolverecosystem-id]

Resolver configuration owns publishing behavior even when package discovery and version edits come from a plugin.

| Field          | Type                   | Default | Meaning                                                               |
| -------------- | ---------------------- | ------- | --------------------------------------------------------------------- |
| `pre-check`    | HTTP or command object | none    | Checks whether the target package version already exists.             |
| `prepublish`   | command array          | `[]`    | Commands executed before this ecosystem's publish command.            |
| `publish`      | command array          | `[]`    | Commands that publish the package to its registry.                    |
| `post-version` | command array          | `[]`    | Commands executed after package and changelog file edits are applied. |

### HTTP pre-check [#http-pre-check]

```toml
[resolver.rust.pre-check]
type = "http"
url = "https://crates.io/api/v1/crates/{{ package.name }}/{{ package.version }}"
extra-headers = { User-Agent = "Example release automation" }
retry = [2, 5, 15]
```

HTTP status `200` means the version exists and `404` means it does not. Other statuses fail the pre-check. `retry` contains delays in seconds for retryable failures and may be combined with a bounded `Retry-After` response.

### Command pre-check [#command-pre-check]

```toml
[resolver.internal.pre-check]
type = "command"
command = "./scripts/version-exists"
args = ["--json-lines"]
extra-env = { REGISTRY = "internal" }
```

The command runs in the package directory. stdin receives one line of `PublishPackageContext` JSON followed by a newline. stdout must contain exactly one line: `{"exists": true}` or `{"exists": false}`.

A start failure, non-zero exit, invalid JSON, or extra non-empty stdout content fails preflight. stderr is inherited for diagnostics. Command pre-checks run during both normal publishing and global `--dry-run`, so the script must be read-only and safe to repeat.

### Command entries [#command-entries]

```toml
[[resolver.nodejs.publish]]
command = "npm"
args = ["publish", "--provenance", "--access", "public", "--tag", "rc"]
extra-env = { NPM_CONFIG_PROVENANCE = "true" }
stdout = "inherit"
stderr = "inherit"
dry-run = false
```

| Field               | Type                         | Default   | Meaning                                                                                 |
| ------------------- | ---------------------------- | --------- | --------------------------------------------------------------------------------------- |
| `command`           | string                       | required  | Executable without shell expansion.                                                     |
| `args`              | string array                 | `[]`      | Arguments passed directly to the executable.                                            |
| `extra-env`         | string map                   | `{}`      | Additional environment variables. Avoid putting credentials in committed configuration. |
| `stdout` / `stderr` | `inherit`, `pipe`, or `null` | `inherit` | Child-process output policy.                                                            |
| `dry-run`           | boolean                      | `false`   | Whether this command is explicitly safe to run during `--dry-run`.                      |

Semifold does not interpret shell operators inside `args`; use a script when a workflow requires pipes or compound shell behavior.

## Template variables [#template-variables]

Registry URLs and configured commands use package-scoped template context. Common values include:

| Variable                | Meaning                                  |
| ----------------------- | ---------------------------------------- |
| `{{ package.name }}`    | Manifest or registry name.               |
| `{{ package.version }}` | Version being checked or published.      |
| `{{ package.path }}`    | Package path relative to the repository. |

The changelog templates receive a richer structured context. Keep template customization separate from registry command configuration and validate changes with `smif status` and `smif version --dry-run`.

