Mental model
This page explains the concepts that show up across every Tagsmith command and config field. Everything else in these docs maps back to here.
Target
A target is a releasable unit in the repository. In a single-app repo there is exactly one target (often named app or the project name); in a monorepo there are typically several (web, api, auth, ...).
Every target has:
- a unique name matching
^[a-z][a-z0-9-]*$ - a path that resolves from the repo root, must exist as a directory, must stay inside the repo's realpath, and must be unique by realpath across all targets
- a channel set (at least one channel; exactly one must have
strategy: "stable") - optional overrides for
tagPattern,tagMessage, andinitialVersion(defaults inherit from the top-leveldefaultsblock)
Target names and channel names are case-sensitive. Tagsmith never normalizes case. Target and channel names live in separate namespaces — a channel may share a name with a target.
Channel
A channel is a release line within a target. Each channel has:
- a name matching
^[a-z][a-z0-9-]*$, unique within the target - a strategy — either
"stable"or"prerelease" - an optional
dependsOnarray of channel names in the same target
There is exactly one "stable" channel per target. Convention is to call it stable, but the name doesn't matter; the strategy controls behavior. "prerelease" channels are everything else (alpha, beta, rc, custom names).
Channel name semantics:
- For
"prerelease"channels, the channel name is encoded in the SemVer prerelease identifier.rcchannel produces1.2.3-rc.1,1.2.3-rc.2, etc. - For the
"stable"channel, the name is never encoded in the version or the tag.1.2.3is just1.2.3.
Strategy
"stable"— versions are canonical stable SemVer (X.Y.Z, no prerelease, no build metadata). Allowed bumps:major,minor,patch.--bump prereleaseis rejected."prerelease"— versions have a prerelease identifier matching the channel name (X.Y.Z-<channel>.NwithN≥ 1). All four bumps are allowed.
Base version
The base version of a release is its stable X.Y.Z. For stable channels, the base version equals the version. For prerelease channels, the base version is the X.Y.Z part before the -. So 1.2.3-rc.1 has base version 1.2.3, and so does the eventual stable 1.2.3.
Base versions matter for two reasons:
dependsOnis evaluated at the same base. Tagsmith requires the dependency channel's tag at the same base, locally and remotely, peeling to the currentHEAD. The exact tag depends on the dependency channel's strategy: aprereleasedependency means the highest<base>-<channel>.N(e.g. tagging1.2.3onstablewithdependsOn: ["rc"]requires1.2.3-rc.Nfor the highest existingN); astabledependency means the canonical<base>tag itself.- Stable bumps ignore prerelease lines. A prerelease at a higher base doesn't influence the next stable bump. Worked example: latest stable
1.2.0, latest beta1.4.0-beta.1,--channel stable --bump minorresolves to1.3.0.
Bump
Tagsmith resolves the next version one of two ways:
--bump major | minor | patch | prerelease— incremental bump from existing tag history (or frominitialVersionwhen there is no tag history for that target).--version <semver>— explicit version literal. Tagsmith still enforces channel shape, monotonicity, anddependsOn.
Exactly one of --bump or --version is required.
Stable channels
Latest stable for the target is the maximum X.Y.Z across all stable tags managed by Tagsmith for that target.
--bump major→<latestStable.major + 1>.0.0--bump minor→<latestStable.major>.<latestStable.minor + 1>.0--bump patch→<latestStable.major>.<latestStable.minor>.<latestStable.patch + 1>--bump prerelease→ rejected (stable channels cannot bump prerelease)
If no stable tag exists, the bump resolves from initialVersion.
Prerelease channels
--bump major | minor | patch— start a new prerelease line atX.Y.Z-<channel>.1, whereX.Y.Zis the bumped base computed from the latest stable (orinitialVersion).--bump prerelease— continue the highest existing same-target / same-channel line by incrementingN. Fails with an actionable error if no prerelease tag exists yet for that channel — you must start a line with--bump major|minor|patchor--versionfirst.
Worked example: latest stable 1.2.0 for target app, no rc tags yet. --channel rc --bump minor produces 1.3.0-rc.1. The next --channel rc --bump prerelease produces 1.3.0-rc.2. A subsequent --channel rc --bump major then starts a fresh line at 2.0.0-rc.1.
Each prerelease bump still has to be strictly greater than the latest existing same-target / same-channel prerelease. After 1.3.0-rc.2, --bump patch would resolve to 1.2.1-rc.1 (base 1.2.1 from latest stable 1.2.0), which is less than 1.3.0-rc.2 — Tagsmith rejects it. Use --bump major to leap above the existing line, or --version to set the line explicitly.
Explicit --version
--version accepts the same shapes the matching strategy would produce. Tagsmith enforces:
- stable: canonical
X.Y.Z, strictly greater than the latest stable for this target (or ≥initialVersionif no stable exists). - prerelease:
X.Y.Z-<selected-channel>.NwithN≥ 1, strictly greater than the latest same-target / same-channel prerelease, and (if any stable exists) the base must be strictly greater than the latest stable. dependsOnis enforced just like a bumped version.
You can skip numbers — --version 5.0.0 after 1.2.0 is fine — but you can't go backwards.
dependsOn
dependsOn is a direct, validation-only gate between channels in the same target.
- Direct only — Tagsmith does not transitively walk dependencies. If
stabledependsOn: ["rc"]andrcdependsOn: ["beta"], taggingstablechecksrcat the same base but does not also checkbeta. (Indirect coverage comes from your own promotion discipline.) - Same target only — you cannot depend on a channel in another target.
- No self-dependencies, no cycles — both are config-validation errors.
- Does not participate in version resolution.
dependsOnis checked after the version has been resolved; it cannot influence which version comes next. - Evaluated at the same base. The dependency tag must exist locally and remotely, both must peel to the same commit, and that commit must equal the current
HEADwhen creating the tag. Duringvalidate, both must peel to the validated tag's commit. The exact dependency tag depends on the dependency channel's strategy:prereleasedependency — the highest<base>-<channel>.N. ForstabledependsOn: ["rc"]at base1.2.3, that's the highest1.2.3-rc.N.stabledependency — the canonical stable<base>tag (no prerelease identifier). A channel that depends onstableat base1.2.3requires the1.2.3tag itself to exist.
- If multiple matching prerelease dependency tags exist for the same base, only the highest
Nis checked. Stable dependencies have exactly one canonical tag per base.
Tag and tag message
The Git tag name is rendered from tagPattern. The annotated tag message is rendered from tagMessage. See Tag patterns for the full grammar.
tagPatternsupports{target}(optional, at most once) and exactly one{version}. Allowed literal characters:[a-z0-9._@-]. Channel name is never rendered into the tag name; it's encoded inside the version for prerelease channels.tagMessagesupports{target},{version},{tag}. Must be single-line printable text and non-empty after interpolation.
The annotated tag message is data, not code. Tagsmith never executes it. validate does not compare the existing annotated tag message to the rendered message — only the rendered version, tag, and target.
Managed namespace
A tag is managed by Tagsmith when it matches the target's effective tagPattern literals. If the literal parts match but the {version} capture is invalid or shaped wrong (e.g. lightweight, has build metadata, wrong prerelease shape, below initialVersion, local/remote peel to different commits), it is a malformed managed tag and fails the relevant command.
This means Tagsmith is strict inside its namespace and ignores tags outside it. Migration guidance: if you have legacy tags that conflict, pick a namespace like tagPattern: "managed-v{version}" for new releases.
init is the only command that doesn't read tags. targets validates config and paths but does not read tags or remotes. tag and validate scan the managed namespace and fail on anything malformed they encounter.
Reachability
validate requires that the validated tag's commit is reachable from <remote>/<baseBranch> according to local Git history. Tagsmith never fetches automatically, so CI must check out enough history (typically fetch-depth: 0) and fetch tags before invoking validate. If reachability cannot be proven, validate fails with explicit fetch guidance.
Interactive vs non-interactive
Every interactive decision is also an explicit flag. Tagsmith prompts only when all of the following hold:
- stdin and stdout are TTY
CIis unset or falsy (empty,0, orfalse)- no
--help/--version - no
--json/--github-output - not
init --dry-run(raw mode) - argv parsed cleanly (unknown commands/flags fail before any prompt)
See Interactive flows for the full eligibility rules and the per-command flow.
Vocabulary cheat sheet
| Term | Meaning |
|---|---|
| Target | Named releasable unit with a path and channels. |
| Channel | Release line within a target; one is "stable", others are "prerelease". |
| Strategy | "stable" or "prerelease" — controls allowed bumps and version shape. |
| Base version | Stable X.Y.Z portion. 1.2.3-rc.1 has base 1.2.3. |
| Bump | major, minor, patch, or prerelease. |
| Managed tag | Tag matching a target's effective tagPattern literals. |
| Malformed managed tag | Managed tag whose {version} capture or peel state is invalid. |
| dependsOn | Direct, validation-only gate between channels in the same target. |
| Preflight | Ordered set of checks run before mutation. See Preflight. |