Priority: low (tier-4). Filed from an investigation; no code yet.
Problem
The rolling-next git tag can silently desync from plugin.json. A branch bumps plugin.json (e.g. to 1.16.2), merges to dev, but the matching v1.16.2 tag is only created if someone manually runs /forge:next (forge-next-prep --tag). If nobody runs it, dev sits at plugin.json=1.16.2 with the latest tag still v1.16.1 — and the pre-commit guard keeps passing (1.16.2 > v1.16.1), so later PRs are never forced to bump. This actually happened: v1.16.2 was missing and had to be tagged by hand.
The tagging logic already exists
next_prep.py::_maybe_tag_release already does the right thing — tags v<plugin.json.version> at HEAD and pushes when plugin.json is strictly newer than the latest tag, idempotent otherwise. It resolves the latest tag via _latest_v_tag = git tag --list v* --sort=-v:refname (global semver-max, branch-independent — correct). It's just wired only to the manual /next path.
Proposal
Call the existing _maybe_tag_release automatically from forge-post-merge (already interactive-only / CI-safe per FOUNDATION §15; already fires on every git pull). No new tag logic — reuse.
Forge-only gating (must not fire in consumer repos): add an explicit opt-in [tool.forge] auto_tag_on_merge = true (FOUNDATION §16 Pattern C — only forge sets it), AND guard on current branch == dev_branch. _maybe_tag_release already no-ops without .claude-plugin/plugin.json.
Open decision — auto-push risk: _maybe_tag_release pushes the tag (irreversible release) as a side effect of git pull. Two postures:
- Advisory: post-merge only warns ("plugin.json X > latest tag Y — run forge-next-prep --tag"), mirroring the staleness-check pattern. Human pulls the trigger.
- Full-auto: tag + push behind the opt-in flag (the bump was already PR-reviewed).
Also: fix a guard inconsistency (tech-debt)
verify_plugin_version.py resolves "latest tag" with git describe --tags --abbrev=0 (ancestry-scoped — misses tags not in HEAD's history, e.g. a release tagged on main after a dev/main split). next_prep._latest_v_tag uses global semver-max. They disagree in the dual-track case. Switch the guard to the same global-max resolution so the guard and the auto-tagger never contradict.
Decisions to make when picked up
Priority: low (tier-4). Filed from an investigation; no code yet.
Problem
The rolling-next git tag can silently desync from
plugin.json. A branch bumpsplugin.json(e.g. to1.16.2), merges todev, but the matchingv1.16.2tag is only created if someone manually runs/forge:next(forge-next-prep --tag). If nobody runs it,devsits atplugin.json=1.16.2with the latest tag stillv1.16.1— and the pre-commit guard keeps passing (1.16.2 > v1.16.1), so later PRs are never forced to bump. This actually happened:v1.16.2was missing and had to be tagged by hand.The tagging logic already exists
next_prep.py::_maybe_tag_releasealready does the right thing — tagsv<plugin.json.version>at HEAD and pushes whenplugin.jsonis strictly newer than the latest tag, idempotent otherwise. It resolves the latest tag via_latest_v_tag=git tag --list v* --sort=-v:refname(global semver-max, branch-independent — correct). It's just wired only to the manual/nextpath.Proposal
Call the existing
_maybe_tag_releaseautomatically fromforge-post-merge(already interactive-only / CI-safe per FOUNDATION §15; already fires on everygit pull). No new tag logic — reuse.Forge-only gating (must not fire in consumer repos): add an explicit opt-in
[tool.forge] auto_tag_on_merge = true(FOUNDATION §16 Pattern C — only forge sets it), AND guard oncurrent branch == dev_branch._maybe_tag_releasealready no-ops without.claude-plugin/plugin.json.Open decision — auto-push risk:
_maybe_tag_releasepushes the tag (irreversible release) as a side effect ofgit pull. Two postures:Also: fix a guard inconsistency (tech-debt)
verify_plugin_version.pyresolves "latest tag" withgit describe --tags --abbrev=0(ancestry-scoped — misses tags not in HEAD's history, e.g. a release tagged onmainafter a dev/main split).next_prep._latest_v_taguses global semver-max. They disagree in the dual-track case. Switch the guard to the same global-max resolution so the guard and the auto-tagger never contradict.Decisions to make when picked up
auto_tag_on_merge?)verify_plugin_versiontag-resolution fix into the same PR