# Semifold — complete documentation --- --- # Using Semifold with agents Find focused Markdown documentation and a portable Semifold skill for release tasks. Source: https://semifold.noctisynth.org/docs/agents/ Language: en Agents can read the same maintained content as this website through the [documentation index](https://semifold.noctisynth.org/llms.txt), the [Chinese index](https://semifold.noctisynth.org/zh/llms.txt), or [complete bilingual text](https://semifold.noctisynth.org/llms-full.txt). The indexes link directly to individual Markdown pages so an agent can load only what the task needs. Each documentation page advertises its Markdown version with an HTML alternate link. ## Get the Semifold skill [#get-the-semifold-skill] The repository distributes [skills/semifold](https://github.com/noctisynth/semifold/tree/main/skills/semifold) in the [Agent Skills format](https://agentskills.io/specification). Copy the whole `semifold` folder, including `references/`, into the skills location supported by your agent client, or use that client's repository skill installer with repository `noctisynth/semifold` and path `skills/semifold`. Installation and discovery locations depend on the client; this repository does not install it globally. The skill routes changeset creation, configuration maintenance, release review, and release troubleshooting to the existing command documentation. It preserves your repository instructions and the scope you authorize. `AGENTS.md` at the Semifold repository root instead guides agents contributing to Semifold itself. ## Before a task [#before-a-task] Run `smif --version` and `smif --help`. Read the target repository's `.changes/config.toml` and workflow; use exact configured package IDs and tags. For older versions, installed help takes precedence over examples using newer flags. Do not infer an installed version from the website's current documentation. ## Task checkpoints [#task-checkpoints] | Task | Prerequisites and operation | Expected result and side effects | Recovery reference | | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------- | | Record a change | Configured package IDs; `smif commit` with complete explicit arguments when non-interactive | One new changeset; versions unchanged. Review `smif status` next. | [Changesets](https://semifold.noctisynth.org/docs/commands/commit/) | | Review release intent | Valid configuration and changesets; `smif status` | Package/version plan and propagation reasons; no package file writes. `--comment` adds a GitHub write. | [Status](https://semifold.noctisynth.org/docs/commands/status/) | | Maintain configuration | Existing project; `smif config sync --check` | Reports drift; applying `config sync` can update configuration. | [Configuration](https://semifold.noctisynth.org/docs/commands/config/) | | Prepare versions | Choose manual or CI workflow; inspect hooks | Version/changelog edits and changeset consumption; hook failure can leave partial applied state. | [Version](https://semifold.noctisynth.org/docs/commands/version/) | | Publish | Prepared versions, credentials, and explicit release scope | Registry and optional GitHub mutations; partial progress is reported. | [Publish](https://semifold.noctisynth.org/docs/commands/publish/) | `--dry-run` can execute configured commands explicitly enabled for dry run, and publish preflight can access registries. Inspect hooks and respect network or local build constraints before describing a preview as harmless. With generated GitHub Actions, follow [CI](https://semifold.noctisynth.org/docs/commands/ci/) and avoid duplicate local version or publish runs for the same release. --- # Documentation Learn how Semifold versions and publishes packages across every ecosystem in one repository. Source: https://semifold.noctisynth.org/docs/ Language: en Semifold is a versioning and release tool for monorepos that span multiple languages and package ecosystems. It connects packages that use different manifests and registries, then manages their changesets, related version updates, changelogs, and dependency-ordered publishing as one repository-wide workflow. ## Choose what you need [#choose-what-you-need] Install the published CLI with the script, Cargo, npm, or PyPI. Initialize a repository, create a changeset, review affected packages, and run version and publish. See how package manifests, stable package IDs, dependencies, changesets, and release commands fit together. Learn the structure and maintenance workflow of `.changes/config.toml`. Use the published JavaScript plugin runtime and TypeScript SDK for custom package formats. Distinguish packages, PackageId values, ecosystems, changesets, channels, registries, and plans. ## The repository-wide lifecycle [#the-repository-wide-lifecycle] The ecosystem adapter for each package understands its manifest. Semifold owns the relationships across adapters: stable package identity, cross-ecosystem dependency propagation, changeset intent, release channels, changelogs, publish ordering, and recovery reporting. ## A useful reading order [#a-useful-reading-order] If Semifold is new to you, read [the introduction](https://semifold.noctisynth.org/docs/introduction/) before the configuration reference. Then complete [the first-release tutorial](https://semifold.noctisynth.org/docs/getting-started/first-release/) with a small repository. Use the [glossary](https://semifold.noctisynth.org/docs/concepts/glossary/) whenever an unfamiliar domain term appears. The [command documentation](https://semifold.noctisynth.org/docs/commands/) explains each command in detail. Its [CLI reference](https://semifold.noctisynth.org/docs/commands/reference/) is a lookup page, not the primary way to learn the workflow. --- # What is Semifold? Understand the repository problem Semifold solves and how it works across different package ecosystems. Source: https://semifold.noctisynth.org/docs/introduction/ Language: en Semifold manages versions and releases for a repository that contains packages from more than one ecosystem. Imagine a product repository with a Rust core library, Node.js bindings, a Python client, and a C++ component. Each ecosystem already has a good package manifest and publishing tool. The difficult part is everything between them: * deciding which packages need a new version after one change; * updating internal requirements when a dependency moves; * keeping one understandable changelog history; * publishing in an order that respects relationships across languages; * recovering when one registry succeeds and another fails. Semifold supplies that repository-wide layer. It does not replace Cargo, npm, Python packaging, CMake, or your registries. ## One workspace graph [#one-workspace-graph] Every managed package becomes a node in one workspace dependency graph. A node contains: * a stable `PackageId` used by configuration and changesets; * the manifest or registry name used by its ecosystem; * current version, path, and publishability; * manifest dependencies and optional explicit relationships; * the adapter responsible for reading and editing its manifest. This normalized graph is why a Rust package can affect a Node.js or Python package without pretending their manifests are the same. ### Built-in and custom ecosystems [#built-in-and-custom-ecosystems] Semifold includes adapters for Rust, Node.js, Python, and C++. They understand the package and workspace formats covered by their implementation and fixtures. Repository-local JavaScript ecosystem plugins implement the same discovery, inspection, and edit-planning boundary, so custom packages join the same graph instead of living in a separate release script. ## The work you do with Semifold [#the-work-you-do-with-semifold] ### 1. Describe the repository [#1-describe-the-repository] `smif init` discovers packages and creates `.changes/config.toml`. The file gives every package a stable ID and records release policy that package manifests cannot express. As the repository changes, `smif config sync` compares current discovery with the saved configuration. It updates discovered facts without silently replacing intentional policy. ### 2. Record a user-visible change [#2-record-a-user-visible-change] A changeset is a small Markdown file that names affected package IDs, requested version bumps, changelog categories, and a summary. It travels with the code change and can be reviewed before versions move. ```md --- native-core: "minor:feat" web-bindings: "patch:fix" --- Expose the new parser through the web bindings. ``` ### 3. Update every affected version [#3-update-every-affected-version] `smif status` shows the direct changes and additional packages affected by dependency rules. `smif version` then updates package versions, internal requirements, changelogs, and consumed changesets through the adapters responsible for each manifest. The plan is valuable because it makes the decision reviewable, but the product capability is the consistent cross-ecosystem version update it describes. ### 4. Publish with ecosystem-specific rules [#4-publish-with-ecosystem-specific-rules] `smif publish` orders packages by dependency, checks whether target versions already exist, and runs the configured command for each ecosystem. GitHub Release and asset work can be enabled separately. The final report distinguishes successful, skipped, failed, and not-started packages. That information lets a maintainer repair credentials or registry configuration and resume without guessing which versions already escaped. ## What Semifold deliberately leaves to other tools [#what-semifold-deliberately-leaves-to-other-tools] Semifold does not build or test your packages, host a package registry, infer product policy from commit messages, or hide credentials inside plugins. Build and test commands remain in your existing tooling or explicit hooks; registry authentication remains in the execution environment. ## Learn the vocabulary as you need it [#learn-the-vocabulary-as-you-need-it] You do not need to memorize every internal term before starting. The [glossary](https://semifold.noctisynth.org/docs/concepts/glossary/) explains `PackageId`, ecosystem adapters, changesets, release channels, registries, and plans with their practical differences. Next, [install Semifold](https://semifold.noctisynth.org/docs/getting-started/installation/) and follow [your first release](https://semifold.noctisynth.org/docs/getting-started/first-release/). --- # Continuous integration Run the generated GitHub Actions release-PR or publish branch behavior from one command. Source: https://semifold.noctisynth.org/docs/commands/ci/ Language: en `smif ci` is the recommended release entry point for repositories that selected GitHub Actions during `smif init`. It refuses to run outside GitHub Actions and skips branches other than the configured base branch. ```bash smif ci ``` ## When changesets are present [#when-changesets-are-present] On the base branch, `ci` computes the version operation, renders the configured release branch, commit message, and pull request title, applies the version files, force-updates that branch, and creates or refreshes its pull request back to the base branch. The commit message and pull request title both default to `chore(release): bump versions`; optionally customize them under `[release]` with the same strict `release.*` context used by the branch template. The rendered release branch must differ from the base branch. Semifold checks this before applying version files or changing Git refs; using the base branch as the release branch is rejected rather than treated as a trunk-release workflow. Contributors therefore only need to commit a changeset with their code and merge that pull request. Maintainers review versions and changelogs in the generated release pull request. Release pull request bodies normally include each package's changelog. If the body exceeds a conservative 65,536 UTF-8 byte budget, Semifold replaces it with a package version summary and a notice directing reviewers to the pull request's **Files changed** tab for the complete changelogs. The summary includes only complete package entries that fit the budget, in package ID order. Changelog files remain complete. This protection applies both when creating and when updating the pull request; no configuration is required. ## When no changesets remain [#when-no-changesets-remain] After the release pull request is merged, the next base-branch run sees no pending changesets and calls the publish operation with GitHub Release handling enabled. Packages are checked and published in dependency order. ## Environment and outputs [#environment-and-outputs] The command relies on `GITHUB_ACTIONS`, `GITHUB_REF_NAME`, `GITHUB_REPOSITORY`, and `GITHUB_TOKEN`. The generated workflow uses the stable step ID `semifold`. The branch that actually runs writes either the `semifold-version` or `semifold-publish` step output key, and the job maps them to `version` and `publish`: ```yaml jobs: release: outputs: version: ${{ steps.semifold.outputs['semifold-version'] }} publish: ${{ steps.semifold.outputs['semifold-publish'] }} steps: - id: semifold run: smif ci ``` Downstream jobs read `needs.release.outputs.version` or `needs.release.outputs.publish`. The branch that did not run produces an empty string. On a partial publish failure, Semifold tries to write publish JSON with recovery states before returning a non-zero status. `--dry-run` prepares the selected branch behavior without pushing a branch, creating a pull request, or publishing. Use the generated workflow as the supported baseline instead of reconstructing permissions from a minimal snippet. ## GitHub error diagnostics [#github-error-diagnostics] GitHub failures identify the operation and include the HTTP status, API message, validation details, and documentation link when available. Client failures include their underlying error chain. These details do not require `--debug`; GitHub tokens are redacted. A 403 includes permission checks appropriate to the operation, without assuming that permissions are the only possible cause. Release pull request queries, creation, updates, and branch push failures stop the command. When `ci` selects publishing, it uses the same Release and asset diagnostics as `publish`. --- # Changesets Create one changeset interactively or supply the same release intent through explicit arguments. Source: https://semifold.noctisynth.org/docs/commands/commit/ Language: en `smif commit` records release intent before versions change. The visible alias `smif add` behaves the same way. ```bash smif commit ``` ## What the command asks [#what-the-command-asks] The interactive flow selects one or more configured `PackageId` values, assigns `patch`, `minor`, or `major` to each, selects an optional configured changelog tag, chooses a file name, and collects a user-facing summary. For the name `add-api`, Semifold creates `.changes/add-api.md`: ```md title=".changes/add-api.md" --- core: "minor:feat" web: "patch:fix" --- Expose the new API through the web binding. ``` The bump controls version intent. The tag chooses a changelog section and does not choose a release channel. ## Explicit form for automation [#explicit-form-for-automation] ```bash smif commit --name add-api \ --package core=minor \ --package web=patch \ --tag feat \ --summary "Expose the new API through the web binding." ``` `--package` and `--summary` are repeatable. A package without an inline level uses `--level`. Non-interactive callers must choose either `--tag ` or `--no-tag` so Semifold never guesses a missing answer. ## Validation and side effects [#validation-and-side-effects] * package IDs and tags must exist in `.changes/config.toml`; * the name must map to a safe changeset file and cannot silently overwrite different content; * the package set and summary cannot be empty; * `--dry-run` validates and renders the proposed changeset without creating it. Commit the changeset with the code change. Run [`smif status`](https://semifold.noctisynth.org/docs/commands/status/) before merging when you need to inspect dependency propagation. --- # Configuration Synchronize discovered packages, migrate legacy fields, and manage named release channels without rewriting unrelated policy. Source: https://semifold.noctisynth.org/docs/commands/config/ Language: en `smif config` groups maintenance operations for `.changes/config.toml`. ```text smif config sync smif config migrate smif config channel set|clear ``` ## Synchronize the workspace [#synchronize-the-workspace] Start with a read-only check: ```bash smif config sync --check ``` `sync` runs package discovery and compares it with `[packages]`. It reports additions, missing packages, renames, moves, and conflicts while preserving comments and intentional fields. Remove `--check` to apply an unambiguous plan. Use `--resolver rust --resolver nodejs` to limit discovery. `--prune` removes configured packages missing from a complete successful scan; it is intentionally unavailable when a partial scan or conflict makes deletion unsafe. ## Migrate from v0.2.x [#migrate-from-v02x] ```bash smif config migrate --check smif config migrate ``` Semifold reads and migrates v0.2.x configuration before strict project loading. It can replace legacy `version-mode`, normalize known snake\_case keys, and add the required typed `pre-check` discriminator while preserving unrelated TOML and comments. `--check` returns a non-zero status when a migration is needed and never writes the file. When another command cannot load TOML configuration under the current contract, it displays the underlying parse or validation error in full before suggesting `smif config migrate`. If migration also fails, the problem is outside the supported legacy-field conversions and must be corrected from that error. This command does not migrate JSON configuration or repair file permissions. ## Set or clear a release channel [#set-or-clear-a-release-channel] ```bash smif config channel set beta --package web --bump minor smif config channel clear --package web ``` `set` accepts a named channel and either repeatable `--package` values or `--all`. The optional one-time `--bump preserve|patch|minor|major` controls the stable baseline used when entering the channel; a successful `version` consumes it. `clear` restores the default stable channel. Both operations support `--check`. For Node.js packages, Semifold warns when the configured `npm publish` command lacks the matching `--tag`; it reports the policy mismatch without rewriting the command. ## `--check` versus `--dry-run` [#--check-versus---dry-run] `--check` answers whether persisted configuration already matches the requested state and is designed for CI drift detection. `--dry-run` previews an operation. Neither writes configuration, but their success criteria are different. --- # Command-line overview Choose a Semifold command by task, then open its behavior guide or the complete option reference. Source: https://semifold.noctisynth.org/docs/commands/ Language: en The installed executable is `smif`; `semifold` is an equivalent long name. Commands are documented separately so you can understand their inputs, side effects, and recovery behavior without turning the rest of the documentation into a list of flags. ## Choose a command [#choose-a-command] | Command | Use it when you need to | Writes local files | | ------------------------------------ | -------------------------------------------------------------------------------- | ------------------------- | | [`init`](https://semifold.noctisynth.org/docs/commands/init/) | Adopt Semifold in a repository and optionally generate GitHub Actions workflows. | Yes | | [`commit`](https://semifold.noctisynth.org/docs/commands/commit/) | Record a reviewed release intent as a changeset. | Yes | | [`config`](https://semifold.noctisynth.org/docs/commands/config/) | Synchronize, migrate, or change release-channel configuration. | Depends on the subcommand | | [`status`](https://semifold.noctisynth.org/docs/commands/status/) | Inspect affected packages and target versions before writing anything. | No | | [`version`](https://semifold.noctisynth.org/docs/commands/version/) | Apply package, dependency, changelog, and changeset edits manually. | Yes | | [`publish`](https://semifold.noctisynth.org/docs/commands/publish/) | Publish already-versioned packages in dependency order. | External side effects | | [`ci`](https://semifold.noctisynth.org/docs/commands/ci/) | Let the generated GitHub Actions workflow maintain the release PR or publish it. | Yes | | [`mcp`](https://semifold.noctisynth.org/docs/commands/mcp/) | Expose changeset CRUD tools to an MCP client over stdio. | Depends on the tool | ## Guides versus reference [#guides-versus-reference] Each command page explains normal behavior, not only syntax. Use the [complete CLI reference](https://semifold.noctisynth.org/docs/commands/reference/) when you already know the workflow and only need an option name. For a first release, follow the [tutorial](https://semifold.noctisynth.org/docs/getting-started/first-release/). If it generated GitHub Actions, the normal contributor path ends after committing a changeset; `smif ci` performs versioning and publishing at the appropriate base-branch runs. --- # Initialization Discover packages, create Semifold configuration, and optionally generate the supported GitHub Actions workflows. Source: https://semifold.noctisynth.org/docs/commands/init/ Language: en `smif init` adopts Semifold in a repository. Run it once at the repository root; use [`smif config sync`](https://semifold.noctisynth.org/docs/commands/config/) for later package additions, removals, or moves. ```bash smif init ``` ## Default interactive flow [#default-interactive-flow] In a terminal, Semifold asks for the built-in ecosystems to scan, base and release branch names, default changelog categories, and whether to generate GitHub Actions. It then plans package discovery before writing: * `.changes/config.toml` with branches, tags, changelog templates, discovered packages, and resolver defaults; * `.github/workflows/semifold-ci.yaml` when GitHub Actions is selected; * `.github/workflows/semifold-status.yaml` for pull-request status comments when GitHub Actions is selected. Review the stable `PackageId` keys and paths under `[packages]` immediately after initialization. ## Automation form [#automation-form] A non-interactive caller must answer every prompt explicitly: ```bash smif init --resolvers rust --resolvers nodejs \ --base-branch main \ --release-branch release \ --default-tags \ --github-actions ``` Use the matching `--no-resolvers`, `--no-default-tags`, or `--no-github-actions` option to choose an empty result. This form exists for automation; it is not required for normal local use. ## Important options [#important-options] | Option | Behavior | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `--target ` | Changes the changeset directory from the default `.changes`. | | `--resolvers ` | Scans a built-in ecosystem; repeat for multiple ecosystems. | | `--base-branch ` | Selects the branch that receives the generated release pull request. | | `--release-branch ` | Selects the automation-maintained release branch; it must differ from the base branch after template rendering. | | `--force` | Explicitly reinitializes an existing configuration. It is not a package-sync command. | | `--allow-non-root` | Allows invocation from a subdirectory while using the discovered repository root. | | `--dry-run` | Plans and reports initialization without writing the files. | Initialization fails before writing when discovery or required choices are invalid. In particular, `--release-branch` is not another name for the current base branch: generated CI force-updates it while maintaining a pull request back to `--base-branch`. Existing repositories should not use `--force` as routine maintenance because it can replace intentional policy; run `smif config sync --check` instead. --- # MCP tools Start the stdio MCP server that exposes safe, revision-aware changeset CRUD tools. Source: https://semifold.noctisynth.org/docs/commands/mcp/ Language: en `smif mcp` starts a JSON-RPC MCP server on standard input and output. It is intended for a configured MCP client, not for an interactive terminal session. ```bash smif mcp ``` By default, Semifold locates the project from the current directory. If the MCP client does not start the server from the repository, use `--project-root /path/to/repository` to select the repository root explicitly. Project loading is lazy so one invalid tool request does not terminate the server. ## Published tools [#published-tools] | Tool | Input | Behavior | | ------------------ | ------------------------------------------------ | ----------------------------------------------------------------- | | `get_changeset` | optional `id` | Lists changesets or returns one record with its SHA-256 revision. | | `create_changeset` | `name`, `packages`, `summary` | Idempotently creates the requested changeset. | | `update_changeset` | `id`, expected `revision`, `packages`, `summary` | Replaces content only when the caller's revision still matches. | | `delete_changeset` | `id`, expected `revision` | Deletes only the exact revision the caller inspected. | Each package input contains `package`, `bump` (`patch`, `minor`, or `major`), and an optional `tag`. Responses use schema version 1 and return structured status or error data. ## Concurrency and safety [#concurrency-and-safety] Mutation tools are serialized. Update and delete use optimistic SHA-256 revisions to reject stale callers, tool panics are isolated, invalid arguments return structured errors, and a following request can continue on the same server. The global `--dry-run` changes mutation tools into validated previews without writing changeset files. --- # Publishing Build a fresh publish plan from versioned manifests and changelogs, then release packages in dependency order. Source: https://semifold.noctisynth.org/docs/commands/publish/ Language: en `smif publish` handles packages whose version and changelog edits are already present. It does not depend on the changesets consumed by `version`. ```bash smif publish --dry-run smif publish ``` When generated GitHub Actions are enabled, merging the prepared release pull request triggers the recommended publish path through [`smif ci`](https://semifold.noctisynth.org/docs/commands/ci/). Do not run a duplicate local publish for that release. ## Planning and preflight [#planning-and-preflight] Semifold inspects current manifests and changelogs, orders packages through the workspace graph, expands configured commands, and performs every available registry pre-check before starting publish commands. Missing changelogs, versions already present, and package policy become explicit skip reasons. A private package skips registry preflight and publish commands only. Effective private status uses package-level `publish` first and falls back to manifest or plugin discovery when it is omitted. A private package does not create a GitHub Release by default; with explicit `github-release = true` and `--github-release`, Semifold still creates its Release and uploads assets. The final status is a private-package skip only when no Forge work remains. HTTP checks treat `200` as existing and `404` as missing; unexpected status codes fail safely. Command checks use the configured JSON Lines contract. ## Execution [#execution] For each eligible package, Semifold runs configured pre-publish and publish commands in dependency order. The final report separates `succeeded`, `skipped`, `failed`, and `not-started` packages so a partial failure can be retried without guessing. | Option | Behavior | | ------------------ | ----------------------------------------------------------------------------------------------------------------------- | | `--dry-run` | Runs read-only preflight and skips registry and Forge mutations. Commands run only when explicitly allowed for dry run. | | `--allow-dirty` | Accepts the current worktree diff as intentional publish input. | | `--github-release` | Creates configured GitHub Releases and uploads assets; only accepted in CI. | If a registry version already exists, its registry command is skipped. With GitHub Releases enabled, Semifold can still create a missing Release; an already existing Release does not re-upload missing assets. ## GitHub error diagnostics [#github-error-diagnostics] GitHub failures identify the operation and include the HTTP status, API message, validation details, and documentation link when available. Client failures include their underlying error chain. These details do not require `--debug`; GitHub tokens are redacted. A 403 includes permission checks appropriate to the operation, without assuming that permissions are the only possible cause. Release creation and asset upload failures retain the package and failure stage, partial publish report, and recovery guidance. An existing Release still follows the normal idempotent skip behavior. --- # Command index and global options Find each current Semifold command, its behavior page, and shared execution options. Source: https://semifold.noctisynth.org/docs/commands/reference/ Language: en ```text smif [OPTIONS] ``` `semifold` is an equivalent executable name. This page is a command index; each linked command page explains behavior, the default interactive path, and failure semantics. Run `smif --help` for parameter types, defaults, repeatability, and conflicts in the installed binary. ## Commands [#commands] | Command | Syntax | Detailed behavior | | ---------------------- | --------------------------------------------- | --------------------------------------------------- | | `init` | `smif init [OPTIONS]` | [Initialization](https://semifold.noctisynth.org/docs/commands/init/) | | `commit` (`add`) | `smif commit [OPTIONS]` | [Changeset creation](https://semifold.noctisynth.org/docs/commands/commit/) | | `config sync` | `smif config sync [OPTIONS]` | [Configuration maintenance](https://semifold.noctisynth.org/docs/commands/config/) | | `config migrate` | `smif config migrate [OPTIONS]` | [Configuration maintenance](https://semifold.noctisynth.org/docs/commands/config/) | | `config channel set` | `smif config channel set [OPTIONS] ` | [Configuration maintenance](https://semifold.noctisynth.org/docs/commands/config/) | | `config channel clear` | `smif config channel clear [OPTIONS]` | [Configuration maintenance](https://semifold.noctisynth.org/docs/commands/config/) | | `status` | `smif status [OPTIONS]` | [Release inspection](https://semifold.noctisynth.org/docs/commands/status/) | | `version` | `smif version [OPTIONS]` | [Version application](https://semifold.noctisynth.org/docs/commands/version/) | | `publish` | `smif publish [OPTIONS]` | [Publish execution](https://semifold.noctisynth.org/docs/commands/publish/) | | `ci` | `smif ci [OPTIONS]` | [GitHub Actions orchestration](https://semifold.noctisynth.org/docs/commands/ci/) | | `mcp` | `smif mcp [OPTIONS]` | [MCP server](https://semifold.noctisynth.org/docs/commands/mcp/) | ## Global options [#global-options] | Option | Meaning | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--dry-run` | Enables preview mode across every command. Semifold-managed files, registry and Forge resources, and pull-request comments are not changed; explicitly dry-run-enabled configured commands can still execute. | | `--debug` | Enables additional diagnostics without making sensitive configuration a public output contract. | | `-h`, `--help` | Prints help for the current command level. | | `-V`, `--version` | Prints the executable version. | ## Command-specific option index [#command-specific-option-index] ### `init` [#init] `--target`, repeatable `--resolvers`, `--no-resolvers`, `--force`, `--base-branch`, `--release-branch`, `--default-tags`, `--no-default-tags`, `--github-actions`, `--no-github-actions`, and `--allow-non-root`. ### `commit` [#commit] `--name`, `--level`, repeatable `--summary`, repeatable `--package PACKAGE[=LEVEL]`, `--tag`, and `--no-tag`. ### `config` [#config] * `sync`: `--check`, `--prune`, repeatable `--resolver`; * `migrate`: `--check`; * `channel set`: positional `CHANNEL`, `--bump`, repeatable `--package` or `--all`, `--check`; * `channel clear`: repeatable `--package` or `--all`, `--check`. ### Release commands [#release-commands] * `status`: `--comment`; * `version`: `--allow-dirty`; * `publish`: `--github-release`, `--allow-dirty`; * `ci`: no command-specific options; * `mcp`: `-C`, `--project-root` (alias `--cd`). --- # Release status Compute the repository release decision without changing package files, changelogs, or changesets. Source: https://semifold.noctisynth.org/docs/commands/status/ Language: en `smif status` is the read-only checkpoint between a changeset and version edits. ```bash smif status ``` ## What it computes [#what-it-computes] Semifold loads configured packages into one dependency graph, merges requested bump levels, applies release-channel and dependency-propagation rules, and displays: * current and target versions for every affected package; * direct reasons such as `.changes/add-api.md`; * packages included by dependency or shared-version propagation; * warnings and a deterministic 12-character plan fingerprint. The same release decision is recomputed by [`smif version`](https://semifold.noctisynth.org/docs/commands/version/) from the same repository inputs. `status` does not persist a plan and does not edit manifests, changelogs, configuration, or changesets. ## Pull-request comments [#pull-request-comments] ```bash smif status --comment ``` `--comment` publishes the status report as a GitHub pull-request comment and is only valid in the expected CI environment. The workflow generated by `smif init` uses this path for review feedback. With global `--dry-run`, Semifold may collect and render the read-only facts needed for the comment preview, but it never creates or updates the pull-request comment. If GitHub rejects comment creation or update, the release plan remains available and `status` completes successfully. The warning includes the failed operation, GitHub API status and message, any documentation URL returned by GitHub, and a permission hint for `403 Forbidden` responses. If a package, target version, or reason is wrong, correct the changeset or dependency configuration rather than manually editing a package version. ## GitHub error diagnostics [#github-error-diagnostics] GitHub failures identify the operation and include the HTTP status, API message, validation details, and documentation link when available. Client failures include their underlying error chain. These details do not require `--debug`; GitHub tokens are redacted. A 403 includes permission checks appropriate to the operation, without assuming that permissions are the only possible cause. Comment and changed-file queries, including pagination, report detailed failures. Comment creation or update failures remain warnings and do not invalidate the release plan. --- # Versioning Validate and apply package versions, internal requirements, changelogs, and consumed changesets as one release operation. Source: https://semifold.noctisynth.org/docs/commands/version/ Language: en `smif version` performs the file-changing half of a release. When the generated GitHub Actions workflow is enabled, prefer [`smif ci`](https://semifold.noctisynth.org/docs/commands/ci/) instead of running this command locally for the same release. ```bash smif version --dry-run smif version ``` ## Plan and validation [#plan-and-validation] The command recomputes the same release decision shown by `smif status`, then asks each built-in adapter or repository plugin for deterministic manifest edits. Before normal writes begin, Semifold validates target paths, expected file hashes, edit conflicts, changelog templates, package identity, and the complete dependency graph. A normal run can update: * package versions and eligible internal dependency requirements; * shared workspace version sources; * package changelogs; * one-time `channel-bump` configuration; * consumed changeset files; * configured `post-version` commands. ## Worktree protection [#worktree-protection] The command rejects a dirty Git worktree by default. `--allow-dirty` means the existing diff is intentionally part of the operation; it does not make unrelated changes safe. ## Dry run [#dry-run] `--dry-run` prepares and validates the release without normal file writes. A `post-version` command still runs when its configuration explicitly sets `dry-run = true`, so such a command must be safe to repeat. ## Failure and recovery [#failure-and-recovery] Planning or validation failure leaves package files and changesets untouched. If a `post-version` command fails after validated file edits were applied, Semifold keeps those edits and retains the changesets, then reports the failed command and recovery facts. Inspect the Git diff, fix the hook, and retry instead of deleting the changeset by hand. After success, test and review the generated diff before committing it to the configured release branch. ## GitHub error diagnostics [#github-error-diagnostics] GitHub failures identify the operation and include the HTTP status, API message, validation details, and documentation link when available. Client failures include their underlying error chain. These details do not require `--debug`; GitHub tokens are redacted. A 403 includes permission checks appropriate to the operation, without assuming that permissions are the only possible cause. Changelog pull request metadata lookup failures remain warnings. They now include the affected package and GitHub diagnostic; version preparation continues without that remote metadata. --- # Glossary The repository, package, versioning, and publishing terms used throughout Semifold. Source: https://semifold.noctisynth.org/docs/concepts/glossary/ Language: en Use this page when a Semifold term is unfamiliar or when two similar concepts need to be distinguished. ## Repository and packages [#repository-and-packages] ### Monorepo [#monorepo] A Git repository that contains more than one independently versioned package or project. A **polyglot monorepo** contains packages from multiple language ecosystems. ### Workspace [#workspace] The set of packages Semifold manages as one dependency graph. Semifold builds this graph from package manifests, built-in ecosystem adapters, repository-local plugins, and explicit `depends-on` relationships. ### Package [#package] One versioned unit in the workspace. A package has a stable `PackageId`, a manifest name, a version, a path, an ecosystem, and a publishability state. ### PackageId [#packageid] The stable identifier used as a key under `[packages]` in `.changes/config.toml` and in changesets. It does not have to equal the name published to a registry. ```toml [packages.web-bindings] path = "packages/web" resolver = "nodejs" ``` Here, `web-bindings` is the `PackageId`; the `name` in `package.json` may be different. ### Manifest [#manifest] The ecosystem-specific file that describes a package. Examples include `Cargo.toml`, `package.json`, `pyproject.toml`, `CMakeLists.txt`, and `vcpkg.json`. ## Ecosystems and extension [#ecosystems-and-extension] ### Ecosystem [#ecosystem] A package format and its surrounding version and publishing conventions, such as Cargo/crates.io or npm. Semifold identifies each ecosystem with a stable ID such as `rust`, `nodejs`, or `com.example.game`. ### Adapter and resolver [#adapter-and-resolver] An **adapter** is the implementation boundary that discovers packages, reads their versions and dependencies, and plans manifest edits. The configuration uses the historical name `resolver` to select an ecosystem and to define its publish commands and registry pre-check. ### Plugin [#plugin] A repository-local, single-file JavaScript implementation of an ecosystem adapter. A plugin can discover and inspect packages and plan version edits. It cannot write files, run commands, access host credentials, publish packages, or create Forge releases. ## Versioning [#versioning] ### Changeset [#changeset] A small Markdown file under `.changes/` that records which `PackageId` values changed, their requested version bump, their changelog category, and a human-readable summary. ### Bump level [#bump-level] The requested version increase: `patch`, `minor`, or `major`. Dependency propagation and release-channel rules may increase or add package bumps after the direct changesets are read. ### Changelog tag [#changelog-tag] A label such as `feat` or `fix` that chooses a changelog section. A tag categorizes a change; it does not select a release channel. ### Release channel [#release-channel] The publishing state of a package, such as `stable`, `rc`, or `beta`. Named channels affect version encoding and, for Node.js, should be paired with the corresponding npm dist-tag. ### Release plan [#release-plan] The immutable result displayed by `smif status`: target versions, reasons, dependency propagation, planned file edits, and a fingerprint. `smif version` computes and applies the same release decision from the same repository state. ## Publishing [#publishing] ### Publish plan [#publish-plan] The ordered work Semifold derives after versions and changelogs have been written. It contains registry checks, prepublish and publish commands, package ordering, and optional Forge work. ### Registry [#registry] The package service that stores published versions, such as crates.io, npm, or PyPI. Semifold can run an HTTP or command pre-check before executing a package's publish command. ### Forge [#forge] A source-hosting and release service. Semifold currently integrates with GitHub Releases and release assets independently from registry publishing. ### Dry run [#dry-run] A preview execution selected with `--dry-run`. File, registry, and Forge mutations are skipped. Read-only registry pre-checks still run, and a configured command runs only when it explicitly has `dry-run = true`. ## Where to continue [#where-to-continue] * [Understand Semifold](https://semifold.noctisynth.org/docs/introduction/) for the complete product model. * [Configuration overview](https://semifold.noctisynth.org/docs/configuration/overview/) for how these concepts appear in `.changes/config.toml`. * [Plugin system](https://semifold.noctisynth.org/docs/plugins/overview/) for custom ecosystem support. --- # Configuration overview Understand the responsibilities and normal maintenance workflow of .changes/config.toml. Source: https://semifold.noctisynth.org/docs/configuration/overview/ Language: en Semifold stores repository-wide release rules in `.changes/config.toml`. `smif init` creates the file; after that, edit intentional policy by hand and use `smif config sync` to reconcile package discovery. ## Start with a small configuration [#start-with-a-small-configuration] ```toml title=".changes/config.toml" [branches] base = "main" release = "release" [tags] feat = "New Features" fix = "Bug Fixes" [packages.core] path = "crates/core" resolver = "rust" [packages.web] path = "packages/web" resolver = "nodejs" [resolver.rust.pre-check] type = "http" url = "https://crates.io/api/v1/crates/{{ package.name }}/{{ package.version }}" [[resolver.rust.publish]] command = "cargo" args = ["publish"] [resolver.nodejs.pre-check] type = "http" url = "https://registry.npmjs.org/{{ package.name }}/{{ package.version }}" [[resolver.nodejs.publish]] command = "npm" args = ["publish", "--provenance", "--access", "public"] ``` This file says: 1. releases start from `main` and are prepared on `release`; 2. `core` and `web` are the stable `PackageId` values used by changesets; 3. each package chooses the adapter that understands its manifest; 4. each ecosystem defines how to check for an existing version and how to publish. Keep `base` and `release` separate. The generated automation force-updates the release branch while maintaining its pull request back to the base branch, so Semifold rejects a fixed or rendered release branch that matches `base`. ## Configuration sections [#configuration-sections] | Section | Responsibility | | ------------- | -------------------------------------------------------------------------------------------- | | `[branches]` | Base and release branch names used by the release workflow. | | `[release]` | Optional templates for the release commit message and pull request title. | | `[tags]` | Allowed changeset categories and their changelog headings. | | `[changelog]` | Optional MiniJinja templates for release blocks and changeset entries. | | `[packages]` | Stable package IDs, paths, ecosystems, channels, explicit relationships, and release assets. | | `[plugins]` | Repository-local JavaScript ecosystem plugins and their allowed capabilities. | | `[resolver]` | Per-ecosystem registry pre-check, publish hooks, and post-version commands. | ## Discovery and policy have different owners [#discovery-and-policy-have-different-owners] An adapter or plugin can discover package paths, manifest names, versions, and manifest dependencies. It cannot decide your release branch, changelog categories, named channels, explicit `depends-on` edges, or GitHub Release policy. This division matters during synchronization: ```bash smif config sync --check ``` The command reports drift without writing. Remove `--check` to add or update discovered packages. Add `--prune` only after reviewing packages that would be removed. To synchronize only selected ecosystems, repeat `--resolver`: ```bash smif config sync --resolver rust --resolver nodejs ``` ## Validate older configuration [#validate-older-configuration] When upgrading Semifold, check whether the file needs a schema migration: ```bash smif config migrate --check ``` If a migration is available, review and apply it with: ```bash smif config migrate ``` When a regular command cannot read TOML configuration under the current contract, it preserves the specific parse or validation error, including available line, column, and source context, before suggesting this migration command. The suggestion is recovery guidance for older configuration, not a promise to repair arbitrary TOML syntax. If migration still reports a parse error, fix the reported problem manually. JSON configuration and file-read failures do not receive this suggestion because migration does not handle them. For example, the legacy `version-mode` package field is accepted for migration but current configuration should use `channel` and, when entering a named channel, `channel-bump`. ## Add policy gradually [#add-policy-gradually] You do not need to configure every feature before the first release. A practical order is: 1. verify discovered package IDs and paths; 2. confirm manifest dependencies and add only genuinely supplemental `depends-on` edges; 3. run one stable release; 4. add named channels, custom changelog templates, registry checks, assets, and CI policy as needed; 5. register a plugin only for an ecosystem that the built-in adapters do not understand. Continue to the [configuration reference](https://semifold.noctisynth.org/docs/configuration/reference/) for every field, or open the [plugin system overview](https://semifold.noctisynth.org/docs/plugins/overview/) for custom ecosystems. --- # 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[""].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.]` [#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.]` [#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.]` [#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`. --- # Adopt an existing monorepo Let Semifold discover packages, assign stable identities, and manage versions and releases without replacing existing build tools. Source: https://semifold.noctisynth.org/docs/getting-started/adopt-existing-monorepo/ Language: en Semifold does not require a repository reorganization or one build tool for every language. Adoption starts by making the existing packages and their relationships explicit, then introducing changesets, version edits, and release automation. ## Before you start [#before-you-start] Install the `latest` release, then verify at the repository root that: * each package manifest is valid for its existing tool; * the Git worktree has no unrelated changes; * you know the base branch used for development and the branch to use for the release pull request. A custom manifest format does not need to be converted to one of the four built-in ecosystems. Adopt the built-in packages first, then add the custom format through the [plugin system](https://semifold.noctisynth.org/docs/plugins/overview/). ## 1. Initialize and review discovery [#1-initialize-and-review-discovery] Run from the repository root: ```bash smif init ``` Select the ecosystems that the repository actually uses and optionally generate the GitHub Actions workflows. Initialization creates Semifold configuration, a changeset directory, and optional workflows. It does not move packages or rewrite application source. Open `.changes/config.toml` and review `[packages]` first: ```toml [packages.rust-core] path = "crates/core" resolver = "rust" [packages.web-client] path = "packages/web" resolver = "nodejs" depends-on = ["rust-core"] ``` `rust-core` and `web-client` are stable package IDs (`PackageId` values). Changesets, graph edges, and configuration references use these IDs, which may differ from registry names in manifests. Keep an ID stable after choosing it; moving a package normally changes only `path`. Read [Package discovery](https://semifold.noctisynth.org/docs/workspace/package-discovery/) and verify each ecosystem's discovery boundary. A directory that looks package-like is not necessarily in the supported scan range. ## 2. Add relationships manifests cannot express [#2-add-relationships-manifests-cannot-express] Manifest dependencies within one ecosystem enter the shared workspace graph. Semifold never guesses a cross-ecosystem edge from matching names. Add `depends-on` to the dependent package instead: ```toml [packages.python-binding] path = "bindings/python" resolver = "python" depends-on = ["rust-core"] ``` This says that `python-binding` depends on `rust-core`: the Rust package is processed first, and a new `rust-core` release gives the binding a `patch` release. See [Dependencies and version propagation](https://semifold.noctisynth.org/docs/workspace/dependencies/) for the complete distinction between ordering and propagation. ## 3. Preview before changing files [#3-preview-before-changing-files] Before creating the first changeset, check that the configuration forms a valid workspace: ```bash smif config sync --check smif status ``` `config sync --check` returns a non-zero status when discovery and configuration differ, but does not write files. `status` computes version decisions only; an empty changeset directory does not invent a release just to test publishing. Resolve duplicate manifest names within one ecosystem, unknown `PackageId` references, and dependency cycles first. Semifold refuses to plan through ambiguous relationships. ## 4. Validate with one real change [#4-validate-with-one-real-change] Choose a small change with a clear impact and use the interactive commands: ```bash smif commit smif status smif version --dry-run ``` Confirm the directly changed package, propagation reasons, target versions, and planned files. Then commit the code and `.changes/*.md` together. When GitHub Actions are enabled, let automation maintain the release pull request and publish after it is merged; do not repeat `version` and `publish` locally. [Make your first release](https://semifold.noctisynth.org/docs/getting-started/first-release/) walks through the complete path. ## As the repository changes [#as-the-repository-changes] After adding, moving, or removing packages, run: ```bash smif config sync ``` The default operation applies safe additions and path changes while reporting packages that can no longer be discovered. Configuration is removed only with an explicit `--prune`. Avoid maintaining an existing setup by repeatedly running `smif init --force`, because that obscures the boundary between discovery facts and hand-maintained release policy. --- # Make your first release Initialize a small repository, record a change, review affected packages, update versions, and publish. Source: https://semifold.noctisynth.org/docs/getting-started/first-release/ Language: en This tutorial follows the normal interactive CLI. You will create Semifold configuration, record one changeset, inspect the packages affected by it, update files, and prepare a registry publish. ## Before you start [#before-you-start] Use a Git repository whose default branch is `main` and whose worktree is clean. The example assumes at least one valid Rust package with a `Cargo.toml`; the same lifecycle applies to other built-in ecosystems. Install Semifold first if `smif --version` does not work. The [installation guide](https://semifold.noctisynth.org/docs/getting-started/installation/) lists every supported method. ## 1. Initialize the repository [#1-initialize-the-repository] Run this from the repository root: ```bash smif init ``` The prompts guide you through: 1. which package ecosystems to discover; 2. the base and release branch names; 3. default changelog categories; 4. whether to generate GitHub Actions workflows. For this tutorial, select Rust, keep `main` and `release`, accept the default categories, and generate the workflows. Open `.changes/config.toml`. Under `[packages]`, confirm that each discovered package has the expected path and resolver. The table key is its stable `PackageId`, which may differ from the manifest's registry name. If this repository was already initialized and packages later changed, use `smif config sync` instead of running `init --force` again. ### Configure GitHub Actions permissions [#configure-github-actions-permissions] Before running the generated workflows, open the repository's **Settings → Actions → General → Workflow permissions**, then: 1. Select **Read and write permissions** so the release workflow can push prepared version commits. 2. Enable **Allow GitHub Actions to create and approve pull requests** so it can create or update the release pull request. Save the settings before continuing. Organization or enterprise policy can prevent these repository-level options from being enabled; in that case, ask an administrator to allow them at the higher level. See GitHub's [repository Actions settings documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#setting-the-permissions-of-the-github_token-for-your-repository). ## 2. Make one user-visible change [#2-make-one-user-visible-change] Change a public API or another behavior that users will notice. Keep the change small enough that its version impact is easy to judge. Now record the release intent: ```bash smif commit ``` Choose the changed package, select `patch`, `minor`, or `major`, choose a changelog category, give the changeset a short name such as `add-api`, and write a user-facing summary. Semifold creates a file like: ```md title=".changes/add-api.md" --- my-package: "minor:feat" --- Add the public API. ``` `minor` requests the version increase. `feat` chooses the changelog section; it does not select a release channel. Commit the changeset with the code change so reviewers can evaluate both together. ## 3. Review affected packages [#3-review-affected-packages] ```bash smif status ``` * your package appears with the expected current and target versions; * its direct reason points to the changeset file named `.changes/add-api.md`; * any additional package has an understandable dependency-propagation reason; * the final message says the release plan is ready, not that files were changed. If a package or version is wrong, stop here. Correct the changeset, package ID, or dependency configuration and run `smif status` again. ## 4. Choose the automated or manual path [#4-choose-the-automated-or-manual-path] If `smif init` generated the GitHub Actions workflows, the recommended path stops using local release commands here: 1. push the code change and `.changes/add-api.md`, then merge that pull request into `main`; 2. the generated workflow runs `smif ci`, updates the configured `release` branch, and creates or refreshes its release pull request; 3. review the generated versions and changelogs in that release pull request, then merge it into `main`; 4. the next workflow run sees no pending changesets and publishes the prepared package versions in dependency order. Do not also run `smif version` or `smif publish` locally for the same release. Continue below only when the repository does not use the generated workflow, or when you are deliberately learning and testing the manual mechanism in a disposable repository. ## 5. Manually preview and apply version changes [#5-manually-preview-and-apply-version-changes] Preview the file and command work first: ```bash smif version --dry-run ``` The preview does not apply normal version side effects. A configured command runs during the preview only when its configuration explicitly says `dry-run = true`; such a command must be safe to run repeatedly. When the preview matches your intent, apply it: ```bash smif version ``` Semifold updates package versions and eligible internal requirements, writes changelogs, runs configured post-version work, and consumes applied changesets. Review the Git diff and run the repository's normal tests. Commit these generated files to the `release` branch, or let the generated GitHub Actions workflow maintain the release pull request. ## 6. Manually publish [#6-manually-publish] After the version changes reach the branch used for publishing, provide the registry credentials required by the selected ecosystems. Preview the publish run: ```bash smif publish --dry-run ``` This validates package ordering and runs read-only registry pre-checks without publishing packages or creating Forge releases. When the preview is correct: ```bash smif publish ``` Packages publish in dependency order. The final report distinguishes successful, skipped, failed, and not-started packages. In CI, add `--github-release` when the configured GitHub Releases and assets should also be created. ## If something fails [#if-something-fails] * Package not found: use the `PackageId` key from `[packages]` in `.changes/config.toml`, not a guessed registry name. * Worktree is dirty: inspect and commit unrelated files first. `--allow-dirty` means you deliberately accept the current diff as input; it does not clean the repository. * Registry version already exists: Semifold skips that registry command. When GitHub Releases are enabled, it may still create a missing release; an existing release does not trigger asset recovery. * Publish partially fails: use the final successful, failed, and not-started states to repair the failing credential, pre-check, or command before retrying. Do not manually republish a version confirmed to exist. ## Automation after the workflow is clear [#automation-after-the-workflow-is-clear] Interactive prompts are the normal learning and local-maintenance path. CI systems and constrained environments can provide the same answers as flags: ```bash smif init --resolvers rust \ --base-branch main \ --release-branch release \ --default-tags \ --github-actions smif commit --name add-api \ --package my-package=minor \ --tag feat \ --summary "Add the public API." ``` These flags are an automation compatibility surface, not a different release model. You now have the complete basic lifecycle: discover packages, record a changeset, update related versions and changelogs, and publish in dependency order. Continue with the [configuration overview](https://semifold.noctisynth.org/docs/configuration/overview/) when the repository needs more policy. --- # Install Semifold Install the latest published Semifold CLI and verify that the smif command is available. Source: https://semifold.noctisynth.org/docs/getting-started/installation/ Language: en The published command is available as both `smif` and `semifold`. This documentation uses the shorter `smif` form. Unless you need to reproduce an older environment, install the latest release. The installation scripts query GitHub Releases and select the newest published stable binary release whose tag matches `semifold-vX.Y.Z`. They do not use the repository-wide `latest` pointer, which may refer to another package in the Semifold monorepo. ## Recommended: installation script [#recommended-installation-script] ```bash curl -L https://semifold.noctisynth.org/install/install.sh | sh ``` The script installs to `$HOME/.local/bin` by default. If that directory is not already on `PATH`, add it in your shell configuration. ```powershell irm https://semifold.noctisynth.org/install/install.ps1 | iex ``` The script installs to `%USERPROFILE%\.local\bin` by default. Add that directory to `PATH` if necessary. To change the installation directory, pass an argument to the downloaded script: ```bash curl -L https://semifold.noctisynth.org/install/install.sh | \ sh -s -- --install-dir "$HOME/bin" ``` ```powershell & ([scriptblock]::Create((irm https://semifold.noctisynth.org/install/install.ps1))) ` -InstallDir "$HOME\bin" ``` ## Install a specific version [#install-a-specific-version] Pass a version only when you need to reproduce a particular environment. Both `0.3.1` and `v0.3.1` resolve to the `semifold-v0.3.1` GitHub Release: ```bash curl -L https://semifold.noctisynth.org/install/install.sh | \ sh -s -- 0.3.1 ``` ```powershell & ([scriptblock]::Create((irm https://semifold.noctisynth.org/install/install.ps1))) ` -Version 0.3.1 ``` Prerelease versions are never selected by the default installation. Pass the complete prerelease version explicitly when one is required. ## Package-manager alternatives [#package-manager-alternatives] Choose the registry already trusted in your environment: ```bash cargo install semifold ``` ```bash npm install --global @semifold/cli ``` ```bash pipx install semifold ``` The npm package requires Node.js 20 or newer. It installs a platform-specific N-API binding for macOS, Windows, or glibc-based Linux on x64 and arm64. Linux musl distributions are not supported by this package yet; use the installation script or Cargo there. ## Verify the command [#verify-the-command] Open a new shell after changing `PATH`, then run: ```bash smif --version ``` If the command prints the installed Semifold version without an error, the installation is ready. You can now continue to [your first release](https://semifold.noctisynth.org/docs/getting-started/first-release/) or read [what Semifold manages](https://semifold.noctisynth.org/docs/introduction/) first. --- # Capabilities and security Understand the plugin runtime's default-deny file, network, execution, and edit boundaries. Source: https://semifold.noctisynth.org/docs/plugins/capabilities/ Language: en Semifold treats ecosystem plugins as repository code that parses untrusted project data. The runtime therefore uses explicit, operation-scoped capabilities instead of ambient machine access. ## Boundary at a glance [#boundary-at-a-glance] | Capability | Default | How it is granted | Important limit | | ---------------- | ---------------- | ------------------------------------------- | ----------------------------------------------------------------------------------- | | List files | denied | `metadata.readPatterns` | Requested and returned paths must match an authorized repository-relative glob. | | Read text | denied | Same declared pattern | Reads remain inside the project root and are subject to file and operation budgets. | | HTTPS fetch | denied | `allowed-origins` in `.changes/config.toml` | Exact HTTPS origins only; no wildcard, credential, or path-based grants. | | `URL` | available subset | built into the Boa host | Only the SDK-declared surface is supported. | | Write files | unavailable | cannot be granted | Plugins return candidate edits; the host validates and applies them. | | Start commands | unavailable | cannot be granted | Publish and hook commands stay in resolver configuration. | | Host credentials | unavailable | cannot be granted | Registry and Forge credentials never enter plugin input. | ## File access [#file-access] The plugin declares the smallest useful glob set in metadata: ```ts export const metadata = definePluginMetadata({ ecosystem: 'com.example.game', pluginVersion: '1.0.0', readPatterns: [ 'packages/*/manifest.json', 'workspace.lock', ], }); ``` `host.listFiles(pattern)` does not expand authority. The requested glob must exactly equal one entry in `readPatterns`, and every returned path is checked again. A different glob is rejected even when it appears narrower than a declared value. `host.readText(path)` applies the same project-root, glob, path-encoding, file-size, and cumulative-operation checks. Use forward-slash, repository-relative paths in protocol data. Do not depend on the caller's current working directory. ## Network access [#network-access] Grant exact origins in configuration: ```toml [plugins."com.example.game"] path = "plugins/game.js" allowed-origins = [ "https://api.example.com", "https://metadata.example.net:8443", ] ``` The runtime rejects non-HTTPS URLs, embedded credentials, unapproved ports, and origins that only look similar. Request and response bodies, request counts, redirects, concurrency, and elapsed requests are bounded. The transport does not inherit a system proxy. `fetch` and `URL` are deliberately smaller than browser APIs. Use `@semifold/plugin-sdk` types as the supported contract; the presence of a familiar Web API name does not imply a DOM, cookies, browser cache, or Node.js behavior. ## Candidate edits, not direct writes [#candidate-edits-not-direct-writes] `plan-edits` returns declarative file edits. For an existing file, an edit includes the SHA-256 of the bytes the plugin inspected. The host then verifies: * target paths are valid and remain inside the repository; * the package and dependency named by the edit exist; * the edit source is consistent with the release plan; * current file content still matches the expected hash; * duplicate targets and cross-adapter conflicts do not exist; * the proposed edit is accepted by the normal file-edit executor. The plugin never receives a writable file handle. ## Runtime and protocol failures [#runtime-and-protocol-failures] A plugin should return structured diagnostics for expected domain failures. Semifold also turns runtime exceptions, invalid schema versions, missing operations, mismatched output operations, malformed paths, resource exhaustion, and invalid edits into plugin-scoped errors. One plugin failure stops the operation that needed it; it does not grant fallback access or silently accept a partial workspace. ## What remains outside plugins [#what-remains-outside-plugins] The host continues to own: * changeset parsing and bump merging; * cross-ecosystem dependency graph validation; * release-channel version calculation; * changelog rendering; * registry pre-check and command execution; * GitHub Release and asset upload; * final file application and recovery reporting. This boundary keeps a custom package format extensible without turning a plugin into an unrestricted release script. --- # Plugin system Extend Semifold to package formats beyond the four built-in ecosystems. Source: https://semifold.noctisynth.org/docs/plugins/overview/ Language: en Semifold's plugin system lets a repository teach Semifold how to discover, inspect, and version packages from another ecosystem. The result participates in the same workspace dependency graph, changesets, version propagation, configuration synchronization, and release workflow as a built-in package. The ecosystem plugin system is available since Semifold v0.3.0. ## When to write a plugin [#when-to-write-a-plugin] Use a plugin when the repository contains a real package format that Rust, Node.js, Python, or C++ adapters do not understand. A plugin is appropriate when Semifold needs to: * find package manifests and stable package IDs; * read current versions and package-to-package dependencies; * distinguish package-local and shared version sources; * update versions and internal requirements without losing manifest structure. Do not write a plugin merely to customize `cargo publish`, `npm publish`, or another release command. Publish commands, registry checks, GitHub Releases, and assets remain normal `[resolver.]` and package configuration. ## One adapter contract [#one-adapter-contract] A plugin exports schema-v1 metadata and one default async entrypoint. Semifold calls three operations: | Operation | Input | Required output | | ------------ | -------------------------------------------------------------- | ------------------------------------------------------------------- | | `discover` | Project root | Complete package inspections for every package found by the plugin. | | `inspect` | One configured package location | The current package inspection. | | `plan-edits` | Workspace snapshots, released package IDs, and target versions | Candidate file edits with expected file state and an edit source. | Each package inspection includes its `PackageId`, manifest name, semantic version, version source, ecosystem ID, path, publishability, and manifest dependencies. The host resolves manifest names to stable package IDs and builds the cross-ecosystem graph. ## Repository-local and authenticated [#repository-local-and-authenticated] Plugins are registered by stable ecosystem ID: ```toml title=".changes/config.toml" [plugins."com.example.game"] path = "plugins/game.js" sha256 = "64-lowercase-hex-characters" [packages.engine] path = "engine" resolver = "com.example.game" [resolver."com.example.game"] ``` The path must stay inside the repository. An optional SHA-256 pin authenticates the exact plugin file before it is loaded. Registry entries are sorted by ecosystem ID, so behavior does not depend on discovery order. ## Capability-scoped runtime [#capability-scoped-runtime] The embedded Boa runtime starts with no project-file or network access. Metadata explicitly lists readable glob patterns, and configuration explicitly lists exact HTTPS origins. The runtime exposes only the supported `host.listFiles`, `host.readText`, `fetch`, and `URL` subsets. A plugin cannot: * write files directly; * start child processes or publish commands; * read registry or Forge credentials from the host; * create GitHub Releases or upload assets; * use Node.js built-ins, a DOM, or runtime module loading. Semifold validates plugin metadata, responses, diagnostics, paths, dependencies, expected hashes, edit conflicts, and resource budgets before applying any candidate edit. ## SDK and bundling status [#sdk-and-bundling-status] `@semifold/plugin-sdk` provides TypeScript wire types and response builders generated from the Rust schema. Its runtime declarations describe the actual Boa `fetch`, `URL`, and file-host surface instead of pretending browser or Node.js APIs exist. A companion Vite plugin for producing one ESM file and rejecting unsupported imports or globals is not implemented. For the first release, use your existing bundler and verify that the configured output is one ESM file with no runtime imports. Continue with [write a plugin](https://semifold.noctisynth.org/docs/plugins/quick-start/) or read the [capability and security model](https://semifold.noctisynth.org/docs/plugins/capabilities/). --- # Write an ecosystem plugin Define a schema-v1 JavaScript adapter, bundle it, register its capabilities, and verify discovery and version planning. Source: https://semifold.noctisynth.org/docs/plugins/quick-start/ Language: en This guide shows the complete integration path. The example package format stores one `manifest.json` per package with `name`, `version`, and `dependencies` fields. ## 1. Choose a stable ecosystem ID [#1-choose-a-stable-ecosystem-id] Use a reverse-domain ID that you control, for example `com.example.game`. Built-in IDs such as `rust` and `nodejs` are reserved. Create a source directory and install the typed SDK: ```bash bun add --dev @semifold/plugin-sdk ``` ## 2. Export metadata and an entrypoint [#2-export-metadata-and-an-entrypoint] ```ts title="plugins/game.ts" import { createPluginFailure, createPluginSuccess, definePlugin, definePluginMetadata, type PluginHostV1, type PluginPackageInspectionV1, } from '@semifold/plugin-sdk'; const ecosystem = 'com.example.game'; export const metadata = definePluginMetadata({ ecosystem, pluginVersion: '1.0.0', readPatterns: ['packages/*/manifest.json'], }); async function inspectPackage( id: string, path: string, host: PluginHostV1, ): Promise { const manifest = JSON.parse( await host.readText(`${path}/manifest.json`), ) as { name: string; version: string; dependencies?: Record; }; return { id, 'manifest-name': manifest.name, version: manifest.version, 'version-source': { kind: 'package-manifest' }, ecosystem, path, publishable: true, dependencies: Object.entries(manifest.dependencies ?? {}).map( ([name, requirement]) => ({ 'manifest-name': name, kind: 'runtime', requirement, }), ), }; } export default definePlugin(async (request, host) => { switch (request.operation) { case 'discover': { const manifests = await host.listFiles('packages/*/manifest.json'); const packages = await Promise.all( manifests.map((manifest) => { const path = manifest.slice(0, -'/manifest.json'.length); return inspectPackage(path, path, host); }), ); return createPluginSuccess(request, { packages }); } case 'inspect': { const { id, path } = request.input.package; return createPluginSuccess(request, { package: await inspectPackage(id, path, host), }); } case 'plan-edits': return createPluginFailure(request, ecosystem, { code: 'plan-edits-not-implemented', message: 'Add deterministic manifest edits before enabling releases.', }); } }); ``` This first version intentionally makes discovery and inspection testable while refusing version writes. A failure response is safer than returning success with incomplete edits. ## 3. Implement version edits [#3-implement-version-edits] For every package in `request.input['released-packages']`, find its snapshot in `request.input['workspace-packages']` and target version in `request.input.versions`. Return one or more edits: ```ts { path: 'packages/engine/manifest.json', expected: { kind: 'existing', sha256: '', }, 'new-content': '{\n "name": "engine",\n "version": "1.1.0"\n}\n', source: { kind: 'package-version', package: 'packages/engine', }, } ``` When an internal dependency requirement changes, use `dependency-version` as the source and name both the owner package and dependency. For a shared workspace manifest, use `workspace-manifest` and list its shared version edits and dependencies. The protocol requires the SHA-256 of every existing target. Bundle a deterministic SHA-256 implementation with the plugin; the Boa host does not expose Node.js `crypto`. Semifold re-hashes the file before applying the edit, rejects stale content, and performs its normal path and conflict checks. ## 4. Produce one ESM file [#4-produce-one-esm-file] Bundle the SDK helpers and all other source into one ESM file, for example `plugins/game.js`. The configured file must have: * named `metadata` export; * default async plugin entrypoint; * no runtime `import` statements; * no Node.js built-ins, dynamic module loading, DOM assumptions, or unsupported Web APIs. Semifold does not yet ship the planned Vite integration that automates this check. Configure your existing bundler to inline dependencies and inspect the final file before registration. ## 5. Register the plugin [#5-register-the-plugin] ```toml title=".changes/config.toml" [plugins."com.example.game"] path = "plugins/game.js" [resolver."com.example.game"] pre-check = { type = "command", command = "./scripts/game-version-exists" } publish = [{ command = "./scripts/publish-game-package" }] ``` The ecosystem ID in the table must exactly match `metadata.ecosystem`. Add an optional `sha256` after the bundle stabilizes. Add `allowed-origins` only when the plugin genuinely needs a specific HTTPS service. ## 6. Discover and verify [#6-discover-and-verify] ```bash smif config sync --resolver com.example.game --check ``` Review the proposed package IDs and paths. Apply synchronization without `--check`, then implement and test `plan-edits` before creating a changeset: ```bash smif config sync --resolver com.example.game smif status smif version --dry-run ``` The plugin only proposes edits. Semifold still validates the complete cross-ecosystem dependency graph and applies all accepted edits through the same host-controlled file executor. Read [capabilities and security](https://semifold.noctisynth.org/docs/plugins/capabilities/) before granting file or network access. --- # CLI reference moved Continue to the command-line module for Semifold command behavior and options. Source: https://semifold.noctisynth.org/docs/reference/cli/ Language: en The CLI reference now lives with the detailed command guides. Continue to the [Semifold command reference](https://semifold.noctisynth.org/docs/commands/reference/). --- # Synchronize workspace configuration Maintain .changes/config.toml after packages are added, moved, or removed without overwriting release policy. Source: https://semifold.noctisynth.org/docs/workspace/config-sync/ Language: en `smif init` creates configuration once. As repository structure changes, use `smif config sync` to discover packages again and update `[packages]` locally. ```bash smif config sync ``` Synchronization edits TOML while preserving formatting. Existing comments, field order, blank lines, `publish` overrides, publish commands, assets, channels, and `depends-on` values remain intact. Newly discovered packages do not receive an automatic `publish` field. Repeating the command with the same input produces no further diff. ## Default synchronization behavior [#default-synchronization-behavior] The command compares configured packages with current discovery and shows a plan before applying it: * newly discovered packages can be added to configuration; * package path changes can be updated when the match is reliable; * configured packages that are no longer discovered are reported but not removed by default; * ambiguous renames, duplicate names, and parse failures stop synchronization. Synchronization never guesses cross-ecosystem dependencies from directory names and does not create `depends-on` for new packages. Those relationships require an explicit maintainer decision. ## Read-only checks in CI [#read-only-checks-in-ci] ```bash smif config sync --check ``` `--check` writes nothing and returns a non-zero status whenever configuration needs synchronization. It is suitable for preventing an unconfigured package from entering a pull request. Global `--dry-run` also writes nothing, but serves a different purpose: it previews the complete synchronization plan and does not treat drift as an assertion failure. ## Remove packages that are gone [#remove-packages-that-are-gone] ```bash smif config sync --prune ``` `--prune` removes configuration for packages that cannot currently be discovered. Confirm that each package was deliberately removed rather than temporarily invalid or outside a discovery boundary. `--prune` conflicts with `--check`. Removing configuration may invalidate changesets or `depends-on` entries that reference its `PackageId`. After applying the change, run: ```bash smif status ``` to validate those references through the workspace graph. ## Synchronize selected ecosystems [#synchronize-selected-ecosystems] `--resolver` is repeatable: ```bash smif config sync --resolver rust --resolver nodejs ``` Only the selected ecosystems are scanned, which is useful for targeted maintenance in a large repository. Values are ecosystem IDs from configuration; a plugin ecosystem uses its own ID as well. ## Migration and synchronization are different [#migration-and-synchronization-are-different] `smif config migrate` converts legacy v0.2.x fields to the current TOML contract. It can replace `version-mode` with `channel`, rename known snake\_case fields to kebab-case, and add `type = "http"` to a legacy HTTP pre-check. It does not perform package discovery. Migrate old configuration first, then synchronize it with the repository: ```bash smif config migrate --check smif config migrate smif config sync --check ``` See [Configuration commands](https://semifold.noctisynth.org/docs/commands/config/) for the complete options and conflicts. --- # C++ workspaces Static CMake package discovery, version edits, internal link ordering, and current limits. Source: https://semifold.noctisynth.org/docs/workspace/cpp/ Language: en The built-in C++ adapter targets CMake projects that can be analyzed statically. It does not run CMake or try to interpret directories and targets that exist only at build time. ## Root project requirement [#root-project-requirement] The project root must contain `CMakeLists.txt` with a numeric version in `project(...)`: ```cmake project(my_library VERSION 1.4.0 LANGUAGES CXX) ``` A root project without `VERSION` cannot serve as the current C++ workspace entry point. The `project` name is the manifest name used for matching packages within the C++ workspace. ## Subproject discovery [#subproject-discovery] Semifold recursively follows literal calls such as: ```cmake add_subdirectory(libs/core) ``` An intermediate directory may only group other directories. Every reachable directory whose own `CMakeLists.txt` contains `project(... VERSION ...)` is discovered as a package. To keep discovery deterministic and safe, the adapter does not interpret: * subdirectories assembled from variables; * generator expressions; * paths downloaded or generated by scripts or the configure phase; * relative paths that escape the project root. A resolved path outside the project root fails discovery instead of reading an external project. ## Internal dependencies [#internal-dependencies] The adapter recognizes a static call whose first argument is the current project name: ```cmake target_link_libraries(my_library PRIVATE support_library) ``` When `support_library` is another discovered `project` name in the same workspace, an internal edge is created. `PUBLIC`, `PRIVATE`, and `INTERFACE` all affect dependency order only and do not automatically propagate versions. The adapter does not guess relationships from variables, alias targets, generator expressions, or other indirect links. Add `depends-on` to Semifold configuration when a release must propagate. ## Version edits [#version-edits] Semifold edits `project(... VERSION ...)` in the corresponding `CMakeLists.txt`. When the package directory also contains `vcpkg.json`, it synchronizes the root `version` field so the two public version sources do not drift. The current CMake adapter accepts stable numeric versions only. Named channels such as `alpha`, `beta`, and `rc` are unsupported. Setting a named `channel` for a C++ package fails version planning; for custom prerelease formats, first evaluate whether a plugin and custom publish workflow can represent the complete format. ## Publishing [#publishing] CMake describes a build graph but does not define one registry, so built-in discovery treats C++ packages as publishable by default. Set `publish = false` explicitly for a package that should not enter the registry flow. Semifold keeps checks, builds, uploads, and publish behavior in `[resolver.cpp]` command configuration and runs it in dependency order through the unified publish plan. Use `smif publish --dry-run` to inspect preflight and command behavior. Commands run in the package directory. Put compound CMake configuration, packaging, or registry-client logic in a repository script instead of placing shell operators in `args`. ## When to use a plugin [#when-to-use-a-plugin] The following cases are generally outside the built-in static rules: * the version is not in `project(... VERSION ...)`; * workspace members come from custom metadata or runtime scripts; * internal dependencies use CMake logic that cannot be expanded statically; * versions require non-numeric or custom prerelease formats. A plugin can define discovery, inspection, and candidate version edits for these repositories. Publish commands remain in resolver configuration. --- # Dependencies and version propagation Distinguish dependency ordering, automatic version propagation, and cross-ecosystem depends-on edges. Source: https://semifold.noctisynth.org/docs/workspace/dependencies/ Language: en Semifold places packages from every built-in ecosystem and plugin into one dependency graph. The graph answers two separate questions: 1. which package must be processed first for edits, builds, and publishing; 2. whether publishing a dependency also requires a release of its dependent. “Participates in ordering” does not mean “automatically publishes.” This distinction is essential when reading `smif status`. ## Manifest dependencies always participate in ordering [#manifest-dependencies-always-participate-in-ordering] When an adapter can uniquely match a manifest dependency to a package in the same ecosystem, it becomes an internal graph edge. Rust runtime, development, and build dependencies, together with recognized Node.js, Python, and C++ dependencies, all participate in deterministic topological ordering. Dependencies appear before dependents. Unrelated packages are ordered by stable `PackageId`, so repeated runs do not produce arbitrary sequences. ## Current automatic propagation rules [#current-automatic-propagation-rules] | Dependency source | Participates in ordering | Automatically releases the dependent | | ------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------- | | Rust `[dependencies]` | yes | only when the dependency's new version no longer satisfies the Rust requirement; the dependent receives `patch` | | Rust development/build dependency | yes | no | | Node.js, Python, or C++ manifest dependency | yes | no | | Configured `depends-on` | yes | yes; the dependent receives `patch` | Semifold does not approximate npm, PEP 440, or CMake constraints with Rust semver. Their manifest edges currently determine order only. When a lower-level package release must rebuild and republish a binding, declare `depends-on` explicitly. ## Declare a cross-ecosystem relationship [#declare-a-cross-ecosystem-relationship] Reference the dependency's stable `PackageId` from the dependent package: ```toml [packages.rust-core] path = "crates/core" resolver = "rust" [packages.node-binding] path = "bindings/node" resolver = "nodejs" depends-on = ["rust-core"] ``` This edge has three effects: * `rust-core` is edited and published before `node-binding`; * any `rust-core` release gives an otherwise unchanged `node-binding` a `patch` release; * if `node-binding` already has a `minor` or `major` changeset, the higher bump remains and the propagation reason is added. `depends-on` may also connect packages in the same ecosystem when a manifest cannot express a rebuild or republish relationship. ## How names are matched [#how-names-are-matched] Manifest dependencies match by ecosystem plus manifest name, not directly by `PackageId`. Therefore: * a dependency outside the workspace remains external; * duplicate manifest names within one ecosystem are ambiguous and fail loading; * equal names in different ecosystems never create an implicit edge; * `depends-on` must use the exact `PackageId` from configuration. ## Diagnose unexpected propagation [#diagnose-unexpected-propagation] Run: ```bash smif status ``` Every planned package lists either a direct changeset or a dependency-propagation reason. If the result is surprising, check in order: 1. whether the changeset names the correct `PackageId`; 2. whether a Rust runtime requirement still includes the dependency's new version; 3. whether each `depends-on` represents a real republish requirement; 4. whether manifest names are accidentally duplicated. Unknown `PackageId` values and dependency cycles stop planning with structured errors. Remove a cycle from the manifests or configuration; Semifold does not hide it by selecting an arbitrary order. --- # Node.js workspaces Current support for package.json, npm and pnpm workspaces, dependency prefixes, and private packages. Source: https://semifold.noctisynth.org/docs/workspace/nodejs/ Language: en The built-in Node.js adapter uses `package.json` as the package fact source and supports npm-style `workspaces` together with `pnpm-workspace.yaml`. ## Package discovery [#package-discovery] Semifold reads these declarations from the project root: * `workspaces` in the root `package.json`; * `packages` in `pnpm-workspace.yaml`. In workspace mode, the root `package.json` is also inspected and discovered as a package. Without a workspace declaration, the root `package.json` is read as one package. The manifest `name` is used for dependency matching. An explicit `version` must be valid semver. A template package with no `version` is read as `0.0.0`, and its first version operation inserts the target version. `private: true` disables registry publishing by default, but optional package configuration `publish` can override the effective eligibility used by Semifold. `publish = true` does not remove `private` from `package.json`, so the default `npm publish` command may still reject it. A private package continues to participate in version plans, dependency ordering, and file edits. ## Dependency inspection [#dependency-inspection] The adapter reads: * `dependencies`; * `devDependencies`; * `peerDependencies`; * `optionalDependencies`. A dependency that matches another Node.js manifest name in the workspace enters the shared topological order. These npm constraints do not currently trigger automatic dependent releases because npm ranges cannot be approximated with Cargo's constraint rules. If a Node.js package must rebuild or republish whenever an internal dependency releases, add `depends-on` to its Semifold package configuration. ## Version and dependency edits [#version-and-dependency-edits] When editing `package.json`, Semifold validates the complete document with a JSON parser, preserves existing object key order, writes standard indentation, and keeps one trailing newline. It does not promise to retain original whitespace that a standard JSON parser cannot represent. When an internal dependency target must be updated, common declaration intent is retained: * `workspace:*` remains `workspace:*`; * `workspace:^` and `workspace:~` keep their respective prefixes; * ordinary `^` and `~` ranges keep their prefixes. Use `smif version --dry-run` to inspect the exact diff before applying it, especially for historically hand-formatted `package.json` files. ## Release channels and npm tags [#release-channels-and-npm-tags] Configuration such as `channel = "rc"` controls the version calculated by Semifold, but does not rewrite `npm publish` arguments in a shared resolver. A package on a named channel should configure a matching `--tag` explicitly so a prerelease does not enter npm's default `latest` tag. `smif config channel set` warns when an affected Node.js resolver lacks an explicit `--tag`, but it does not choose or edit the tag for you. ## Publishing [#publishing] Registry pre-check, prepublish, and publish commands come from `[resolver.nodejs]`. Semifold runs them in dependency order after unified preflight. A package with `private: true` skips registry preflight and publish commands. GitHub Releases are disabled by default as well, but package-level `github-release = true` can enable one independently. ## Current boundaries [#current-boundaries] * Only root `package.json.workspaces` and `packages` in `pnpm-workspace.yaml` are built-in workspace entry points. Package-manager-specific discovery beyond these needs a plugin or explicit support. * Node.js manifest dependencies affect ordering but do not automatically propagate versions. * A missing `version` reads as `0.0.0`; an explicitly invalid version is an error rather than a defaultable value. --- # Package discovery Understand how Semifold builds one workspace from different manifests and where stable configured package identities come from. Source: https://semifold.noctisynth.org/docs/workspace/package-discovery/ Language: en Each ecosystem adapter discovers its own packages, then Semifold combines those results into one workspace graph. Changeset validation, version propagation, file edits, and publish ordering all use this graph. ## What discovery returns [#what-discovery-returns] Every discovered package provides at least: * its manifest name and current version; * its ecosystem and repository-relative path; * whether it can be published to an external registry; * internal dependencies that can be recognized from its manifest. `smif init` uses these facts to create the initial `[packages]` configuration. `smif config sync` compares existing configuration with the latest discovery result. An adapter does not decide repository-wide versions, write files directly, run publish commands, or access GitHub. Discovered publishability is the default. Package configuration may override it with optional `publish = true` or `publish = false`; when omitted, Semifold keeps the marker inferred by Rust, Node.js, or a plugin. This override controls Semifold's registry flow only and does not edit the manifest. ## Built-in discovery boundaries [#built-in-discovery-boundaries] | Ecosystem | Primary manifest or version source | Discovery entry point | Complete rules | | --------- | ------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------- | | Rust | `Cargo.toml` | A single package or Cargo workspace members | [Rust workspaces](https://semifold.noctisynth.org/docs/workspace/rust/) | | Node.js | `package.json` | Root package, `workspaces`, or `pnpm-workspace.yaml` | [Node.js workspaces](https://semifold.noctisynth.org/docs/workspace/nodejs/) | | Python | `pyproject.toml`, `setup.cfg`, or a source version file | Root plus `packages/*`, `libs/*`, and `apps/*` | [Python workspaces](https://semifold.noctisynth.org/docs/workspace/python/) | | C++ | `CMakeLists.txt` with `project(... VERSION ...)` | Root project and statically reachable `add_subdirectory(...)` entries | [C++ workspaces](https://semifold.noctisynth.org/docs/workspace/cpp/) | This table only helps select an entry point. Each ecosystem page explains dynamic versions, private packages, dependency categories, and static-analysis limits. ## `PackageId` versus manifest name [#packageid-versus-manifest-name] A package table key is its stable `PackageId`: ```toml [packages.rust-core] path = "crates/core" resolver = "rust" ``` The manifest name is the native Cargo, npm, PyPI, or CMake name. Discovery proposes it as an initial ID, but configured identity is stable. After configuration exists, Semifold uses `rust-core` for this package even if its directory or manifest name later changes. Every `PackageId` in one configuration must be globally unique. Manifest names must also be unique within an ecosystem so manifest dependencies have one unambiguous target. Different ecosystems may use the same manifest name. On the first `smif init`, Semifold automatically adds the ecosystem prefix to those packages. For example, Rust and Node.js packages both named `shared` become `rust-shared` and `nodejs-shared`: ```toml [packages.rust-shared] path = "crates/shared" resolver = "rust" [packages.nodejs-shared] path = "packages/shared" resolver = "nodejs" ``` Packages without a name collision still use the manifest name directly. If another package already occupies a prefixed ID, initialization appends a numeric suffix in stable discovery order, such as `rust-shared-2` or `rust-shared-3`. Duplicate manifest names within one ecosystem still report `DuplicatePackageId`, because renaming the configured IDs cannot resolve ambiguous manifest dependencies. Automatic prefixing only applies when creating the initial configuration. Existing configuration preserves stable IDs by ecosystem and path, and `config sync` does not rename configured packages. If a later discovery finds multiple unconfigured packages with the same name across ecosystems, `config sync` still reports a conflict so a maintainer can choose their IDs explicitly. Matching names in two ecosystems never imply a dependency. A cross-ecosystem edge must use [`depends-on`](https://semifold.noctisynth.org/docs/workspace/dependencies/) to reference a stable ID. ## Plugin-defined ecosystems [#plugin-defined-ecosystems] Built-in adapters are not a closed list. A JavaScript plugin can implement the same three workspace operations: 1. discover packages; 2. inspect the current facts for a configured package; 3. return candidate file edits for target versions. Plugin results enter the same workspace graph and receive the same identity, unknown-dependency, cycle, and file-edit validation. A plugin does not publish packages directly; registry pre-checks and commands remain in `[resolver.]` configuration. See [Plugin overview](https://semifold.noctisynth.org/docs/plugins/overview/). ## What a discovery failure means [#what-a-discovery-failure-means] The following problems stop workspace loading instead of being silently ignored: * a configured path leaves the project root or no longer contains the expected manifest; * duplicate manifest names within one ecosystem are ambiguous; * `depends-on` references an unknown `PackageId`; * internal dependencies form a cycle; * a manifest exists but its name, explicit version, or structure is invalid. Fix the manifest or configuration and rerun `smif config sync --check`. If the issue is an ecosystem discovery boundary, use the matching page to decide whether to adjust repository structure, configure an explicit relationship, or write a plugin. --- # Python workspaces pyproject.toml, setup.cfg, dynamic version sources, discovery directories, and release-channel limits. Source: https://semifold.noctisynth.org/docs/workspace/python/ Language: en The built-in Python adapter prefers `pyproject.toml` and falls back to `setup.cfg` when no usable project metadata is available. It handles both static versions and a defined set of common dynamic-version layouts. ## Package discovery [#package-discovery] Semifold inspects the project root and direct children of: * `packages/*`; * `libs/*`; * `apps/*`. A directory is parsed only when it contains `pyproject.toml` or `setup.cfg`. Arbitrarily deep directories, tool-specific workspace declarations, and projects generated at runtime are not discovered automatically. Use an adjusted layout or a plugin for those cases. Supported project metadata includes: * PEP 621 `[project]`; * Poetry `[tool.poetry]`; * package metadata in `setup.cfg`. ## Dynamic versions [#dynamic-versions] When PEP 621 declares `dynamic = ["version"]`, the adapter checks these explicitly supported sources: * a static `__version__` in a common package `__init__.py`; * `__version__.py`; * the corresponding file under a `src/...` layout; * a sibling `Cargo.toml` version for common maturin or PyO3 projects; * Hatch `version.path`. Only a source that can be located and parsed statically can be edited safely. When a dynamic version cannot currently be read, discovery warns and uses `0.0.0`. Before creating real version edits, confirm that the source is in the supported set so a placeholder is not mistaken for repository fact. ## Version edits [#version-edits] Depending on the discovered source, Semifold can edit: * PEP 621 `project.version`; * Poetry `version`; * the version field in `setup.cfg`; * a static `__version__`; * the file referenced by Hatch `version.path`. A Python binding may read a sibling `Cargo.toml` as a dynamic version source, but the Python adapter never writes a Rust manifest. Cross-ecosystem packages keep independent version sequences by default. Add `depends-on` to the binding when a Rust release must trigger its Python release. ## Dependencies and propagation [#dependencies-and-propagation] The adapter reads dependencies from supported Python formats and adds workspace matches to ordering. Python manifest constraints do not currently trigger dependent releases because PEP 440 semantics cannot be replaced with Rust semver rules. Use explicit `depends-on` for bindings, generated packages, or other upper layers that must rebuild and republish. ## Release channels [#release-channels] Python versions support these named channel mappings: | Semifold channel | Python version form | | ---------------- | ------------------- | | `alpha` | `aN` | | `beta` | `bN` | | `rc` | `rcN` | | `post` | `.postN` | Other named channels cannot produce a supported Python version and fail planning. Stable releases are unaffected. ## Publishing [#publishing] Registry pre-checks and publish commands come from `[resolver.python]`; the Python adapter does not execute them directly. Use `smif publish --dry-run` to validate the current version and preflight before publishing. Built-in Python discovery does not currently infer a private-package marker from project metadata; every discovered Python package is publishable by default. Set `publish = false` in the corresponding `[packages.]` when it should not enter the registry flow. Package-level `github-release` still controls GitHub Release behavior independently. --- # Rust workspaces Cargo discovery, shared versions, dependency requirement edits, and private release behavior for Rust packages. Source: https://semifold.noctisynth.org/docs/workspace/rust/ Language: en The built-in Rust adapter reads and edits `Cargo.toml` for both a single Cargo package and a Cargo workspace. Cargo remains responsible for builds and dependency resolution; Semifold connects the discovered facts to repository-wide versioning and publishing. ## Package discovery [#package-discovery] When the project-root `Cargo.toml` contains `[workspace]`, Semifold expands `workspace.members` globs and inspects member manifests. A root manifest that also contains `[package]` contributes the root package. Without a workspace, the root `Cargo.toml` is read as one package. The Cargo package name is its manifest name. It may seed a `PackageId` during initialization, but the ID in existing configuration is the stable identity used by changesets and graph edges. Cargo `publish = false` makes a crate unavailable for registry publishing by default. Optional package configuration `publish` can override the effective eligibility used by Semifold. `publish = true` does not remove the Cargo field, so the default `cargo publish` command may still reject it. A private crate continues to participate in version calculation, shared-version groups, file edits, and dependency ordering. ## Dependency inspection [#dependency-inspection] The adapter reads: * `[dependencies]`; * `[dev-dependencies]`; * `[build-dependencies]`; * `[workspace.dependencies]` in the root manifest. A dependency alias is resolved through its `package` field. When a member uses `workspace = true`, Semifold understands and updates the requirement in shared `[workspace.dependencies]` instead of inserting a duplicate version in the member manifest. Every internal dependency participates in ordering. Currently, only runtime dependencies from `[dependencies]` receive constraint-aware automatic propagation: when a dependency's new version no longer satisfies the existing Cargo requirement, the dependent receives a `patch` release. Development and build dependencies affect order only. ## Version edits [#version-edits] A normal package receives its target version in `package.version`. Rust manifests use format-preserving TOML edits, so an unrelated layout is not reordered for one version field. When an internal runtime requirement needs an update, Semifold handles normal, aliased, and workspace-inherited declarations together. It only considers rewriting a requirement when the dependency's target version changes, avoiding needless normalization of a valid broad range. ## Shared workspace versions [#shared-workspace-versions] Members may use: ```toml [package] version.workspace = true [workspace.package] version = "1.4.0" ``` Packages backed by the same `[workspace.package].version` form one version group. When any member changes: * every member enters the release plan; * the highest requested changeset bump applies to the group; * every member receives the same target version; * the root `Cargo.toml` shared version is edited once; * a private member can advance the shared version and bring public members into the release. All members must have exactly the same `channel` and pending `channel-bump`. A mismatch fails planning rather than choosing whichever package is visited first. ## Publishing [#publishing] The Rust adapter only provides manifest facts and candidate edits. Registry pre-checks, pre-publish commands, and publish commands come from `[resolver.rust]` and run through the shared publisher in dependency order. A private crate skips registry preflight and publish commands. It does not create a GitHub Release by default, but `github-release = true` can explicitly enable a Release and assets. This policy is independent of Cargo's `publish = false`. ## Common problems [#common-problems] * A workspace member glob omits a package: fix `workspace.members` instead of inventing the package only in Semifold configuration. * `version.workspace = true` has no valid `[workspace.package].version`: provide the shared source. * Two crates have the same package name: remove the Cargo-level ambiguity; changing only `PackageId` cannot make manifest dependency matching unique. * A Rust change must republish a Node.js or Python binding: add a cross-ecosystem `depends-on` to the binding package. --- # 与 Agent 一起使用 Semifold 为发布任务提供按需读取的 Markdown 文档和可移植的 Semifold Skill。 Source: https://semifold.noctisynth.org/zh/docs/agents/ Language: zh Agent 可以通过[英文索引](https://semifold.noctisynth.org/llms.txt)、[中文索引](https://semifold.noctisynth.org/zh/llms.txt)或[双语全文](https://semifold.noctisynth.org/llms-full.txt)读取与网页同源的文档。索引直接链接逐页 Markdown,便于按任务读取。每个文档网页通过 HTML alternate 链接标记对应的 Markdown 版本。 ## 获取 Semifold Skill [#获取-semifold-skill] 仓库以 [Agent Skills 格式](https://agentskills.io/specification)分发 [skills/semifold](https://github.com/noctisynth/semifold/tree/main/skills/semifold)。将包含 `references/` 的完整 `semifold` 目录复制到客户端支持的技能目录,或使用客户端的仓库技能安装功能,指定仓库 `noctisynth/semifold` 与路径 `skills/semifold`。安装和发现位置由客户端决定,本仓库不会自动执行全局安装。 Skill 将 changeset 创建、配置维护、发布计划检查和发布故障定位引导到已有命令文档,并遵守使用者仓库规则及授权范围。Semifold 仓库根目录的 `AGENTS.md` 则服务于修改 Semifold 自身源码的 Agent。 ## 开始任务前 [#开始任务前] 运行 `smif --version` 和 `smif --help`,读取目标仓库的 `.changes/config.toml` 和工作流,使用真实配置的 package ID 和 tag。旧版本与新文档示例不一致时,以已安装版本的 help 确认参数,不从网站文档推断本地版本。 ## 任务检查点 [#任务检查点] | 任务 | 前置条件与操作 | 预期结果和副作用 | 恢复参考 | | ------ | ------------------------------------------ | ----------------------------------------------- | -------------------------------------- | | 记录变更 | 使用配置中的 package ID;非交互 `smif commit` 提供完整参数 | 创建一个 changeset,不修改版本;随后查看 `smif status` | [Changeset](https://semifold.noctisynth.org/zh/docs/commands/commit/) | | 检查发布计划 | 有效配置和 changeset;`smif status` | 展示包、版本和传播原因,不改包文件;`--comment` 会写 GitHub | [状态](https://semifold.noctisynth.org/zh/docs/commands/status/) | | 维护配置 | 已接入项目;`smif config sync --check` | 检查差异;执行 `config sync` 才应用配置更新 | [配置](https://semifold.noctisynth.org/zh/docs/commands/config/) | | 准备版本 | 确定手动或 CI 流程并检查 hook | 更新版本和 changelog,消费 changeset;hook 失败可能保留部分已应用状态 | [版本更新](https://semifold.noctisynth.org/zh/docs/commands/version/) | | 发布 | 版本已准备、凭据就绪且发布范围明确 | 写入 registry 及按配置操作 GitHub,报告部分完成状态 | [发布](https://semifold.noctisynth.org/zh/docs/commands/publish/) | `--dry-run` 仍可能执行明确允许 dry run 的配置命令,publish preflight 也可能访问 registry。先检查 hook,并遵守网络或本地禁止编译的约束。 使用生成的 GitHub Actions 时遵循 [CI 流程](https://semifold.noctisynth.org/zh/docs/commands/ci/),同一次发布不再重复执行本地 version 或 publish。 --- # 文档 学习怎样用 Semifold 统一管理跨语言单仓库中的软件包版本、依赖、变更日志和发布。 Source: https://semifold.noctisynth.org/zh/docs/ Language: zh Semifold 是面向跨语言单仓库的版本与发布工具。它连接使用不同清单文件和软件包仓库的软件包,再以整座仓库为范围管理变更集、相关版本修改、变更日志和按依赖发布。 ## 选择你现在要完成的事情 [#选择你现在要完成的事情] 通过安装脚本、Cargo、npm 或 PyPI 安装已经发布的命令行工具。 初始化仓库、创建变更集、检查受影响的软件包,再执行版本修改和发布。 理解清单文件、稳定软件包 ID、依赖、变更集和发布命令怎样连接起来。 学习 `.changes/config.toml` 的结构和日常维护方式。 使用已经发布的 JavaScript 插件运行时与 TypeScript SDK 接入自定义软件包格式。 区分软件包、PackageId、软件生态、变更集、发布通道、软件包仓库和各种计划。 ## 整座仓库的版本与发布生命周期 [#整座仓库的版本与发布生命周期] 每个软件包的生态适配器负责理解自己的清单文件。Semifold 则负责适配器之间的关系:稳定的软件包身份、跨生态依赖传播、变更意图、发布通道、变更日志、发布顺序和失败恢复报告。 ## 推荐阅读顺序 [#推荐阅读顺序] 如果你刚接触 Semifold,请先读[介绍](https://semifold.noctisynth.org/zh/docs/introduction/),不要从完整配置参考开始。然后找一座小仓库完成[第一次发布教程](https://semifold.noctisynth.org/zh/docs/getting-started/first-release/)。遇到陌生概念时,随时查看[术语表](https://semifold.noctisynth.org/zh/docs/concepts/glossary/)。 [命令行文档](https://semifold.noctisynth.org/zh/docs/commands/)会逐个解释命令行为,其中的 [CLI 参数参考](https://semifold.noctisynth.org/zh/docs/commands/reference/)适合快速查询,不适合作为理解工作流的起点。 --- # Semifold 是什么? 理解 Semifold 解决的仓库问题,以及它怎样连接不同的软件包生态。 Source: https://semifold.noctisynth.org/zh/docs/introduction/ Language: zh Semifold 用来管理一座仓库中来自多种软件生态的软件包版本和发布。 设想一座产品仓库:底层是 Rust 核心库,上层有 Node.js 绑定和 Python 客户端,同时还包含一个 C++ 组件。每种生态本来就有成熟的清单格式和发布工具,真正困难的是它们之间的工作: * 一项改动完成后,判断哪些软件包应该产生新版本; * 底层依赖升级时,一起修改其他语言软件包中的内部依赖要求; * 为整座仓库维护一套可以读懂的变更日志; * 按照跨语言依赖关系安排发布顺序; * 某个软件包仓库成功、另一个失败时,判断怎样恢复。 Semifold 补上的是这层整座仓库范围的版本与发布管理。它不会替代 Cargo、npm、Python 打包工具、CMake 或软件包仓库。 ## 一张工作区依赖图 [#一张工作区依赖图] 每个受管理的软件包都会成为工作区依赖图中的一个节点。节点包含: * 配置和变更集使用的稳定软件包 ID(`PackageId`); * 对应软件生态使用的清单名称或软件包仓库名称; * 当前版本、路径,以及是否需要发布; * 清单依赖和可选的显式关系; * 负责读取和修改该清单的生态适配器。 正是这张统一后的图,让 Rust 软件包的变化可以影响 Node.js 或 Python 软件包,同时不需要假装它们的清单格式完全相同。 ### 内置与自定义软件生态 [#内置与自定义软件生态] Semifold 内置 Rust、Node.js、Python 和 C++ 生态适配器。每个适配器理解实现与测试样例已经覆盖的软件包和工作区格式。 仓库内 JavaScript 生态插件实现相同的软件包发现、信息读取与修改规划边界,因此自定义软件包也能进入同一张工作区图,而不必躲在另一套发布脚本中。 ## 使用 Semifold 时实际要做的事 [#使用-semifold-时实际要做的事] ### 1. 描述整座仓库 [#1-描述整座仓库] `smif init` 发现软件包并创建 `.changes/config.toml`。这个文件为每个软件包分配稳定 ID,并保存清单文件无法表达的发布策略。 仓库结构变化后,`smif config sync` 会比较当前发现结果与已保存配置。它更新自动发现的事实,但不会悄悄覆盖维护者有意制定的策略。 ### 2. 记录一项用户可见的变化 [#2-记录一项用户可见的变化] 变更集(changeset)是一份小型 Markdown 文件,其中写明受影响的软件包 ID、期望的版本提升、变更日志分类和摘要。它会与代码改动一起接受审查,并且早于真正的版本变化。 ```md --- native-core: "minor:feat" web-bindings: "patch:fix" --- 在 Web 绑定中开放新的解析器。 ``` ### 3. 联动修改所有受影响的版本 [#3-联动修改所有受影响的版本] `smif status` 展示直接变更,以及依赖规则额外影响的软件包。`smif version` 随后通过各清单所属的生态适配器,修改软件包版本、内部依赖要求和变更日志,并消费已经应用的变更集。 发布计划(release plan)的价值在于让这项决定可以审查;Semifold 真正提供的产品能力,是计划所描述的一致跨生态版本修改。 ### 4. 按照各生态自己的规则发布 [#4-按照各生态自己的规则发布] `smif publish` 根据依赖关系排列软件包,检查目标版本是否已经存在,再为每种软件生态运行已配置的发布命令。GitHub Release 和附件可以单独启用。 最终报告会区分成功、跳过、失败和尚未开始的软件包。维护者可以修复凭据或软件包仓库配置后恢复,而不必猜测哪些版本已经对外发布。 ## Semifold 有意不接管的工作 [#semifold-有意不接管的工作] Semifold 不负责构建或测试软件包,不托管软件包仓库,不从提交消息猜测产品策略,也不会把凭据交给生态插件。构建和测试仍由仓库现有工具或明确的钩子负责;软件包仓库凭据仍留在执行环境中。 ## 在需要时学习术语 [#在需要时学习术语] 开始使用前不需要背诵所有内部概念。[术语表](https://semifold.noctisynth.org/zh/docs/concepts/glossary/)用实际区别解释了软件包 ID、生态适配器、变更集、发布通道、软件包仓库和各种计划。 下一步可以[安装 Semifold](https://semifold.noctisynth.org/zh/docs/getting-started/installation/),然后跟随[第一次发布教程](https://semifold.noctisynth.org/zh/docs/getting-started/first-release/)。 --- # 持续集成 用一条命令执行生成的 GitHub Actions 发布拉取请求或发布分支行为。 Source: https://semifold.noctisynth.org/zh/docs/commands/ci/ Language: zh 在 `smif init` 中选择 GitHub Actions 的仓库,推荐使用 `smif ci` 作为发布入口。它拒绝在 GitHub Actions 之外运行,并跳过配置基础分支以外的分支。 ```bash smif ci ``` ## 存在变更集时 [#存在变更集时] 在基础分支上,`ci` 计算版本修改,渲染配置的发布分支、提交消息与 Pull Request 标题,应用版本文件,强制更新发布分支,再创建或刷新返回基础分支的拉取请求。提交消息与 Pull Request 标题都默认使用 `chore(release): bump versions`;也可以在 `[release]` 中使用与分支模板相同的严格 `release.*` 上下文定制。 渲染后的发布分支必须与基础分支不同。Semifold 会在应用版本文件或修改 Git ref 前检查这项约束;把基础分支用作发布分支会直接失败,不会被解释为 trunk release 工作流。 因此,贡献者只需要把变更集与代码一起提交并合入。维护者在自动创建的发布拉取请求中审查版本和变更日志。 发布拉取请求正文通常包含各软件包的变更日志。如果正文超过保守的 65,536 UTF-8 字节预算,Semifold 会改用软件包版本摘要,并提示审查者到拉取请求的 **Files changed** 页面查看完整变更日志。摘要按软件包 ID 排序,只保留预算内的完整软件包条目,变更日志文件仍保留完整内容。创建和更新拉取请求均执行此保护,无需额外配置。 ## 不再存在变更集时 [#不再存在变更集时] 发布拉取请求合入后,下一次基础分支运行发现没有待处理变更集,于是调用发布操作,并启用 GitHub Release 处理。软件包会先接受检查,再按依赖关系发布。 ## 环境与输出 [#环境与输出] 命令使用 `GITHUB_ACTIONS`、`GITHUB_REF_NAME`、`GITHUB_REPOSITORY` 和 `GITHUB_TOKEN`。生成的工作流使用稳定 step ID `semifold`;该 step 实际运行的分支会写出 `semifold-version` 或 `semifold-publish` output key。所在 job 再把它们映射成 `version` 和 `publish`: ```yaml jobs: release: outputs: version: ${{ steps.semifold.outputs['semifold-version'] }} publish: ${{ steps.semifold.outputs['semifold-publish'] }} steps: - id: semifold run: smif ci ``` 后续 job 使用 `needs.release.outputs.version` 或 `needs.release.outputs.publish`;本次没有运行的分支对应空字符串。发布部分失败时,Semifold 会先尽力写出包含恢复状态的 publish JSON,再返回非零状态。 `--dry-run` 会准备相应分支行为,但不会推送分支、创建拉取请求或发布。配置权限时应以生成的工作流为受支持基线,不要从一段缺少权限说明的最小示例重新拼装。 ## GitHub 错误诊断 [#github-错误诊断] GitHub 操作失败时会显示失败操作、HTTP 状态、API 消息,以及可用的校验详情和文档链接。客户端错误保留底层错误链。这些信息无需开启 `--debug`,GitHub token 会被遮蔽。403 会按操作提供权限检查提示,但不会将原因一律断定为权限不足。 发布 PR 的查询、创建、更新或分支推送失败会终止命令。`ci` 进入发布流程时,使用与 `publish` 相同的 Release 和附件诊断。 --- # 创建变更集 通过交互创建变更集,或用明确参数表达同一项发布意图。 Source: https://semifold.noctisynth.org/zh/docs/commands/commit/ Language: zh `smif commit` 在真正修改版本之前记录发布意图。可见别名 `smif add` 的行为完全相同。 ```bash smif commit ``` ## 命令会询问什么 [#命令会询问什么] 交互流程会选择一个或多个已配置的软件包 ID,为每个软件包指定 `patch`、`minor` 或 `major`,选择可选的变更日志标签,再输入文件名和面向使用者的摘要。 输入名称 `add-api` 后,Semifold 创建 `.changes/add-api.md`: ```md title=".changes/add-api.md" --- core: "minor:feat" web: "patch:fix" --- 在 Web 绑定中开放新的 API。 ``` 版本提升级别表达版本意图;标签只选择变更日志分组,不会选择发布通道。 ## 自动化调用 [#自动化调用] ```bash smif commit --name add-api \ --package core=minor \ --package web=patch \ --tag feat \ --summary "在 Web 绑定中开放新的 API。" ``` `--package` 和 `--summary` 可以重复传入。没有内联提升级别的软件包使用 `--level`。不能交互的调用者还必须在 `--tag <标签>` 与 `--no-tag` 中明确选择一个,避免 Semifold 猜测缺失答案。 ## 校验与副作用 [#校验与副作用] * 软件包 ID 和标签必须存在于 `.changes/config.toml`; * 名称必须能转换成安全的变更集文件,不能悄悄覆盖不同内容; * 软件包集合和摘要不能为空; * `--dry-run` 只校验并渲染候选内容,不创建文件。 把变更集和代码改动放在同一个提交中。需要检查依赖传播时,在合入前运行 [`smif status`](https://semifold.noctisynth.org/zh/docs/commands/status/)。 --- # 配置文件 同步发现的软件包、迁移旧字段,并在不重写无关策略的情况下维护命名发布通道。 Source: https://semifold.noctisynth.org/zh/docs/commands/config/ Language: zh `smif config` 集中维护 `.changes/config.toml`: ```text smif config sync smif config migrate smif config channel set|clear ``` ## 同步工作区 [#同步工作区] 先执行只读检查: ```bash smif config sync --check ``` `sync` 运行软件包发现,再与 `[packages]` 比较。它报告新增、缺失、重命名、移动和冲突,同时保留注释和有意配置的字段。计划没有歧义时,去掉 `--check` 即可应用。 使用 `--resolver rust --resolver nodejs` 可以限制扫描的软件生态。`--prune` 会删除完整、成功扫描中不存在的已配置软件包;部分扫描或冲突无法证明删除安全时,Semifold 会拒绝清理。 ## 从 v0.2.x 迁移 [#从-v02x-迁移] ```bash smif config migrate --check smif config migrate ``` Semifold 会在严格加载项目之前读取并迁移 v0.2.x 配置。它可以替换旧 `version-mode`、规范已知的 snake\_case 字段,并为旧的版本存在性检查补充必需的 `type`,同时保留无关 TOML 和注释。需要迁移时,`--check` 返回非零状态且不写文件。 其他命令无法按当前契约加载 TOML 配置时,会完整显示底层解析或验证错误,再提示尝试运行 `smif config migrate`。迁移仍然失败表示问题不属于受支持的旧字段转换,应按该错误手工修正;该命令不迁移 JSON 配置,也不能修复文件读取权限。 ## 设置或清除发布通道 [#设置或清除发布通道] ```bash smif config channel set beta --package web --bump minor smif config channel clear --package web ``` `set` 接受命名通道,并要求可重复的 `--package` 或 `--all`。一次性的 `--bump preserve|patch|minor|major` 决定首次进入通道时使用的稳定版本基线;成功的 `version` 会消费它。`clear` 恢复默认稳定通道。两项操作都支持 `--check`。 对于 Node.js 软件包,如果配置的 `npm publish` 缺少匹配的 `--tag`,Semifold 会报告策略不一致,但不会替用户改写命令。 ## `--check` 与 `--dry-run` [#--check-与---dry-run] `--check` 判断持久化配置是否已经处于请求状态,适合持续集成检查配置漂移;`--dry-run` 用来预览操作。两者都不写配置,但成功条件不同。 --- # 命令行概览 按任务选择 Semifold 命令,再查看行为说明或完整参数参考。 Source: https://semifold.noctisynth.org/zh/docs/commands/ Language: zh 安装后的短命令是 `smif`,`semifold` 是完全等价的长名称。这里把命令分别说明,方便查询输入、副作用和失败恢复;工作流文档仍然按用户要完成的任务组织。 ## 选择命令 [#选择命令] | 命令 | 什么时候使用 | 是否修改本地文件 | | --------------------------------------- | ----------------------------------------- | -------- | | [`init`](https://semifold.noctisynth.org/zh/docs/commands/init/) | 在仓库中接入 Semifold,并按需生成 GitHub Actions 工作流。 | 是 | | [`commit`](https://semifold.noctisynth.org/zh/docs/commands/commit/) | 把经过判断的发布意图记录为变更集。 | 是 | | [`config`](https://semifold.noctisynth.org/zh/docs/commands/config/) | 同步、迁移配置或修改发布通道。 | 取决于子命令 | | [`status`](https://semifold.noctisynth.org/zh/docs/commands/status/) | 写文件前检查受影响的软件包和目标版本。 | 否 | | [`version`](https://semifold.noctisynth.org/zh/docs/commands/version/) | 手工应用软件包、依赖、变更日志和变更集修改。 | 是 | | [`publish`](https://semifold.noctisynth.org/zh/docs/commands/publish/) | 按依赖关系发布已经完成版本修改的软件包。 | 会产生外部副作用 | | [`ci`](https://semifold.noctisynth.org/zh/docs/commands/ci/) | 让生成的 GitHub Actions 工作流维护发布拉取请求或执行发布。 | 是 | | [`mcp`](https://semifold.noctisynth.org/zh/docs/commands/mcp/) | 通过标准输入输出向 MCP 客户端开放变更集增删改查。 | 取决于工具 | ## 行为说明与参数参考 [#行为说明与参数参考] 每个命令页面解释正常行为,而不只是罗列参数。已经理解工作流、只想确认选项名称时,使用[完整 CLI 参数参考](https://semifold.noctisynth.org/zh/docs/commands/reference/)。 第一次发布请跟随[入门教程](https://semifold.noctisynth.org/zh/docs/getting-started/first-release/)。如果教程生成了 GitHub Actions,贡献者的日常路径在提交变更集后就结束;`smif ci` 会在正确的基础分支运行中处理版本修改和发布。 --- # 初始化 发现软件包、创建 Semifold 配置,并按需生成受支持的 GitHub Actions 工作流。 Source: https://semifold.noctisynth.org/zh/docs/commands/init/ Language: zh `smif init` 用来让一座仓库开始使用 Semifold。它通常只在仓库根目录执行一次;以后增加、删除或移动软件包时,使用 [`smif config sync`](https://semifold.noctisynth.org/zh/docs/commands/config/)。 ```bash smif init ``` ## 默认交互流程 [#默认交互流程] 在终端中,Semifold 会询问要扫描的内置软件生态、基础与发布分支名称、默认变更日志分类,以及是否生成 GitHub Actions。完成软件包发现后,它会规划并写入: * `.changes/config.toml`:分支、标签、变更日志模板、发现的软件包和解析器默认配置; * `.github/workflows/semifold-ci.yaml`:选择 GitHub Actions 时生成; * `.github/workflows/semifold-status.yaml`:选择 GitHub Actions 时生成,用于拉取请求状态评论。 初始化后应立即检查 `[packages]` 下稳定的软件包 ID(`PackageId`)和路径。 ## 自动化调用 [#自动化调用] 不能交互的调用者必须明确回答每一项选择: ```bash smif init --resolvers rust --resolvers nodejs \ --base-branch main \ --release-branch release \ --default-tags \ --github-actions ``` 需要明确选择空结果时,使用对应的 `--no-resolvers`、`--no-default-tags` 或 `--no-github-actions`。这种形式用于自动化,不是本地使用的前置知识。 ## 重要选项 [#重要选项] | 选项 | 行为 | | -------------------------- | ------------------------------- | | `--target <路径>` | 把变更集目录从默认 `.changes` 改为其他位置。 | | `--resolvers ` | 扫描一种内置软件生态;可以重复指定。 | | `--base-branch <名称>` | 选择接收自动生成的发布 Pull Request 的基础分支。 | | `--release-branch <名称或模板>` | 选择由自动化维护的发布分支;模板渲染后也不能与基础分支相同。 | | `--force` | 明确重新初始化已有配置,不用于日常同步软件包。 | | `--allow-non-root` | 允许从子目录调用,同时使用自动发现的仓库根目录。 | | `--dry-run` | 只规划和报告初始化结果,不写文件。 | 发现失败或必要选择无效时,初始化会在写入前停止。尤其不要把 `--release-branch` 当成当前基础分支的另一个名称:生成的 CI 会强制更新该分支,并维护一条返回 `--base-branch` 的 Pull Request。已有仓库不要把 `--force` 当作维护方式,因为它可能替换有意制定的策略;请先运行 `smif config sync --check`。 --- # MCP 工具 启动通过标准输入输出提供、具有版本修订保护的变更集增删改查 MCP 服务器。 Source: https://semifold.noctisynth.org/zh/docs/commands/mcp/ Language: zh `smif mcp` 在标准输入输出上启动 JSON-RPC MCP 服务器。它用于已经配置的 MCP 客户端,不是交互式终端命令。 ```bash smif mcp ``` 默认情况下,Semifold 从当前目录查找项目。如果 MCP 客户端不是从仓库目录启动服务器,可以使用 `--project-root /path/to/repository` 明确指定仓库根目录。项目采用延迟加载,因此一次无效工具请求不会终止服务器。 ## 已发布工具 [#已发布工具] | 工具 | 输入 | 行为 | | ------------------ | --------------------------------------- | ---------------------------- | | `get_changeset` | 可选 `id` | 列出变更集,或返回带 SHA-256 修订值的一条记录。 | | `create_changeset` | `name`、`packages`、`summary` | 幂等创建请求的变更集。 | | `update_changeset` | `id`、预期 `revision`、`packages`、`summary` | 只有调用者看到的修订仍然匹配时才替换内容。 | | `delete_changeset` | `id`、预期 `revision` | 只删除调用者已经读取的精确修订。 | 每个软件包输入包含 `package`、版本提升级别 `bump`(`patch`、`minor` 或 `major`)和可选 `tag`。响应使用协议结构版本 1,并返回结构化状态或错误。 ## 并发与安全 [#并发与安全] 修改工具串行执行。更新与删除使用乐观 SHA-256 修订值拒绝过期调用者;工具 panic 会被隔离;无效参数返回结构化错误;下一条请求仍能在同一服务器继续执行。 全局 `--dry-run` 会把修改工具转换成经过校验但不写变更集文件的预览。 --- # 发布软件包 从已经完成版本修改的清单和变更日志重新建立发布执行计划,再按依赖关系发布软件包。 Source: https://semifold.noctisynth.org/zh/docs/commands/publish/ Language: zh `smif publish` 面向版本与变更日志修改已经存在的软件包。它不依赖 `version` 已经消费的变更集。 ```bash smif publish --dry-run smif publish ``` 启用生成的 GitHub Actions 后,合入准备好的发布拉取请求会通过 [`smif ci`](https://semifold.noctisynth.org/zh/docs/commands/ci/) 触发推荐的发布路径。同一次发布不要再从本地重复执行。 ## 规划与发布前检查 [#规划与发布前检查] Semifold 读取当前清单与变更日志,通过工作区图排列软件包,展开配置命令,并在启动发布命令前执行所有可用的软件包仓库检查。缺少变更日志、已经存在的版本和软件包策略都会成为明确的跳过原因。 私有软件包只跳过软件包仓库预检和发布命令。有效的私有状态优先采用软件包级 `publish`,缺省时才采用清单或插件发现结果。私有软件包默认不创建 GitHub Release;显式配置 `github-release = true` 并启用 `--github-release` 时,仍会创建 Release 和上传附件。只有没有任何代码托管平台工作需要执行时,最终状态才会显示为私有软件包跳过。 HTTP 检查把 `200` 视为版本存在、`404` 视为不存在,其他状态码会安全失败。命令检查使用配置的 JSON Lines 契约。 ## 执行 [#执行] 对于符合条件的软件包,Semifold 按依赖顺序运行发布前命令和发布命令。最终报告分别列出成功、跳过、失败和尚未开始的软件包,让部分失败可以基于事实重试。 | 选项 | 行为 | | ------------------ | ----------------------------------------- | | `--dry-run` | 运行只读检查,跳过软件包仓库与代码托管平台修改;只有明确允许预演的配置命令会执行。 | | `--allow-dirty` | 接受当前工作区差异作为有意的发布输入。 | | `--github-release` | 创建配置的 GitHub Release 并上传附件;只能在持续集成中使用。 | 如果软件包仓库中已经存在目标版本,对应发布命令会被跳过。启用 GitHub Release 后,Semifold 仍可补建缺失的 Release;已经存在的 Release 不会重新上传缺失附件。 ## GitHub 错误诊断 [#github-错误诊断] GitHub 操作失败时会显示失败操作、HTTP 状态、API 消息,以及可用的校验详情和文档链接。客户端错误保留底层错误链。这些信息无需开启 `--debug`,GitHub token 会被遮蔽。403 会按操作提供权限检查提示,但不会将原因一律断定为权限不足。 创建 Release 或上传附件失败时,保留对应包、失败阶段、部分发布报告和恢复指引。Release 已存在时仍按既有幂等逻辑跳过。 --- # 命令索引与通用选项 快速查找当前 Semifold 命令、对应行为页面和全局执行选项。 Source: https://semifold.noctisynth.org/zh/docs/commands/reference/ Language: zh ```text smif [OPTIONS] ``` `semifold` 是等价的完整命令名。本页是命令索引;逐条行为、默认交互路径和失败语义在对应命令页面中说明。运行 `smif --help` 可以查看当前安装版本的参数类型、默认值、可重复性和冲突关系。 ## 命令 [#命令] | 命令 | 语法 | 详细行为 | | ---------------------- | --------------------------------------------- | ------------------------------------------ | | `init` | `smif init [OPTIONS]` | [初始化](https://semifold.noctisynth.org/zh/docs/commands/init/) | | `commit`(`add`) | `smif commit [OPTIONS]` | [创建变更集](https://semifold.noctisynth.org/zh/docs/commands/commit/) | | `config sync` | `smif config sync [OPTIONS]` | [维护配置](https://semifold.noctisynth.org/zh/docs/commands/config/) | | `config migrate` | `smif config migrate [OPTIONS]` | [维护配置](https://semifold.noctisynth.org/zh/docs/commands/config/) | | `config channel set` | `smif config channel set [OPTIONS] ` | [维护配置](https://semifold.noctisynth.org/zh/docs/commands/config/) | | `config channel clear` | `smif config channel clear [OPTIONS]` | [维护配置](https://semifold.noctisynth.org/zh/docs/commands/config/) | | `status` | `smif status [OPTIONS]` | [检查版本决定](https://semifold.noctisynth.org/zh/docs/commands/status/) | | `version` | `smif version [OPTIONS]` | [应用版本修改](https://semifold.noctisynth.org/zh/docs/commands/version/) | | `publish` | `smif publish [OPTIONS]` | [执行发布](https://semifold.noctisynth.org/zh/docs/commands/publish/) | | `ci` | `smif ci [OPTIONS]` | [GitHub Actions 编排](https://semifold.noctisynth.org/zh/docs/commands/ci/) | | `mcp` | `smif mcp [OPTIONS]` | [MCP 服务器](https://semifold.noctisynth.org/zh/docs/commands/mcp/) | ## 全局选项 [#全局选项] | 选项 | 含义 | | ---------------- | ------------------------------------------------------------------------------------ | | `--dry-run` | 为所有命令启用预演。不会修改 Semifold 管理的文件、registry 或 Forge 资源及 Pull Request 评论;明确允许预演的配置命令仍可能执行。 | | `--debug` | 启用额外诊断,但不会把敏感配置变成公开输出契约。 | | `-h`、`--help` | 输出当前命令层级的帮助。 | | `-V`、`--version` | 输出可执行文件版本。 | ## 命令专用选项速查 [#命令专用选项速查] ### `init` [#init] `--target`、可重复的 `--resolvers`、`--no-resolvers`、`--force`、`--base-branch`、`--release-branch`、`--default-tags`、`--no-default-tags`、`--github-actions`、`--no-github-actions` 和 `--allow-non-root`。 ### `commit` [#commit] `--name`、`--level`、可重复的 `--summary`、可重复的 `--package PACKAGE[=LEVEL]`、`--tag` 和 `--no-tag`。 ### `config` [#config] * `sync`:`--check`、`--prune`、可重复的 `--resolver`; * `migrate`:`--check`; * `channel set`:位置参数 `CHANNEL`、`--bump`、可重复的 `--package` 或 `--all`、`--check`; * `channel clear`:可重复的 `--package` 或 `--all`、`--check`。 ### 发布相关命令 [#发布相关命令] * `status`:`--comment`; * `version`:`--allow-dirty`; * `publish`:`--github-release`、`--allow-dirty`; * `ci`:没有专用选项; * `mcp`:`-C`、`--project-root`(别名 `--cd`)。 --- # 发布状态 在不修改软件包文件、变更日志和变更集的情况下,计算整座仓库的版本决定。 Source: https://semifold.noctisynth.org/zh/docs/commands/status/ Language: zh `smif status` 是变更集与版本修改之间的只读检查点。 ```bash smif status ``` ## 它会计算什么 [#它会计算什么] Semifold 把已配置软件包加载成一张依赖图,合并请求的版本提升级别,应用发布通道与依赖传播规则,并展示: * 每个受影响软件包的当前版本和目标版本; * `.changes/add-api.md` 这样的直接原因; * 因依赖或共享版本传播而加入的软件包; * 警告和一段确定性的 12 位计划指纹。 相同仓库输入下,[`smif version`](https://semifold.noctisynth.org/zh/docs/commands/version/) 会重新计算同一项版本决定。`status` 不持久化计划,也不会修改清单、变更日志、配置或变更集。 ## 拉取请求评论 [#拉取请求评论] ```bash smif status --comment ``` `--comment` 把状态报告写成 GitHub 拉取请求评论,只能在预期的持续集成环境使用。`smif init` 生成的状态工作流会调用这条路径。 启用全局 `--dry-run` 后,Semifold 可以收集并渲染评论预览所需的只读事实,但绝不会创建或更新 Pull Request 评论。 如果 GitHub 拒绝创建或更新评论,发布计划仍然有效,`status` 也会成功完成。警告会展示失败的操作、GitHub API 状态与消息、GitHub 返回的文档链接,并为 `403 Forbidden` 响应提供权限检查提示。 如果软件包、目标版本或原因错误,应修改变更集或依赖配置,不要手工改软件包版本来掩盖问题。 ## GitHub 错误诊断 [#github-错误诊断] GitHub 操作失败时会显示失败操作、HTTP 状态、API 消息,以及可用的校验详情和文档链接。客户端错误保留底层错误链。这些信息无需开启 `--debug`,GitHub token 会被遮蔽。403 会按操作提供权限检查提示,但不会将原因一律断定为权限不足。 评论和变更文件查询(包括分页)失败时会显示详细原因。创建或更新评论失败仍为警告,不影响已经生成的发布计划。 --- # 更新版本 把软件包版本、内部依赖要求、变更日志和已消费变更集作为一项发布操作进行校验和应用。 Source: https://semifold.noctisynth.org/zh/docs/commands/version/ Language: zh `smif version` 执行发布过程中会修改文件的部分。已经启用生成的 GitHub Actions 工作流时,同一次发布应使用 [`smif ci`](https://semifold.noctisynth.org/zh/docs/commands/ci/),不要再在本地运行这条命令。 ```bash smif version --dry-run smif version ``` ## 规划与校验 [#规划与校验] 命令会重新计算 `smif status` 展示的同一项版本决定,再让每个内置适配器或仓库插件生成确定性的清单修改。正常写入开始前,Semifold 会校验目标路径、预期文件哈希、修改冲突、变更日志模板、软件包身份和完整依赖图。 正常执行可能修改: * 软件包版本和符合条件的内部依赖要求; * 工作区共享版本来源; * 软件包变更日志; * 一次性的 `channel-bump` 配置; * 已经消费的变更集文件; * 配置的版本修改后命令(`post-version`)。 ## 工作区保护 [#工作区保护] 命令默认拒绝不干净的 Git 工作区。`--allow-dirty` 表示现有差异有意参与这次操作,不代表无关修改已经变得安全。 ## 预演 [#预演] `--dry-run` 会准备并校验发布,但不执行正常的文件写入。配置明确设置 `dry-run = true` 的版本修改后命令仍然会运行,因此这种命令必须可以安全重复执行。 ## 失败与恢复 [#失败与恢复] 规划或校验失败时,软件包文件和变更集保持不变。如果已经应用文件修改后某个版本修改后命令失败,Semifold 会保留这些修改和变更集,并报告失败命令与恢复事实。请检查 Git diff、修复钩子后重试,不要手工删除变更集。 成功后,在把生成内容提交到配置的发布分支之前,仍应运行测试并审查完整差异。 ## GitHub 错误诊断 [#github-错误诊断] GitHub 操作失败时会显示失败操作、HTTP 状态、API 消息,以及可用的校验详情和文档链接。客户端错误保留底层错误链。这些信息无需开启 `--debug`,GitHub token 会被遮蔽。403 会按操作提供权限检查提示,但不会将原因一律断定为权限不足。 changelog 的 PR 元数据查询失败仍为警告,同时显示受影响的包及 GitHub 诊断。版本准备会继续执行,仅缺少对应远程元数据。 --- # 术语表 解释 Semifold 文档中与仓库、软件包、版本和发布有关的核心术语。 Source: https://semifold.noctisynth.org/zh/docs/concepts/glossary/ Language: zh 遇到陌生术语,或者需要区分两个相近概念时,可以回到本页。 ## 仓库与软件包 [#仓库与软件包] ### 单仓库(monorepo) [#单仓库monorepo] 一座 Git 仓库中包含多个可以独立确定版本的软件包或项目。\*\*跨语言单仓库(polyglot monorepo)\*\*还会同时包含来自多种编程语言和软件生态的软件包。 ### 工作区(workspace) [#工作区workspace] Semifold 作为一张依赖关系图统一管理的软件包集合。这张图来自软件包清单、内置生态适配器、仓库内插件,以及配置中的显式 `depends-on` 关系。 ### 软件包(package) [#软件包package] 工作区中一个可以确定版本的单元。每个软件包都有稳定的软件包 ID、清单名称、版本、路径、所属软件生态,以及是否需要发布等信息。 ### 软件包 ID(PackageId) [#软件包-idpackageid] `.changes/config.toml` 的 `[packages]` 和变更集使用的稳定标识。它不必等于发布到软件包仓库时使用的名称。 ```toml [packages.web-bindings] path = "packages/web" resolver = "nodejs" ``` 这里的 `web-bindings` 是 `PackageId`;`package.json` 中的 `name` 可以不同。 ### 清单文件(manifest) [#清单文件manifest] 某种软件生态用来描述软件包的文件,例如 `Cargo.toml`、`package.json`、`pyproject.toml`、`CMakeLists.txt` 或 `vcpkg.json`。 ## 软件生态与扩展 [#软件生态与扩展] ### 软件生态(ecosystem) [#软件生态ecosystem] 一种软件包格式及其版本、依赖和发布约定,例如 Cargo/crates.io 或 npm。Semifold 使用 `rust`、`nodejs`、`com.example.game` 这样的稳定 ID 区分软件生态。 ### 生态适配器(adapter)与解析器(resolver) [#生态适配器adapter与解析器resolver] **生态适配器**负责发现软件包、读取版本与依赖,并规划清单文件修改。配置文件沿用 `resolver` 这个历史字段名,用它选择软件生态,并配置发布命令和发布前检查。 ### 生态插件(plugin) [#生态插件plugin] 保存在仓库中的单文件 JavaScript 生态适配器。插件可以发现和读取软件包,并规划版本修改;它不能直接写文件、运行命令、读取宿主凭据、发布软件包或创建代码托管平台 Release。 ## 版本管理 [#版本管理] ### 变更集(changeset) [#变更集changeset] `.changes/` 下的一份小型 Markdown 文件。它记录哪些 `PackageId` 发生了变化、期望怎样提升版本、变更日志如何分类,以及给使用者阅读的变更摘要。 ### 版本提升级别(bump level) [#版本提升级别bump-level] 期望的版本增量:`patch`、`minor` 或 `major`。Semifold 读取直接变更集后,还可能根据依赖传播和发布通道规则提高级别,或加入其他受影响的软件包。 ### 变更日志标签(changelog tag) [#变更日志标签changelog-tag] `feat`、`fix` 等用于选择变更日志分组的标签。标签只负责给变更分类,不负责选择发布通道。 ### 发布通道(release channel) [#发布通道release-channel] 软件包当前的发布状态,例如 `stable`、`rc` 或 `beta`。命名通道会影响版本编码;Node.js 软件包还应该在 npm 发布命令中使用对应的 dist-tag。 ### 发布计划(release plan) [#发布计划release-plan] `smif status` 展示的不可变结果:目标版本、提升原因、依赖传播、待修改文件和计划指纹。只要仓库输入没有变化,`smif version` 会计算并应用同一项发布决定。 ## 发布执行 [#发布执行] ### 发布执行计划(publish plan) [#发布执行计划publish-plan] 版本和变更日志写入后,Semifold 为实际发布组织的有序工作。它包括软件包仓库检查、发布前命令、发布命令、软件包顺序和可选的代码托管平台操作。 ### 软件包仓库(registry) [#软件包仓库registry] 保存已发布版本的服务,例如 crates.io、npm 或 PyPI。Semifold 可以在运行发布命令前执行 HTTP 或命令形式的存在性检查。 ### 代码托管平台(Forge) [#代码托管平台forge] 托管源代码和 Release 的服务。Semifold 当前可以独立于软件包仓库发布来创建 GitHub Release 并上传附件。 ### 预演(dry run) [#预演dry-run] 通过 `--dry-run` 选择的预览执行。文件、软件包仓库和代码托管平台修改会被跳过;只读的版本存在性检查仍会运行,配置命令只有明确设置 `dry-run = true` 时才会在预演中执行。 ## 接下来阅读 [#接下来阅读] * [理解 Semifold](https://semifold.noctisynth.org/zh/docs/introduction/):建立完整的产品心智模型。 * [配置概览](https://semifold.noctisynth.org/zh/docs/configuration/overview/):查看这些概念怎样出现在 `.changes/config.toml` 中。 * [插件系统](https://semifold.noctisynth.org/zh/docs/plugins/overview/):了解如何接入自定义软件生态。 --- # 配置概览 理解 .changes/config.toml 各部分的职责,以及日常维护配置的正确流程。 Source: https://semifold.noctisynth.org/zh/docs/configuration/overview/ Language: zh Semifold 把整座仓库的版本与发布规则保存在 `.changes/config.toml`。`smif init` 会创建这个文件;之后,人工维护有意制定的策略,再使用 `smif config sync` 对齐自动发现的软件包。 ## 从一份小配置开始 [#从一份小配置开始] ```toml title=".changes/config.toml" [branches] base = "main" release = "release" [tags] feat = "新增功能" fix = "问题修复" [packages.core] path = "crates/core" resolver = "rust" [packages.web] path = "packages/web" resolver = "nodejs" [resolver.rust.pre-check] type = "http" url = "https://crates.io/api/v1/crates/{{ package.name }}/{{ package.version }}" [[resolver.rust.publish]] command = "cargo" args = ["publish"] [resolver.nodejs.pre-check] type = "http" url = "https://registry.npmjs.org/{{ package.name }}/{{ package.version }}" [[resolver.nodejs.publish]] command = "npm" args = ["publish", "--provenance", "--access", "public"] ``` 这份配置表达了四件事: 1. 发布以 `main` 为基础,并在 `release` 分支准备版本变更; 2. `core` 和 `web` 是变更集使用的稳定软件包 ID(`PackageId`); 3. 每个软件包选择能够理解自己清单文件的生态适配器; 4. 每种软件生态分别定义怎样检查已有版本、怎样执行发布。 `base` 与 `release` 必须使用不同分支。生成的自动化会在维护指向基础分支的 Pull Request 时强制更新发布分支,因此 Semifold 会拒绝与 `base` 相同的固定发布分支或模板渲染结果。 ## 配置分区 [#配置分区] | 分区 | 负责什么 | | ------------- | --------------------------------------- | | `[branches]` | 发布工作流使用的基础分支与发布分支名称。 | | `[release]` | 可选的发布提交消息与 Pull Request 标题模板。 | | `[tags]` | 变更集允许使用的分类,以及对应的变更日志标题。 | | `[changelog]` | 可选的 MiniJinja 发布块和变更条目模板。 | | `[packages]` | 稳定软件包 ID、路径、软件生态、发布通道、显式关系和 Release 附件。 | | `[plugins]` | 仓库内 JavaScript 生态插件及其得到授权的能力。 | | `[resolver]` | 各软件生态的版本存在性检查、发布钩子和版本修改后命令。 | ## 自动发现与发布策略由不同部分负责 [#自动发现与发布策略由不同部分负责] 生态适配器或插件可以发现软件包路径、清单名称、版本和清单中的依赖关系。它不能替你决定发布分支、变更日志分类、命名发布通道、显式 `depends-on` 关系或 GitHub Release 策略。 因此,同步配置时应先检查差异: ```bash smif config sync --check ``` 这条命令只报告配置漂移,不写文件。确认结果后去掉 `--check`,即可加入或更新发现的软件包。只有在审查过待删除软件包后,才应加入 `--prune`。 如需只同步部分软件生态,可以重复传入 `--resolver`: ```bash smif config sync --resolver rust --resolver nodejs ``` ## 检查旧配置 [#检查旧配置] 升级 Semifold 后,先检查配置是否需要迁移: ```bash smif config migrate --check ``` 如果存在迁移,审查后执行: ```bash smif config migrate ``` 普通命令无法按当前契约读取 TOML 配置时,会先保留具体的解析或验证错误,包括可用的行列和源码片段,再建议尝试这条迁移命令。它是针对旧配置的恢复提示,并不表示任意 TOML 语法错误都能自动修复;如果迁移命令仍然报告解析错误,需要根据具体原因手工修正。JSON 配置和文件读取错误不会得到这条建议,因为迁移命令不处理它们。 例如,旧的 `version-mode` 软件包字段只为迁移兼容而保留;新配置应使用 `channel`,进入命名通道时还可以使用一次性的 `channel-bump`。 ## 逐步加入策略 [#逐步加入策略] 第一次发布之前不需要配完所有功能。更容易理解的顺序是: 1. 确认自动发现的软件包 ID 与路径; 2. 检查清单依赖,只为清单无法表达的真实关系添加 `depends-on`; 3. 先完成一次稳定通道发布; 4. 按需加入命名通道、自定义变更日志模板、软件包仓库检查、附件和持续集成策略; 5. 只有内置适配器无法理解某种软件生态时,才注册生态插件。 接下来可以查看包含全部字段的[配置参考](https://semifold.noctisynth.org/zh/docs/configuration/reference/),或者阅读[插件系统概览](https://semifold.noctisynth.org/zh/docs/plugins/overview/)来接入自定义软件生态。 --- # 配置参考 逐项说明当前 .changes/config.toml 配置结构中的字段、默认值和职责。 Source: https://semifold.noctisynth.org/zh/docs/configuration/reference/ Language: zh Semifold 读取 `.changes/config.toml` 中的 TOML 配置。字段名使用 kebab-case。JSON 配置和未知字段不属于受支持的配置契约。 ## `[branches]` [#branches] | 字段 | 类型 | 必需 | 含义 | | --------- | ------------------ | -- | ------------------------------------- | | `base` | 字符串 | 是 | 日常开发与发布准备开始的基础分支。 | | `release` | 严格 MiniJinja 模板字符串 | 是 | 由自动化维护版本修改和发布拉取请求的分支;最终结果不能等于 `base`。 | 发布分支由发布自动化接管:`smif ci` 会强制更新该分支,并从它创建或刷新指向 `base` 的 Pull Request。Semifold 不会把 `release = "main"` 解释为 trunk release 策略。固定发布分支必须与 `base` 不同,模板渲染结果也必须落在其他分支。相同的固定分支名会在读取配置时被拒绝;相同的模板结果会在修改版本文件、分支或远端 ref 之前被拒绝。 不含模板语法的值会保持为固定分支名。需要按当前版本决定生成分支时,可以使用 `release.plan.fingerprint`、`release.plan.common_version`,或通过稳定 `PackageId` 读取 `release.plan.packages[""].next_version`: ```toml [branches] base = "main" release = 'release/v{{ release.plan.packages["semifold"].next_version }}' ``` 模板使用严格变量检查。引用的软件包不在本次发布集合,或计划中的软件包没有共同版本时,渲染会失败而不是生成含糊的分支名。 ## `[release]` [#release] 可选的严格 MiniJinja 模板可以定制发布分支上的提交消息及其 Pull Request 标题: ```toml [release] commit-message = "chore(release): {{ release.plan.fingerprint }}" pull-request-title = 'chore(release): {{ release.plan.packages["semifold"].next_version }}' ``` | 字段 | 类型 | 默认值 | 含义 | | -------------------- | ------------------ | ------------------------------- | ------------------------------------- | | `commit-message` | 严格 MiniJinja 模板字符串 | `chore(release): bump versions` | 完整的 Git 提交消息;允许渲染为多行。 | | `pull-request-title` | 严格 MiniJinja 模板字符串 | `chore(release): bump versions` | GitHub Pull Request 标题;渲染结果必须是非空单行文本。 | 两个模板只暴露与 `branches.release` 相同的 `release.*` 上下文。省略字段或整个分区即可保留默认行为;`smif init` 和 `smif config sync` 不会自动写入这个可选分区。无效模板会在修改版本文件或消费变更集之前失败。 ## `[tags]` [#tags] 变更集标签到变更日志标题的映射: ```toml [tags] feat = "新增功能" fix = "问题修复" ``` 变更集使用的每个标签都必须在这里存在。标签不会选择发布通道。 ## `[changelog]` [#changelog] | 字段 | 类型 | 默认值 | 含义 | | -------------------- | --- | ---- | ------------------------ | | `template` | 字符串 | 内置模板 | 渲染完整版本发布块的 MiniJinja 模板。 | | `changeset-template` | 字符串 | 内置模板 | 渲染一条变更集内容的 MiniJinja 模板。 | `smif init` 会写入内置模板,方便直接发现和修改。替换完整发布模板时,必须保留 Semifold 识别版本所需的稳定标记。 ## `[packages.]` [#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" }, ] ``` | 字段 | 类型 | 默认值 | 含义 | | ---------------- | ------------------------------------ | ---------------------------- | ------------------------------------------------------- | | `path` | 路径 | 必需 | 相对于仓库根目录的软件包路径。 | | `resolver` | 软件生态 ID | 必需 | 该软件包使用的内置适配器或已注册插件。 | | `publish` | 布尔值 | 清单或插件发现结果 | 是否让 Semifold 对该软件包执行 registry pre-check 与发布命令。缺省时不写入配置。 | | `channel` | 字符串 | `stable` | 稳定通道,或 `rc`、`beta` 等命名发布通道。 | | `channel-bump` | `preserve`、`patch`、`minor` 或 `major` | 无 | 下一次进入命名通道时使用的一次性稳定基线提升方式。 | | `depends-on` | `PackageId` 数组 | `[]` | 清单文件无法表达的补充内部依赖关系。 | | `github-release` | 布尔值 | 公开软件包为 `true`;私有软件包为 `false` | 覆盖 GitHub Release 创建策略。 | | `assets` | 字符串或 `{ path, name }` 数组 | `[]` | 上传到该软件包 GitHub Release 的文件或 glob。 | 旧的 `version-mode` 字段仍可读取,以便完成配置迁移。新配置应使用 `channel`。 `publish` 是生态无关的显式覆盖。`publish = false` 可以阻止 Python、C++ 或内部工具进入软件包仓库流程;`publish = true` 可以覆盖 Rust `publish = false`、Node.js `private: true` 或插件返回的私有标识。覆盖只改变 Semifold 的有效发布资格,不会改写原生清单,也不会绕过 `cargo publish`、`npm publish` 等工具自身的限制。 缺省的 `github-release` 策略基于覆盖后的有效发布资格:可发布软件包默认创建,私有软件包默认不创建。显式 `github-release = true` 或 `false` 仍可以单独覆盖代码托管平台行为。 ## `[plugins.]` [#pluginsecosystem-id] ```toml [plugins."com.example.game"] path = "plugins/game.js" sha256 = "64-lowercase-hex-characters" allowed-origins = ["https://api.example.com"] ``` | 字段 | 类型 | 默认值 | 含义 | | ----------------- | ---------- | ---- | ------------------------------------------------- | | `path` | 路径 | 必需 | 相对于仓库根目录的一份已打包 ESM 文件。 | | `sha256` | 字符串 | 无 | 可选的小写 SHA-256 内容锁。 | | `allowed-origins` | HTTPS 来源数组 | `[]` | 插件可以通过 `fetch` 访问的精确来源。路径、通配符、凭据和非 HTTPS 来源都会被拒绝。 | 插件导出的元数据还会单独声明 `read-patterns`。只有元数据声明和宿主校验同时允许时,文件读取才会成功。 ## `[resolver.]` [#resolverecosystem-id] 即使软件包发现和版本修改来自插件,解析器配置仍然负责实际发布行为。 | 字段 | 类型 | 默认值 | 含义 | | -------------- | ---------- | ---- | ---------------- | | `pre-check` | HTTP 或命令对象 | 无 | 检查目标软件包版本是否已经存在。 | | `prepublish` | 命令数组 | `[]` | 在该软件生态的发布命令之前执行。 | | `publish` | 命令数组 | `[]` | 把软件包发布到软件包仓库。 | | `post-version` | 命令数组 | `[]` | 软件包与变更日志修改写入后执行。 | ### HTTP 版本检查 [#http-版本检查] ```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 状态 `200` 表示版本存在,`404` 表示不存在,其他状态都会让检查失败。`retry` 是可重试失败的等待秒数,也可以与有上限的 `Retry-After` 响应配合。 ### 命令形式的版本检查 [#命令形式的版本检查] ```toml [resolver.internal.pre-check] type = "command" command = "./scripts/version-exists" args = ["--json-lines"] extra-env = { REGISTRY = "internal" } ``` 命令在被检查的软件包目录中运行。stdin 接收单行 `PublishPackageContext` JSON,并以换行结束;stdout 必须严格只包含一行 `{"exists": true}` 或 `{"exists": false}`。 命令无法启动、非零退出、无效 JSON 或额外的非空 stdout 内容都会让预检失败。stderr 继承用于诊断。命令 pre-check 在普通发布和全局 `--dry-run` 中都会执行,因此脚本必须只读且可以安全重复运行。 ### 命令条目 [#命令条目] ```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 ``` | 字段 | 类型 | 默认值 | 含义 | | ------------------- | ------------------------- | --------- | ----------------------------- | | `command` | 字符串 | 必需 | 不经过 shell 展开的可执行文件。 | | `args` | 字符串数组 | `[]` | 直接传给可执行文件的参数。 | | `extra-env` | 字符串映射 | `{}` | 额外环境变量。不要把凭据写进提交到仓库的配置。 | | `stdout` / `stderr` | `inherit`、`pipe` 或 `null` | `inherit` | 子进程输出策略。 | | `dry-run` | 布尔值 | `false` | 是否明确允许该命令在 `--dry-run` 预演中运行。 | Semifold 不会解释 `args` 中的 shell 操作符;需要管道或复合 shell 行为时,请把逻辑放进单独的脚本。 ## 模板变量 [#模板变量] 软件包仓库 URL 和配置命令可以使用软件包作用域的模板上下文。常见值包括: | 变量 | 含义 | | ----------------------- | --------------- | | `{{ package.name }}` | 清单名称或软件包仓库名称。 | | `{{ package.version }}` | 正在检查或发布的版本。 | | `{{ package.path }}` | 相对于仓库根目录的软件包路径。 | 变更日志模板会收到更丰富的结构化上下文。请把变更日志定制与软件包仓库命令配置分开,并使用 `smif status` 和 `smif version --dry-run` 验证修改。 --- # 能力与安全边界 理解插件运行时默认拒绝的文件、网络、命令执行和文件修改边界。 Source: https://semifold.noctisynth.org/zh/docs/plugins/capabilities/ Language: zh Semifold 把生态插件视为“仓库中负责解析不可信项目数据的代码”。因此,运行时使用明确且限定在单次操作内的能力,不允许插件继承整台机器的环境权限。 ## 边界速览 [#边界速览] | 能力 | 默认状态 | 怎样授权 | 重要限制 | | -------- | ------ | ------------------------------------------- | ------------------------------- | | 列出文件 | 拒绝 | `metadata.readPatterns` | 请求和返回路径都必须匹配已授权的仓库相对 glob。 | | 读取文本 | 拒绝 | 同一项读取模式 | 读取不能离开项目根目录,并受单文件和单次操作预算限制。 | | HTTPS 请求 | 拒绝 | `.changes/config.toml` 中的 `allowed-origins` | 只接受精确 HTTPS 来源,不接受通配符、凭据或按路径授权。 | | `URL` | 提供受限子集 | Boa 宿主内置 | 只支持 SDK 已声明的接口。 | | 写文件 | 不提供 | 无法授权 | 插件返回候选修改,由宿主验证并应用。 | | 启动命令 | 不提供 | 无法授权 | 发布与钩子命令保留在解析器配置中。 | | 宿主凭据 | 不提供 | 无法授权 | 软件包仓库与代码托管平台凭据不会进入插件输入。 | ## 文件访问 [#文件访问] 插件在元数据中声明尽可能小的 glob 集合: ```ts export const metadata = definePluginMetadata({ ecosystem: 'com.example.game', pluginVersion: '1.0.0', readPatterns: [ 'packages/*/manifest.json', 'workspace.lock', ], }); ``` `host.listFiles(pattern)` 不能扩大权限:请求的 glob 必须与 `readPatterns` 中的某一项完全相同,每条返回路径也会再次检查。不能用一个看似更窄的不同 glob 代替已声明值。`host.readText(path)` 会执行相同的项目根目录、glob、路径编码、文件大小和单次操作累计预算检查。 协议数据应统一使用正斜杠和仓库相对路径,不要依赖调用者的当前工作目录。 ## 网络访问 [#网络访问] 在配置中授权精确来源: ```toml [plugins."com.example.game"] path = "plugins/game.js" allowed-origins = [ "https://api.example.com", "https://metadata.example.net:8443", ] ``` 运行时会拒绝非 HTTPS URL、内嵌凭据、未授权端口和只是看起来相似的来源。请求与响应正文、请求次数、重定向、并发和累计耗时都有限制。网络传输不会继承系统代理。 `fetch` 和 `URL` 有意小于浏览器 API。请把 `@semifold/plugin-sdk` 类型当作受支持的契约;熟悉的 Web API 名称并不意味着存在 DOM、Cookie、浏览器缓存或 Node.js 行为。 ## 返回候选修改,而不是直接写文件 [#返回候选修改而不是直接写文件] `plan-edits` 返回声明式文件修改。修改已有文件时,必须携带插件读取到的原始字节对应的 SHA-256。宿主随后会验证: * 目标路径合法且没有离开仓库; * 修改提到的软件包和依赖确实存在; * 修改来源与发布计划一致; * 当前文件内容仍与预期哈希匹配; * 不存在重复目标或跨适配器冲突; * 候选修改能被普通文件修改执行器接受。 插件永远不会得到可写文件句柄。 ## 运行时与协议失败 [#运行时与协议失败] 可以预期的领域失败应由插件返回结构化诊断。Semifold 还会把运行时异常、错误协议版本、缺少操作、响应操作不匹配、非法路径、资源超限和无效修改转换成带插件范围的错误。 某个插件失败会停止需要它的当前操作;Semifold 不会因此开放备用权限,也不会悄悄接受一张不完整的工作区图。 ## 仍由宿主负责的工作 [#仍由宿主负责的工作] 以下能力不会进入插件: * 解析变更集并合并版本提升级别; * 验证跨生态依赖图; * 计算发布通道版本; * 渲染变更日志; * 执行软件包仓库检查与发布命令; * 创建 GitHub Release 并上传附件; * 最终应用文件修改并报告恢复状态。 这条边界让自定义软件包格式可以扩展,又不会把插件变成一段拥有整台机器权限的发布脚本。 --- # 插件系统 通过受限权限的仓库内插件,让 Semifold 支持四种内置生态之外的软件包格式。 Source: https://semifold.noctisynth.org/zh/docs/plugins/overview/ Language: zh Semifold 的插件系统允许一座仓库补充自己的生态适配器:插件告诉 Semifold 怎样发现软件包、读取软件包信息和修改版本。插件软件包会像内置软件包一样进入工作区依赖图、变更集、版本传播、配置同步和发布流程。 生态插件系统从 Semifold v0.3.0 开始提供。 ## 什么时候应该编写插件 [#什么时候应该编写插件] 当仓库中存在 Rust、Node.js、Python、C++ 内置适配器无法理解的真实软件包格式时,才需要插件。典型任务包括: * 找到软件包清单与稳定的软件包 ID; * 读取当前版本和软件包之间的依赖; * 区分各软件包独立版本与共享版本来源; * 在保留清单结构的前提下更新版本和内部依赖要求。 如果只是想定制 `cargo publish`、`npm publish` 或其他发布命令,不需要编写插件。发布命令、软件包仓库检查、GitHub Release 和附件仍由普通的 `[resolver.]` 与软件包配置负责。 ## 一套生态适配契约 [#一套生态适配契约] 插件导出版本为 1 的协议元数据,以及一个默认异步入口。Semifold 会调用三种操作: | 操作 | 输入 | 必须返回什么 | | ------------ | --------------------- | -------------------- | | `discover` | 项目根目录 | 插件发现的所有软件包及其完整信息。 | | `inspect` | 一个已配置的软件包位置 | 该软件包当前的完整信息。 | | `plan-edits` | 工作区快照、待发布软件包 ID 与目标版本 | 带预期文件状态和修改来源的候选文件修改。 | 每份软件包信息包含 `PackageId`、清单名称、语义化版本、版本来源、软件生态 ID、路径、是否发布,以及清单依赖。宿主会把清单名称解析成稳定的软件包 ID,并建立跨生态依赖图。 ## 保存在仓库中,并校验身份 [#保存在仓库中并校验身份] 插件通过稳定的软件生态 ID 注册: ```toml title=".changes/config.toml" [plugins."com.example.game"] path = "plugins/game.js" sha256 = "64-lowercase-hex-characters" [packages.engine] path = "engine" resolver = "com.example.game" [resolver."com.example.game"] ``` 插件路径必须留在仓库内部。可选的 SHA-256 内容锁会在加载前验证插件文件。注册表按照软件生态 ID 排序,因此行为不会依赖发现顺序。 ## 权限受限的运行时 [#权限受限的运行时] 内嵌的 Boa 运行时默认不能读取项目文件,也不能访问网络。插件元数据显式声明允许读取的 glob;配置则显式声明允许访问的精确 HTTPS 来源。运行时只提供受支持的 `host.listFiles`、`host.readText`、`fetch` 和 `URL` 子集。 插件不能: * 直接写文件; * 启动子进程或运行发布命令; * 从宿主读取软件包仓库或代码托管平台凭据; * 创建 GitHub Release 或上传附件; * 使用 Node.js 内置模块、DOM 或运行时模块加载。 Semifold 会先验证插件元数据、响应、诊断、路径、依赖、预期哈希、修改冲突和资源预算,再应用任何候选修改。 ## SDK 与打包状态 [#sdk-与打包状态] `@semifold/plugin-sdk` 提供从 Rust 协议生成的 TypeScript 传输类型与响应构造工具。它的运行时声明只描述 Boa 真正支持的 `fetch`、`URL` 和文件宿主接口,不会假装完整浏览器或 Node.js API 存在。 用来生成单文件 ESM 并拒绝不支持 import 或全局 API 的配套 Vite 插件尚未实现。首个版本需要使用仓库已有的打包器,并确认输出是一份没有运行时 import 的 ESM 文件。 接下来可以[编写一个插件](https://semifold.noctisynth.org/zh/docs/plugins/quick-start/),或者先了解[能力与安全边界](https://semifold.noctisynth.org/zh/docs/plugins/capabilities/)。 --- # 编写生态插件 定义版本为 1 的 JavaScript 生态适配器,完成打包、权限注册,并验证软件包发现与版本规划。 Source: https://semifold.noctisynth.org/zh/docs/plugins/quick-start/ Language: zh 本指南展示一条完整的接入路径。示例软件包格式在每个软件包目录中保存 `manifest.json`,其中包含 `name`、`version` 和 `dependencies` 字段。 ## 1. 选择稳定的软件生态 ID [#1-选择稳定的软件生态-id] 使用自己控制的反向域名 ID,例如 `com.example.game`。`rust`、`nodejs` 等内置 ID 已被保留。 创建源码目录,并安装带类型的 SDK: ```bash bun add --dev @semifold/plugin-sdk ``` ## 2. 导出元数据与入口函数 [#2-导出元数据与入口函数] ```ts title="plugins/game.ts" import { createPluginFailure, createPluginSuccess, definePlugin, definePluginMetadata, type PluginHostV1, type PluginPackageInspectionV1, } from '@semifold/plugin-sdk'; const ecosystem = 'com.example.game'; export const metadata = definePluginMetadata({ ecosystem, pluginVersion: '1.0.0', readPatterns: ['packages/*/manifest.json'], }); async function inspectPackage( id: string, path: string, host: PluginHostV1, ): Promise { const manifest = JSON.parse( await host.readText(`${path}/manifest.json`), ) as { name: string; version: string; dependencies?: Record; }; return { id, 'manifest-name': manifest.name, version: manifest.version, 'version-source': { kind: 'package-manifest' }, ecosystem, path, publishable: true, dependencies: Object.entries(manifest.dependencies ?? {}).map( ([name, requirement]) => ({ 'manifest-name': name, kind: 'runtime', requirement, }), ), }; } export default definePlugin(async (request, host) => { switch (request.operation) { case 'discover': { const manifests = await host.listFiles('packages/*/manifest.json'); const packages = await Promise.all( manifests.map((manifest) => { const path = manifest.slice(0, -'/manifest.json'.length); return inspectPackage(path, path, host); }), ); return createPluginSuccess(request, { packages }); } case 'inspect': { const { id, path } = request.input.package; return createPluginSuccess(request, { package: await inspectPackage(id, path, host), }); } case 'plan-edits': return createPluginFailure(request, ecosystem, { code: 'plan-edits-not-implemented', message: '启用发布前,请先实现确定性的清单文件修改。', }); } }); ``` 这个初始版本可以安全测试发现和读取,但会拒绝写入版本。尚未准备好修改时返回失败,比成功返回不完整修改更安全。 ## 3. 实现版本修改 [#3-实现版本修改] 遍历 `request.input['released-packages']` 中待发布的软件包,从 `request.input['workspace-packages']` 找到快照,再从 `request.input.versions` 取得目标版本。为每个目标返回一项或多项修改: ```ts { path: 'packages/engine/manifest.json', expected: { kind: 'existing', sha256: '<插件读取到的原始字节所对应的 sha256>', }, 'new-content': '{\n "name": "engine",\n "version": "1.1.0"\n}\n', source: { kind: 'package-version', package: 'packages/engine', }, } ``` 修改内部依赖要求时,来源使用 `dependency-version`,并同时写出所属软件包与被依赖软件包。修改共享工作区清单时,使用 `workspace-manifest`,列出共享版本修改和依赖。 协议要求每个已有目标都携带 SHA-256。请把确定性的 SHA-256 实现一起打包进插件;Boa 宿主不会暴露 Node.js `crypto`。Semifold 会在应用修改前再次计算文件哈希,拒绝已经变化的内容,并继续执行路径与冲突检查。 ## 4. 生成一份 ESM 文件 [#4-生成一份-esm-文件] 把 SDK 工具和其他源码全部打包进一份 ESM 文件,例如 `plugins/game.js`。配置中的最终文件必须满足: * 导出名为 `metadata` 的元数据; * 默认导出异步插件入口; * 不含运行时 `import`; * 不使用 Node.js 内置模块、动态模块加载、DOM 或不受支持的 Web API。 Semifold 尚未提供计划中的 Vite 集成来自动完成打包检查。请让仓库现有的打包器内联依赖,并在注册前检查最终文件。 ## 5. 注册插件 [#5-注册插件] ```toml title=".changes/config.toml" [plugins."com.example.game"] path = "plugins/game.js" [resolver."com.example.game"] pre-check = { type = "command", command = "./scripts/game-version-exists" } publish = [{ command = "./scripts/publish-game-package" }] ``` 配置表中的软件生态 ID 必须与 `metadata.ecosystem` 完全一致。打包结果稳定后,可以加入 `sha256`。只有插件确实需要访问某个 HTTPS 服务时,才加入对应的 `allowed-origins`。 ## 6. 发现并验证软件包 [#6-发现并验证软件包] ```bash smif config sync --resolver com.example.game --check ``` 审查建议的软件包 ID 和路径。去掉 `--check` 应用同步,然后在创建变更集之前实现并测试 `plan-edits`: ```bash smif config sync --resolver com.example.game smif status smif version --dry-run ``` 插件只负责提出候选修改。Semifold 仍然会验证完整的跨生态依赖图,并通过宿主控制的同一套文件执行器应用所有已接受的修改。 授予文件或网络权限前,请阅读[能力与安全边界](https://semifold.noctisynth.org/zh/docs/plugins/capabilities/)。 --- # 接入已有单仓库 在不改变现有构建方式的前提下,让 Semifold 发现软件包、固定身份并接管版本与发布流程。 Source: https://semifold.noctisynth.org/zh/docs/getting-started/adopt-existing-monorepo/ Language: zh Semifold 不要求重新组织仓库,也不要求统一各种语言的构建工具。接入的关键是先让 Semifold 正确理解现有软件包及其关系,再逐步启用变更集、版本修改和自动发布。 ## 开始之前 [#开始之前] 先安装 `latest` 版本,并在仓库根目录确认: * 各软件包的清单文件可以被原有工具正常解析; * Git 工作区没有无关的未提交修改; * 你知道日常开发使用的基础分支和希望用于发布拉取请求的分支名称。 如果仓库包含特殊清单格式,不必先把它改造成四种内置生态之一。可以先接入内置软件包,再通过[插件系统](https://semifold.noctisynth.org/zh/docs/plugins/overview/)补充自定义生态。 ## 1. 初始化并检查发现结果 [#1-初始化并检查发现结果] 从仓库根目录运行: ```bash smif init ``` 选择仓库实际使用的软件生态,并按需要生成 GitHub Actions 工作流。初始化只创建 Semifold 配置、变更集目录和可选工作流,不会移动软件包或改写业务源码。 打开 `.changes/config.toml`,重点检查 `[packages]`: ```toml [packages.rust-core] path = "crates/core" resolver = "rust" [packages.web-client] path = "packages/web" resolver = "nodejs" depends-on = ["rust-core"] ``` 表名中的 `rust-core` 和 `web-client` 是稳定的软件包 ID(`PackageId`)。它们用于变更集、依赖图和配置引用,可以与清单中的发布名称不同。确定 ID 后应尽量保持稳定;移动目录时通常只需更新 `path`。 阅读[软件包发现](https://semifold.noctisynth.org/zh/docs/workspace/package-discovery/)并逐个核对发现范围。不要因为某个目录“看起来像软件包”就假设它一定会被识别。 ## 2. 补充无法从清单推断的关系 [#2-补充无法从清单推断的关系] 同一生态的清单依赖会进入统一工作区图。跨生态关系不会根据同名软件包猜测,需要在依赖方上显式配置 `depends-on`: ```toml [packages.python-binding] path = "bindings/python" resolver = "python" depends-on = ["rust-core"] ``` 这表示 `python-binding` 依赖 `rust-core`:发布时先处理 Rust 软件包;当 `rust-core` 发布新版本时,绑定软件包会获得一次 `patch` 发布。详细规则见[依赖与版本传播](https://semifold.noctisynth.org/zh/docs/workspace/dependencies/)。 ## 3. 先预览,不修改文件 [#3-先预览不修改文件] 创建第一份变更集前,先检查当前配置能否形成工作区: ```bash smif config sync --check smif status ``` `config sync --check` 在发现结果与配置不一致时返回非零状态,但不会写文件。`status` 只计算版本决定;没有变更集时不会为了“测试发布”凭空创建版本。 如果发现重复的同生态清单名称、未知 `PackageId` 或依赖环,先修正这些结构问题。Semifold 会拒绝用含糊关系继续规划。 ## 4. 用一次真实改动验证流程 [#4-用一次真实改动验证流程] 选择一个影响明确的小改动,运行交互式命令: ```bash smif commit smif status smif version --dry-run ``` 确认直接变化的软件包、依赖传播原因、目标版本和待修改文件都符合预期,再把代码与 `.changes/*.md` 一起提交。 启用 GitHub Actions 时,推荐让自动化维护发布拉取请求并在合入后发布;本地不需要重复执行 `version` 和 `publish`。完整过程见[完成第一次发布](https://semifold.noctisynth.org/zh/docs/getting-started/first-release/)。 ## 仓库继续变化时 [#仓库继续变化时] 增加、移动或移除软件包后运行: ```bash smif config sync ``` 默认同步安全的新增和路径变化,并报告已经无法发现的软件包;只有明确使用 `--prune` 才删除缺失配置。不要通过重复运行 `smif init --force` 来维护已有配置,否则很难区分发现结果与手工发布策略。 --- # 完成第一次发布 初始化一座小仓库、记录变更、检查受影响的软件包、修改版本并执行发布。 Source: https://semifold.noctisynth.org/zh/docs/getting-started/first-release/ Language: zh 本教程使用正常的交互式命令行。你会创建 Semifold 配置、记录一份变更集、检查受影响的软件包、修改文件,并准备一次软件包仓库发布。 ## 开始之前 [#开始之前] 准备一座默认分支为 `main`、工作区没有未提交修改的 Git 仓库。示例假设仓库中至少有一个包含有效 `Cargo.toml` 的 Rust 软件包;其他内置软件生态使用相同的生命周期。 如果 `smif --version` 还不能运行,请先按照[安装指南](https://semifold.noctisynth.org/zh/docs/getting-started/installation/)安装 Semifold。 ## 1. 初始化仓库 [#1-初始化仓库] 在仓库根目录运行: ```bash smif init ``` 交互提示会依次询问: 1. 要发现哪些软件包生态; 2. 基础分支和发布分支名称; 3. 是否使用默认的变更日志分类; 4. 是否生成 GitHub Actions 工作流。 本教程选择 Rust,分支保留 `main` 和 `release`,接受默认分类,并生成工作流。 打开 `.changes/config.toml`。在 `[packages]` 下确认每个软件包的路径和解析器符合预期。配置表的 key 是稳定的软件包 ID(`PackageId`),它可能不同于清单文件中用于发布的名称。 如果仓库已经初始化,只是后来增加或删除了软件包,请使用 `smif config sync`,不要再次运行 `init --force`。 ### 配置 GitHub Actions 权限 [#配置-github-actions-权限] 运行生成的工作流前,请打开仓库的 **Settings → Actions → General → Workflow permissions**,然后: 1. 选择 **Read and write permissions**,让发布工作流能够推送准备好的版本提交。 2. 勾选 **Allow GitHub Actions to create and approve pull requests**,让工作流能够创建或更新发布 Pull Request。 保存设置后再继续。如果组织或企业策略禁止启用这些仓库级选项,请联系管理员在上级策略中开放相应能力。参见 GitHub 的[仓库 Actions 设置文档](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#setting-the-permissions-of-the-github_token-for-your-repository)。 ## 2. 完成一项用户可见的改动 [#2-完成一项用户可见的改动] 修改一个公开 API,或者完成其他使用者能感知的行为变化。第一次练习尽量保持改动小而明确,便于判断版本影响。 现在记录发布意图: ```bash smif commit ``` 按照提示选择发生变化的软件包,选择 `patch`、`minor` 或 `major`,选择变更日志分类,输入 `add-api` 这样的简短变更集名称,再写一段给使用者阅读的摘要。 Semifold 会创建类似下面的文件: ```md title=".changes/add-api.md" --- my-package: "minor:feat" --- 开放新的公共 API。 ``` `minor` 表示期望的版本提升;`feat` 选择变更日志分组,不会选择发布通道。 把变更集与代码改动放在同一个提交或拉取请求中,让审查者一起判断实现和版本影响。 ## 3. 检查受影响的软件包 [#3-检查受影响的软件包] ```bash smif status ``` * 发生变化的软件包具有正确的当前版本和目标版本; * 直接原因指向文件名为 `.changes/add-api.md` 的变更集; * 额外出现的软件包都有可以理解的依赖传播原因; * 最后的提示表示发布计划已经就绪,而不是文件已经被修改。 如果软件包或版本不正确,请在这里停止。修改变更集、软件包 ID 或依赖配置,然后重新运行 `smif status`。 ## 4. 选择自动或手工发布路径 [#4-选择自动或手工发布路径] 如果 `smif init` 已经生成 GitHub Actions 工作流,推荐从这里开始交给自动化处理: 1. 推送代码改动和 `.changes/add-api.md`,再把这次拉取请求合入 `main`; 2. 生成的工作流运行 `smif ci`,更新配置中的 `release` 分支,并创建或刷新对应的发布拉取请求; 3. 在发布拉取请求中检查自动生成的版本与变更日志,确认后把它合入 `main`; 4. 下一次工作流运行发现已经没有待处理的变更集,于是按照依赖关系发布准备好的软件包版本。 同一次发布不要再在本地额外运行 `smif version` 或 `smif publish`。只有仓库没有使用生成的工作流,或者正在一次性测试仓库中专门学习手工机制时,才继续执行下面的命令。 ## 5. 手工预览并应用版本修改 [#5-手工预览并应用版本修改] 先预览待修改文件和待运行命令: ```bash smif version --dry-run ``` 预演不会应用正常的版本副作用。配置命令只有明确设置 `dry-run = true` 时才会在预演中运行,因此这类命令必须能够安全重复执行。 确认预览符合预期后,应用修改: ```bash smif version ``` Semifold 会修改软件包版本和符合条件的内部依赖要求,写入变更日志,运行配置的版本修改后工作,并消费已经应用的变更集。 审查 Git diff,再运行仓库原有的测试。把生成文件提交到 `release` 分支,或者让生成的 GitHub Actions 工作流维护发布拉取请求。 ## 6. 手工发布 [#6-手工发布] 版本修改进入用于发布的分支后,为相关软件生态提供所需的软件包仓库凭据。 先预览发布执行: ```bash smif publish --dry-run ``` 这条命令会验证软件包顺序并执行只读的版本存在性检查,但不会发布软件包,也不会创建代码托管平台 Release。 确认预览正确后执行: ```bash smif publish ``` 软件包按照依赖关系发布。最终报告会分别列出成功、跳过、失败和尚未开始的软件包。在持续集成环境中,如果还要创建已经配置的 GitHub Release 与附件,可以加入 `--github-release`。 ## 发生失败时 [#发生失败时] * 找不到软件包:使用 `.changes/config.toml` 中 `[packages]` 的 `PackageId`,不要猜测软件包仓库名称。 * 工作区不干净:先检查并提交无关文件。`--allow-dirty` 表示有意接受当前 diff 作为输入,不会替你清理仓库。 * 软件包仓库中已经存在目标版本:Semifold 会跳过对应发布命令。启用 GitHub Release 时仍可以补建缺失的 Release;已经存在的 Release 不会触发附件恢复。 * 部分发布失败:根据最终报告中的成功、失败和尚未开始状态,修复失败的凭据、版本检查或命令后重试。不要手工再次发布已经确认存在的版本。 ## 理解工作流后再做自动化 [#理解工作流后再做自动化] 交互提示是正常的学习和本地维护路径。持续集成与输入受限环境可以通过参数提供相同答案: ```bash smif init --resolvers rust \ --base-branch main \ --release-branch release \ --default-tags \ --github-actions smif commit --name add-api \ --package my-package=minor \ --tag feat \ --summary "开放新的公共 API。" ``` 这些参数只是自动化兼容接口,不代表另一套发布模型,也不是开始使用 Semifold 的前置知识。 至此,你已经走完基础生命周期:发现软件包、记录变更集、联动修改相关版本与变更日志,再按依赖关系发布。仓库需要更多策略时,继续阅读[配置概览](https://semifold.noctisynth.org/zh/docs/configuration/overview/)。 --- # 安装 Semifold 安装最新发布的 Semifold 命令行工具,并确认 smif 命令可以使用。 Source: https://semifold.noctisynth.org/zh/docs/getting-started/installation/ Language: zh 安装后可以使用 `smif` 和 `semifold` 两个等价的命令名称。本文档统一使用较短的 `smif`。 除非需要复现旧环境,否则建议始终安装最新版本。安装脚本会查询 GitHub Releases,并选择标签符合 `semifold-vX.Y.Z` 的最新已发布稳定二进制 Release。脚本不会使用仓库级 `latest` 指针,因为它可能指向 Semifold monorepo 中的其他软件包。 ## 推荐方式:安装脚本 [#推荐方式安装脚本] ```bash curl -L https://semifold.noctisynth.org/install/install.sh | sh ``` 脚本默认安装到 `$HOME/.local/bin`。如果这个目录尚未加入 `PATH`,请在 shell 配置中添加它。 ```powershell irm https://semifold.noctisynth.org/install/install.ps1 | iex ``` 脚本默认安装到 `%USERPROFILE%\.local\bin`。如有需要,请把该目录加入 `PATH`。 如需修改安装目录,可以向下载后的脚本传入参数: ```bash curl -L https://semifold.noctisynth.org/install/install.sh | \ sh -s -- --install-dir "$HOME/bin" ``` ```powershell & ([scriptblock]::Create((irm https://semifold.noctisynth.org/install/install.ps1))) ` -InstallDir "$HOME\bin" ``` ## 安装指定版本 [#安装指定版本] 只有需要复现特定环境时才需要传入版本。`0.3.1` 和 `v0.3.1` 都会解析到 GitHub Release `semifold-v0.3.1`: ```bash curl -L https://semifold.noctisynth.org/install/install.sh | \ sh -s -- 0.3.1 ``` ```powershell & ([scriptblock]::Create((irm https://semifold.noctisynth.org/install/install.ps1))) ` -Version 0.3.1 ``` 默认安装不会选择预发布版本;如确实需要,请显式传入完整的预发布版本号。 ## 通过软件包管理器安装 [#通过软件包管理器安装] 也可以选择当前环境已经信任的软件包仓库: ```bash cargo install semifold ``` ```bash npm install --global @semifold/cli ``` ```bash pipx install semifold ``` npm 包要求 Node.js 20 或更高版本,并会在安装时选择 macOS、Windows 或基于 glibc 的 Linux 所对应的 x64/arm64 N-API binding。该包暂不支持 Linux musl 发行版,请在这些环境中使用安装脚本或 Cargo。 ## 验证命令 [#验证命令] 修改 `PATH` 后请打开一个新 shell,再运行: ```bash smif --version ``` 命令输出已安装的 Semifold 版本且没有报错,就表示安装成功。 现在可以继续[完成第一次发布](https://semifold.noctisynth.org/zh/docs/getting-started/first-release/),也可以先阅读[Semifold 管理什么](https://semifold.noctisynth.org/zh/docs/introduction/)。 --- # CLI 参数参考已移动 前往命令行模块查看 Semifold 命令行为与参数。 Source: https://semifold.noctisynth.org/zh/docs/reference/cli/ Language: zh CLI 参数参考现在与逐命令行为说明放在同一个模块中。请继续前往 [Semifold 命令参数参考](https://semifold.noctisynth.org/zh/docs/commands/reference/)。 --- # 同步工作区配置 在软件包增加、移动或移除后安全维护 .changes/config.toml,而不覆盖发布策略。 Source: https://semifold.noctisynth.org/zh/docs/workspace/config-sync/ Language: zh `smif init` 负责第一次创建配置。仓库结构继续变化后,使用 `smif config sync` 重新发现软件包并局部更新 `[packages]`。 ```bash smif config sync ``` 同步使用保留格式的 TOML 修改:已有注释、字段顺序、空行、`publish` 覆盖、发布命令、附件、通道设置和 `depends-on` 会保留。新发现的软件包不会自动写入 `publish`。相同输入重复运行不会继续产生差异。 ## 默认同步行为 [#默认同步行为] 命令比较“配置中的软件包”和“当前发现的软件包”,然后先展示计划: * 新发现的软件包可以加入配置; * 能够可靠匹配的软件包路径变化可以更新; * 已配置但无法发现的软件包会被报告,默认不会删除; * 无法安全判断的重命名、重复名称或解析错误会停止同步。 同步不会根据目录名称猜测跨生态依赖,也不会为新软件包自动生成 `depends-on`。这些关系需要维护者明确决定。 ## 用于 CI 的只读检查 [#用于-ci-的只读检查] ```bash smif config sync --check ``` `--check` 不写文件。只要存在需要同步的差异就返回非零状态,适合在拉取请求中防止新增软件包遗漏配置。 全局 `--dry-run` 也不写文件,但用途不同:它预览完整同步计划,并不把“存在差异”当作断言失败。 ## 删除已经移除的软件包 [#删除已经移除的软件包] ```bash smif config sync --prune ``` `--prune` 会删除当前无法发现的软件包配置,因此必须先确认它确实已经从仓库移除,而不是暂时解析失败或超出发现范围。`--prune` 与 `--check` 不能同时使用。 删除配置也会让引用该 `PackageId` 的变更集或 `depends-on` 失效。应用后运行: ```bash smif status ``` 让工作区图重新校验这些引用。 ## 只同步部分生态 [#只同步部分生态] `--resolver` 可以重复指定: ```bash smif config sync --resolver rust --resolver nodejs ``` 这只扫描指定的软件生态,适合大型仓库中的定向维护。参数值是配置中的生态 ID;插件生态也使用自己的 ID。 ## 不要混淆迁移与同步 [#不要混淆迁移与同步] `smif config migrate` 用于把 v0.2.x 配置中的旧字段转换为当前 TOML 契约,例如将 `version-mode` 迁移为 `channel`、把已知 snake\_case 字段改为 kebab-case,并为旧 HTTP pre-check 补上 `type = "http"`。 它不执行软件包发现。旧配置先迁移,再使用 `config sync` 对齐当前仓库: ```bash smif config migrate --check smif config migrate smif config sync --check ``` 完整参数和冲突关系见[配置文件命令](https://semifold.noctisynth.org/zh/docs/commands/config/)。 --- # C++ 工作区 基于 CMake 的静态软件包发现、版本修改、内部链接排序和当前限制。 Source: https://semifold.noctisynth.org/zh/docs/workspace/cpp/ Language: zh C++ 内置适配器围绕能够静态分析的 CMake 项目工作。它不运行 CMake,也不尝试解释构建时才出现的目录和 target。 ## 根项目要求 [#根项目要求] 项目根必须包含 `CMakeLists.txt`,并能从 `project(...)` 调用中读取数字版本: ```cmake project(my_library VERSION 1.4.0 LANGUAGES CXX) ``` 缺少 `VERSION` 的根项目不能作为当前 C++ 工作区入口。`project` 名称是清单名称,用于同一 C++ 工作区内的软件包匹配。 ## 子项目发现 [#子项目发现] Semifold 递归跟随字面量形式的: ```cmake add_subdirectory(libs/core) ``` 中间目录可以只负责分组;每个可达目录只要自己的 `CMakeLists.txt` 包含带 `VERSION` 的 `project(...)`,就会被发现为软件包。 为保证发现是确定且安全的,当前不会解释: * 变量拼接的子目录; * generator expression; * 下载、脚本或配置阶段生成的路径; * 逃出项目根目录的相对路径。 解析后的路径如果离开项目根目录会直接失败,而不是读取外部项目。 ## 内部依赖 [#内部依赖] 适配器识别以当前项目名为第一个参数的静态调用: ```cmake target_link_libraries(my_library PRIVATE support_library) ``` 当 `support_library` 也是同一工作区中发现的 `project` 名称时,建立内部依赖边。`PUBLIC`、`PRIVATE` 和 `INTERFACE` 当前都只影响依赖顺序,不自动触发版本传播。 变量、别名 target、generator expression 或其他间接链接无法可靠匹配时,不会猜测关系。需要重新发布语义时,在 Semifold 配置中加入 `depends-on`。 ## 版本修改 [#版本修改] Semifold 修改对应 `CMakeLists.txt` 的 `project(... VERSION ...)`。如果软件包目录存在 `vcpkg.json`,还会同步其根级 `version` 字段,避免两个公开版本来源漂移。 CMake 适配器当前只接受稳定数字版本,因此不支持 `alpha`、`beta`、`rc` 等命名发布通道。为 C++ 软件包设置命名 `channel` 会让版本规划失败;需要预发布语义时,应先评估自定义插件和发布流程是否能完整表达目标格式。 ## 发布 [#发布] CMake 描述构建图,不规定统一的软件包仓库,因此内置发现缺省把 C++ 软件包视为可发布。不需要 registry 流程的软件包应显式配置 `publish = false`。Semifold 把检查、构建、上传和发布行为留在 `[resolver.cpp]` 的命令配置中,并在统一发布计划中按依赖顺序运行。 使用 `smif publish --dry-run` 检查将运行的预检与命令。命令在软件包目录中执行;复杂的 CMake 配置、打包或 registry 客户端逻辑应放在仓库脚本中,而不是依赖 shell 操作符写进 `args`。 ## 何时使用插件 [#何时使用插件] 以下情况通常超出内置静态规则: * 版本不在 `project(... VERSION ...)`; * workspace 成员由自定义元数据或运行时脚本生成; * 内部依赖来自无法静态展开的 CMake 逻辑; * 需要非数字或自定义预发布版本格式。 插件可以为这些仓库定义发现、读取和候选版本修改,但发布命令仍由 resolver 配置负责。 --- # 依赖与版本传播 区分依赖排序、自动版本传播与跨生态 depends-on,理解为什么相关软件包会进入发布。 Source: https://semifold.noctisynth.org/zh/docs/workspace/dependencies/ Language: zh Semifold 把所有内置生态和插件发现的软件包放进同一张依赖图。依赖图同时回答两个不同问题: 1. 文件修改、构建和发布时谁必须先处理; 2. 某个依赖发布新版本时,依赖方是否也需要发布。 “参与排序”不等于“自动发布”。这是理解 `smif status` 结果最重要的区别。 ## 清单依赖总是参与排序 [#清单依赖总是参与排序] 只要适配器能把清单中的依赖唯一匹配到同一生态的软件包,它就成为内部依赖边。Rust 的 runtime、dev 和 build 依赖,以及 Node.js、Python、C++ 能识别的依赖,都会参与确定性拓扑排序。 排序保证依赖在前、依赖方在后。没有依赖关系的软件包按稳定 `PackageId` 排序,避免相同仓库在不同运行中出现随机顺序。 ## 当前自动传播规则 [#当前自动传播规则] | 依赖来源 | 是否参与排序 | 依赖发布时是否自动让依赖方发布 | | ----------------------- | ------ | ------------------------------------- | | Rust `[dependencies]` | 是 | 只有新版本不再满足 Rust 版本约束时,依赖方获得 `patch` 发布 | | Rust dev/build 依赖 | 是 | 否 | | Node.js、Python、C++ 清单依赖 | 是 | 否 | | 配置中的 `depends-on` | 是 | 是,依赖方获得 `patch` 发布 | Semifold 不使用 Rust semver 近似解释 npm、PEP 440 或 CMake 约束。因此这些生态的清单依赖当前只决定顺序。需要“底层软件包一发布,上层绑定就重新发布”时,显式配置 `depends-on`。 ## 声明跨生态关系 [#声明跨生态关系] 在依赖方的软件包配置中引用依赖的稳定 `PackageId`: ```toml [packages.rust-core] path = "crates/core" resolver = "rust" [packages.node-binding] path = "bindings/node" resolver = "nodejs" depends-on = ["rust-core"] ``` 这条边产生三个结果: * `rust-core` 始终先于 `node-binding` 修改和发布; * `rust-core` 发生任意版本发布时,尚未进入计划的 `node-binding` 获得 `patch` 发布; * 如果 `node-binding` 已有 `minor` 或 `major` 变更集,保留更高提升并追加依赖传播原因。 `depends-on` 也可以连接同一生态的软件包,用来表达清单无法表示的构建或重新发布关系。 ## 名称如何匹配 [#名称如何匹配] 清单依赖按“软件生态 + 清单名称”匹配,不直接按 `PackageId` 匹配。因此: * 不在工作区内的依赖被视为外部依赖; * 同一生态内重复清单名称会导致歧义错误; * 不同生态中的同名软件包不会形成隐式边; * `depends-on` 必须使用配置表中的准确 `PackageId`。 ## 诊断意外传播 [#诊断意外传播] 运行: ```bash smif status ``` 每个进入计划的软件包都会列出直接变更集或依赖传播原因。如果结果不符合预期,依次检查: 1. 变更集是否引用了正确 `PackageId`; 2. Rust runtime 版本约束是否仍包含依赖的新版本; 3. `depends-on` 是否表达了真实的重新发布需求; 4. 清单名称是否意外重复。 未知 `PackageId` 和依赖环都会作为结构化错误停止规划。环路必须在清单或配置中消除,Semifold 不会通过任意选择顺序来掩盖它。 --- # Node.js 工作区 package.json、npm/pnpm workspace、依赖前缀和私有软件包的当前支持范围。 Source: https://semifold.noctisynth.org/zh/docs/workspace/nodejs/ Language: zh Node.js 内置适配器以 `package.json` 为软件包事实来源,并支持 npm 风格 `workspaces` 与 `pnpm-workspace.yaml`。 ## 软件包发现 [#软件包发现] Semifold 从项目根读取以下工作区声明: * 根 `package.json` 中的 `workspaces`; * `pnpm-workspace.yaml` 中的 `packages`。 工作区模式下,根 `package.json` 本身也会被检查并作为软件包发现。没有工作区声明时,根 `package.json` 作为单个软件包读取。 清单中的 `name` 是依赖匹配使用的名称。显式 `version` 必须是有效语义化版本;缺少 `version` 的模板软件包暂时按 `0.0.0` 读取,并在第一次版本修改时写入目标版本。 `private: true` 默认表示不执行 registry 发布,但软件包配置中的可选 `publish` 可以覆盖 Semifold 使用的有效资格。`publish = true` 不会删除 `package.json` 中的 `private`,因此默认的 `npm publish` 仍可能拒绝执行。私有软件包仍参与版本计划、依赖排序和文件修改。 ## 依赖识别 [#依赖识别] 适配器读取: * `dependencies`; * `devDependencies`; * `peerDependencies`; * `optionalDependencies`。 匹配到工作区内同名 Node.js 软件包的依赖会进入统一拓扑排序。当前这些 npm 约束不会自动触发依赖方发布,因为 npm 范围与 Cargo 版本约束不能使用同一套解析规则近似处理。 如果一个 Node.js 软件包必须在内部依赖发布后重新构建或重新发布,请在它的 Semifold 配置中添加 `depends-on`。 ## 版本与依赖修改 [#版本与依赖修改] 修改 `package.json` 时,Semifold 使用 JSON 解析器验证完整结构,保持已有对象键顺序,输出标准缩进并保留一个尾部换行。它不承诺保留 JSON 中无法由标准解析器表示的原始空白布局。 内部依赖目标版本变化并需要修改清单时,会保留常见声明意图: * `workspace:*` 保持 `workspace:*`; * `workspace:^` 与 `workspace:~` 保留对应前缀; * 普通 `^` 与 `~` 范围保留前缀。 在应用版本修改前使用 `smif version --dry-run` 检查实际 diff,尤其是长期手工格式化的 `package.json`。 ## 发布通道与 npm tag [#发布通道与-npm-tag] `channel = "rc"` 等配置决定 Semifold 计算的版本,但不会自动改写共享 resolver 中的 `npm publish` 参数。命名通道的软件包应为发布命令显式配置匹配的 `--tag`,避免预发布版本进入 npm 的默认 `latest` tag。 `smif config channel set` 会在相关 Node.js resolver 缺少显式 `--tag` 时给出警告,但不会替你选择或修改 tag。 ## 发布 [#发布] 实际的软件包仓库检查、prepublish 和 publish 命令由 `[resolver.nodejs]` 配置提供。Semifold 在统一预检完成后按依赖顺序运行它们。 `private: true` 的软件包跳过 registry pre-check 与发布命令。GitHub Release 默认也关闭,但可以通过软件包级 `github-release = true` 单独启用。 ## 当前边界 [#当前边界] * 只有根 `package.json.workspaces` 和 `pnpm-workspace.yaml` 的 `packages` 是内置工作区入口;其他包管理器的专有发现规则需要插件或显式配置支持。 * Node.js 清单依赖参与排序,但不会自动传播版本。 * 缺少 `version` 会按 `0.0.0` 读取;显式无效版本则是错误,不会被替换为默认值。 --- # 软件包发现 理解 Semifold 如何从不同清单建立统一工作区,以及配置中的稳定软件包身份从哪里来。 Source: https://semifold.noctisynth.org/zh/docs/workspace/package-discovery/ Language: zh Semifold 先让每个软件生态适配器发现自己的软件包,再把结果合并成一张工作区图。后续的变更集校验、版本联动、文件修改和发布顺序都以这张图为基础。 ## 发现结果包含什么 [#发现结果包含什么] 每个被发现的软件包至少提供: * 清单中的名称和当前版本; * 所属软件生态与仓库相对路径; * 是否可以发布到外部软件包仓库; * 能够从清单中识别的内部依赖。 `smif init` 用发现结果生成初始 `[packages]`;`smif config sync` 则把已有配置与最新发现结果比较。适配器不会决定全局版本,也不会直接写文件、执行发布命令或访问 GitHub。 发现结果中的发布资格是缺省值。软件包配置可以用可选的 `publish = true` 或 `publish = false` 覆盖它;省略时继续采用 Rust、Node.js 或插件从清单推导的标识。这个覆盖只控制 Semifold 的软件包仓库流程,不修改清单文件。 ## 内置发现范围 [#内置发现范围] | 软件生态 | 主要清单或版本来源 | 发现入口 | 详细规则 | | ------- | ----------------------------------------------- | ----------------------------------------- | ----------------------------------------- | | Rust | `Cargo.toml` | 单软件包或 Cargo workspace members | [Rust 工作区](https://semifold.noctisynth.org/zh/docs/workspace/rust/) | | Node.js | `package.json` | 根软件包、`workspaces` 或 `pnpm-workspace.yaml` | [Node.js 工作区](https://semifold.noctisynth.org/zh/docs/workspace/nodejs/) | | Python | `pyproject.toml`、`setup.cfg` 或源码版本文件 | 根目录以及 `packages/*`、`libs/*`、`apps/*` | [Python 工作区](https://semifold.noctisynth.org/zh/docs/workspace/python/) | | C++ | 带 `project(... VERSION ...)` 的 `CMakeLists.txt` | 根项目和可静态跟随的 `add_subdirectory(...)` | [C++ 工作区](https://semifold.noctisynth.org/zh/docs/workspace/cpp/) | 这张表只帮助选择入口。各生态页面会分别说明动态版本、私有软件包、依赖类别和静态分析限制。 ## `PackageId` 与清单名称 [#packageid-与清单名称] 配置表名是稳定的软件包 ID(`PackageId`): ```toml [packages.rust-core] path = "crates/core" resolver = "rust" ``` 清单名称是 Cargo、npm、PyPI 或 CMake 使用的原生名称;发现时它只是默认 ID 的建议。配置一旦建立,Semifold 使用 `rust-core` 识别这一个软件包,即使清单名称或目录以后变化。 一份配置中的 `PackageId` 必须全局唯一。同一生态内的清单名称也必须唯一,否则 Semifold 无法判断清单依赖指向哪个软件包。不同生态可以使用相同清单名称;首次运行 `smif init` 时,Semifold 会为这些软件包自动添加软件生态前缀。例如,Rust 和 Node.js 中都名为 `shared` 的软件包会分别写成 `rust-shared` 和 `nodejs-shared`: ```toml [packages.rust-shared] path = "crates/shared" resolver = "rust" [packages.nodejs-shared] path = "packages/shared" resolver = "nodejs" ``` 没有同名冲突的软件包仍直接使用清单名称。如果带前缀的 ID 已被其他软件包占用,初始化会按稳定的发现顺序追加数字后缀,例如 `rust-shared-2`、`rust-shared-3`。同一生态内的重复清单名称仍会报告 `DuplicatePackageId`,因为自动改名无法消除清单依赖的歧义。 自动添加前缀只发生在首次生成配置时。已有配置会按“软件生态 + 路径”保留稳定 ID,`config sync` 不会重命名已有软件包;如果后续同时发现多个尚未配置的跨生态同名软件包,`config sync` 仍会报告冲突,等待维护者明确选择 ID。 Semifold 不会因为两个生态的软件包同名就推断依赖。跨生态关系必须使用 [`depends-on`](https://semifold.noctisynth.org/zh/docs/workspace/dependencies/) 显式引用稳定 ID。 ## 插件定义的软件生态 [#插件定义的软件生态] 内置适配器不是封闭清单。JavaScript 插件可以实现与内置生态相同的三个工作区操作: 1. 发现软件包; 2. 读取一个已配置软件包的当前事实; 3. 为目标版本返回候选文件修改。 插件返回的数据仍会进入同一张工作区图,并接受相同的身份、未知依赖、依赖环和文件修改校验。插件不能直接发布软件包;实际的软件包仓库检查和命令继续由 `[resolver.]` 配置负责。参见[插件概览](https://semifold.noctisynth.org/zh/docs/plugins/overview/)。 ## 发现失败意味着什么 [#发现失败意味着什么] 以下问题会阻止工作区加载,而不是被静默忽略: * 已配置路径离开项目根目录或不再包含预期清单; * 同一生态出现无法区分的重复清单名称; * 配置中的 `depends-on` 引用了未知 `PackageId`; * 内部依赖形成环; * 清单存在,但名称、显式版本或结构无效。 修正清单或配置后重新运行 `smif config sync --check`。如果问题来自某种生态的发现边界,请根据对应页面判断是调整仓库结构、显式配置关系,还是编写插件。 --- # Python 工作区 pyproject.toml、setup.cfg、动态版本来源、发现目录和发布通道限制。 Source: https://semifold.noctisynth.org/zh/docs/workspace/python/ Language: zh Python 内置适配器优先读取 `pyproject.toml`,在没有可用项目元数据时回退到 `setup.cfg`。它同时处理静态版本和常见动态版本布局。 ## 软件包发现 [#软件包发现] Semifold 检查项目根目录,以及这些一层目录中的直接子目录: * `packages/*`; * `libs/*`; * `apps/*`。 目录中存在 `pyproject.toml` 或 `setup.cfg` 时才会尝试解析。更深的任意递归目录、工具专有 workspace 声明或运行时生成的项目不会自动发现;这类布局可以调整配置结构或使用插件。 支持的项目元数据包括: * PEP 621 `[project]`; * Poetry `[tool.poetry]`; * `setup.cfg` 中的 package metadata。 ## 动态版本 [#动态版本] 当 PEP 621 声明 `dynamic = ["version"]`,适配器会尝试当前支持的明确来源: * 常见 package `__init__.py` 中的静态 `__version__`; * `__version__.py`; * `src/...` 布局中的对应文件; * 同一目录 `Cargo.toml` 的版本,用于常见 maturin/PyO3 项目; * Hatch 的 `version.path`。 只有能够静态定位和解析的来源才能安全写入。动态版本暂时无法读取时,当前实现会告警并用 `0.0.0` 参与发现;在真正创建版本修改前,应先确认来源属于上面的支持范围,避免把占位版本当作仓库事实。 ## 版本写入 [#版本写入] 根据发现的来源,Semifold 可以修改: * PEP 621 `project.version`; * Poetry `version`; * `setup.cfg` 的版本字段; * 静态 `__version__`; * Hatch `version.path` 指向的文件。 Python binding 可以从同目录 `Cargo.toml` 读取动态版本,但 Python 适配器不会写 Rust 清单。跨生态软件包默认保持各自的版本序列;需要 Rust 变化触发 Python binding 发布时,在 binding 上配置 `depends-on`。 ## 依赖与传播 [#依赖与传播] 适配器会读取支持格式中的 Python 依赖并把工作区内部匹配加入排序。当前不会使用 Python 清单约束自动触发依赖方发布,因为 PEP 440 语义不能由 Rust semver 规则替代。 发布绑定、生成包或需要重新构建的上层软件包时,使用显式 `depends-on` 表达传播意图。 ## 发布通道 [#发布通道] Python 版本只支持这些命名通道映射: | Semifold 通道 | Python 版本形式 | | ----------- | ----------- | | `alpha` | `aN` | | `beta` | `bN` | | `rc` | `rcN` | | `post` | `.postN` | 其他命名通道无法生成受支持的 Python 版本,规划会失败。稳定版本不受此限制。 ## 发布 [#发布] 实际的软件包仓库检查与发布命令来自 `[resolver.python]`,不由 Python 适配器直接执行。发布前先用 `smif publish --dry-run` 验证当前版本和预检。 当前内置 Python 发现不会从项目元数据推断“私有软件包”,发现的软件包缺省都会被视为可发布。不需要进入软件包仓库流程时,在对应 `[packages.]` 中显式设置 `publish = false`。GitHub Release 是否创建仍由软件包级 `github-release` 策略独立控制。 --- # Rust 工作区 Rust 软件包的 Cargo 发现、共享版本、依赖约束修改与私有发布规则。 Source: https://semifold.noctisynth.org/zh/docs/workspace/rust/ Language: zh Rust 内置适配器读取和修改 `Cargo.toml`,支持单个 Cargo 软件包与 Cargo workspace。Cargo 仍负责构建和解析依赖;Semifold 负责把发现结果接入统一的版本与发布流程。 ## 软件包发现 [#软件包发现] 当项目根 `Cargo.toml` 包含 `[workspace]` 时,Semifold 展开 `workspace.members` glob,并检查成员清单。根清单同时包含 `[package]` 时,根软件包也属于工作区。没有 workspace 时,根 `Cargo.toml` 作为单个软件包读取。 每个软件包的 Cargo package name 是清单名称。初始化时它可作为 `PackageId` 建议,但已有配置中的 ID 才是变更集和依赖图使用的稳定身份。 Cargo 的 `publish = false` 默认表示不向 crates.io 或其他软件包仓库发布。软件包配置中的可选 `publish` 可以覆盖 Semifold 使用的有效资格;`publish = true` 不会删除 Cargo 字段,因此默认的 `cargo publish` 仍可能拒绝执行。私有 crate 仍参与版本计算、共享版本组、文件修改和依赖排序。 ## 依赖识别 [#依赖识别] 适配器读取: * `[dependencies]`; * `[dev-dependencies]`; * `[build-dependencies]`; * 根清单中的 `[workspace.dependencies]`。 依赖别名通过 `package` 字段还原真实 Cargo package name。成员使用 `workspace = true` 时,Semifold 在共享的 `[workspace.dependencies]` 中理解和修改约束,不会向成员清单复制一份版本。 所有内部依赖都参与排序。当前只有 `[dependencies]` 中的 runtime 依赖参与约束感知的自动传播:如果依赖的新版本不再满足现有 Cargo 版本约束,依赖方进入一次 `patch` 发布。dev 和 build 依赖只决定顺序。 ## 版本写入 [#版本写入] 普通软件包在自己的 `package.version` 中获得目标版本。Rust 清单使用保留格式的 TOML 编辑,不会为了一个版本字段重排无关内容。 内部 runtime 依赖需要更新时,Semifold 同时处理普通、别名和 workspace 继承声明。只有目标软件包版本确实变化时才考虑重写约束,避免无意义地规范化仍然有效的宽松范围。 ## 共享 workspace 版本 [#共享-workspace-版本] 成员可以使用: ```toml [package] version.workspace = true [workspace.package] version = "1.4.0" ``` 共享同一个 `[workspace.package].version` 的软件包形成一个版本组。组内任意成员发生变化时: * 所有成员进入同一份发布计划; * 组内取最高的变更集提升等级; * 所有成员获得相同目标版本; * 根 `Cargo.toml` 的共享版本只修改一次; * 私有成员也会推动共享版本,并可能让同组公开成员进入发布。 同组软件包必须使用完全相同的 `channel` 与待消费的 `channel-bump`,否则规划失败。Semifold 不会按软件包顺序选择其中一个配置。 ## 发布 [#发布] Rust 适配器只提供清单事实和候选修改。实际的版本存在性检查、发布前命令和发布命令来自 `[resolver.rust]` 配置,并由统一发布引擎按依赖顺序执行。 私有 crate 跳过软件包仓库检查与发布命令。它默认不创建 GitHub Release,但可以通过 `github-release = true` 显式启用 Release 和附件;这项策略独立于 Cargo 的 `publish = false`。 ## 常见问题 [#常见问题] * workspace member glob 没有包含目标目录:修正 `workspace.members`,不要只在 Semifold 配置中伪造软件包。 * `version.workspace = true` 却缺少有效 `[workspace.package].version`:补齐共享版本来源。 * 两个 crate 使用相同 package name:在 Cargo 层消除歧义;仅修改 `PackageId` 无法让清单依赖唯一匹配。 * Rust crate 变化需要重新发布 Node.js 或 Python binding:在绑定软件包上添加跨生态 `depends-on`。