ferrflow 5.22.1

Universal semantic versioning for monorepos and classic repos
Documentation
# `release --json` output

`ferrflow release --json` emits a single JSON object on stdout describing
the release. All other human-readable stdout is suppressed so the object
is the only thing written (diagnostics and warnings still go to stderr).
This is the machine-readable counterpart to `check --json` and is meant
for CI steps that react to what a release just produced.

Combine it with `--dry-run` to get the computed plan without mutating the
repository.

## Schema

```json
{
  "released": [
    {
      "package": "api",
      "previous_version": "1.2.3",
      "new_version": "1.3.0",
      "bump_type": "minor",
      "tag": "api@v1.3.0",
      "commit_count": 7,
      "prerelease": false,
      "forge_release_url": "https://github.com/owner/repo/releases/tag/api@v1.3.0",
      "forge_release_id": 123456789
    }
  ],
  "skipped": [
    { "package": "web", "reason": "no releasable commits" }
  ],
  "git": {
    "commit": "abc1234",
    "tags_pushed": ["api@v1.3.0"],
    "branch": "main"
  },
  "dry_run": false
}
```

### Fields

| Field | Type | Notes |
| --- | --- | --- |
| `released[]` | array | One entry per package that was (or would be) released this run, including dependency-cascade bumps. |
| `released[].package` | string | Package name from the config. |
| `released[].previous_version` | string | Version before the bump. |
| `released[].new_version` | string | Version after the bump. |
| `released[].bump_type` | string | `major`, `minor`, `patch`, `forced`, or a strategy name (`calver`, `sequential`, …). |
| `released[].tag` | string | The tag that was (or would be) created. |
| `released[].commit_count` | number | Number of commits considered for this package's changelog. |
| `released[].prerelease` | boolean | Whether the new version is a pre-release. |
| `released[].forge_release_url` | string \| null | URL of the created forge release. `null` on dry-run, when no forge is configured, or when the release was not created. |
| `released[].forge_release_id` | number \| null | Numeric id of the created forge release (GitHub). `null` on dry-run, on GitLab, or when not created. |
| `skipped[]` | array | Packages that were considered but not released. |
| `skipped[].package` | string | Package name. |
| `skipped[].reason` | string | Why it was skipped (`not touched`, `no new commits`, `no releasable commits`, `version unchanged`). |
| `git.commit` | string | Short HEAD SHA after the run. |
| `git.tags_pushed` | array | Tags pushed to the remote. Empty on dry-run. |
| `git.branch` | string | Branch the release targeted. |
| `dry_run` | boolean | `true` when `--dry-run` was passed. |

## Dry-run behaviour

With `--dry-run --json`:

- `dry_run` is `true`.
- `released[]` and `skipped[]` are populated from the computed plan.
- `git.tags_pushed` is `[]` and the `forge_release_*` fields are `null`
  (nothing is created or pushed).

## Interaction with `--dry-run --verbose`

`--dry-run --verbose` (without `--json`) prints a unified diff of every
file a release would change, including the changelog. When both `--json`
and `--dry-run --verbose` are passed, `--json` wins: only the JSON object
is emitted and the diffs are suppressed.

## Stability

The field names and structure above are a stable contract. New optional
fields may be added over time; existing fields will not be removed or
renamed without a major version bump. Consumers should ignore unknown
fields rather than failing on them.