Configuration reference
Field-by-field reference for the current .changes/config.toml schema.
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]
| Field | Type | Required | Meaning |
|---|---|---|---|
base | string | yes | Branch that normal development and release preparation start from. |
release | strict MiniJinja template string | yes | Branch maintained for prepared version changes and the release pull request. |
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:
[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]
Optional strict MiniJinja templates customize the commit created on the release branch and the title of its pull request:
[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]
A map from changeset tag to changelog heading:
[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]
| 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>]
[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>]
[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>]
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
[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
[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
[[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
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.