Skip to content

Commit 95f2838

Browse files
committed
feat(release): unify plugin versioning at v2
1 parent 2cc6b01 commit 95f2838

12 files changed

Lines changed: 211 additions & 25 deletions

File tree

.github/workflows/release.yml

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
run: |
3636
set -euo pipefail
3737
latest_tag=$(git tag --list --sort=-version:refname |
38-
grep -E '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' |
38+
grep -E '^v([2-9]|[1-9][0-9]+)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' |
3939
head -n 1 || true)
4040
if [[ -n "$latest_tag" ]]; then
4141
echo "has_tag=true" >>"$GITHUB_OUTPUT"
@@ -54,7 +54,7 @@ jobs:
5454
minorList: feat, feature
5555
patchList: fix, bugfix, perf, refactor, test, tests
5656
skipInvalidTags: true
57-
tagFilter: '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
57+
tagFilter: '^v([2-9]|[1-9][0-9]+)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
5858
maxTagsToFetch: 100
5959
noNewCommitBehavior: current
6060
noVersionBumpBehavior: current
@@ -70,8 +70,8 @@ jobs:
7070
run: |
7171
set -euo pipefail
7272
if [[ "$HAS_TAG" == false ]]; then
73-
tag=v0.1.0
74-
version=0.1.0
73+
tag=v2.0.0
74+
version=2.0.0
7575
should_release=true
7676
else
7777
tag=$NEXT_TAG
@@ -82,7 +82,7 @@ jobs:
8282
*) echo "Unexpected semver bump: $BUMP" >&2; exit 1 ;;
8383
esac
8484
fi
85-
[[ "$tag" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
85+
[[ "$tag" =~ ^v([2-9]|[1-9][0-9]+)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
8686
[[ "$version" == "${tag#v}" ]]
8787
{
8888
printf 'tag=%s\n' "$tag"
@@ -103,9 +103,17 @@ jobs:
103103
from pathlib import Path
104104
105105
version = sys.argv[1]
106-
manifest = Path("plugins/gestalt/.codex-plugin/plugin.json")
107-
assert json.loads(manifest.read_text())["version"] == version, manifest
106+
versioned_json = (
107+
Path("plugins/gestalt/.codex-plugin/plugin.json"),
108+
Path("plugins/context-mode/.codex-plugin/plugin.json"),
109+
Path("plugins/context-mode/package.json"),
110+
)
111+
for path in versioned_json:
112+
assert json.loads(path.read_text())["version"] == version, path
113+
provenance = Path("plugins/context-mode/UPSTREAM.md").read_text()
114+
assert f"- Downstream package version: `{version}`" in provenance
108115
PY
116+
bash tests/plugins/context-mode/test-upstream-vendor.sh
109117
110118
- name: Commit synchronized plugin versions
111119
if: steps.release.outputs.should_release == 'true'
@@ -115,10 +123,17 @@ jobs:
115123
set -euo pipefail
116124
git config user.name github-actions[bot]
117125
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
118-
if git diff --quiet -- plugins/gestalt/.codex-plugin/plugin.json; then
119-
echo "Plugin manifests already match $TAG"
126+
release_paths=(
127+
plugins/gestalt/.codex-plugin/plugin.json
128+
plugins/context-mode/.codex-plugin/plugin.json
129+
plugins/context-mode/package.json
130+
plugins/context-mode/UPSTREAM.md
131+
tests/plugins/context-mode/fixtures/context-mode-codex-hardening-4b1348d.sha256
132+
)
133+
if git diff --quiet -- "${release_paths[@]}"; then
134+
echo "Plugin versions already match $TAG"
120135
else
121-
git add plugins/*/.codex-plugin/plugin.json
136+
git add -- "${release_paths[@]}"
122137
git commit -m "chore(release): $TAG [skip ci]"
123138
fi
124139

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ contracts. It also installs both plugins through the current Codex CLI in an
132132
isolated home. GitHub runs it on Linux and macOS with Node.js 22.12.0. The
133133
release job starts only after both operating-system jobs pass.
134134

135+
Releases use conventional commits with `ietf-tools/semver-action@v1`. The
136+
stable release line starts at `v2.0.0`; historical `v0.x` tags are excluded
137+
from future version calculations. Each release assigns the same version to the
138+
Gestalt plugin and the adapted context-mode plugin/runtime. `feat` advances the
139+
minor version, supported fix-oriented commit types advance the patch version,
140+
and breaking changes advance the major version.
141+
135142
# 📃 Plan
136143

137144
Each L1 starts unreviewed. After implementation and test gates make it

USAGE.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,16 @@ integrity, its Codex-focused Vitest suite, and nested MCP startup. GitHub runs
181181
the canonical command on Linux and macOS with Node.js 22.12.0 and verifies both
182182
plugins through the current Codex CLI's real marketplace installation path. A
183183
push to `main` can enter the release job only after both validation jobs pass.
184+
185+
## Release versioning
186+
187+
The repository release line starts at `v2.0.0` and is calculated by
188+
`ietf-tools/semver-action@v1` from conventional commits. Existing `v0.x` tags
189+
remain as historical releases but are excluded from the new calculation
190+
baseline. The Gestalt manifest, context-mode manifest and runtime package use
191+
the same repository release version; context-mode's imported upstream version
192+
remains recorded separately in `UPSTREAM.md`. Once `v2.0.0` exists, `feat` and
193+
`feature` commits bump the minor version; `fix`, `bugfix`, `perf`, `refactor`,
194+
`test`, and `tests` bump the patch version; and a conventional-commit
195+
breaking-change marker bumps the major version. Other commit types do not
196+
create a release.

plugins/context-mode/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "context-mode",
3-
"version": "1.0.169-dyne.4",
3+
"version": "2.0.0",
44
"description": "MCP server for sandboxed output analysis, FTS5 indexed search, and session continuity across compactions.",
55
"author": {
66
"name": "Mert Koseoğlu",

plugins/context-mode/UPSTREAM.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- Imported commit: `4b1348d4bba530d26cfc73181a0c2f263923e334`
66
- Describe: `v1.0.169-56-g4b1348d`
77
- Upstream package version: `1.0.169`
8-
- Downstream package version: `1.0.169-dyne.4`
8+
- Downstream package version: `2.0.0`
99
- License: Elastic-2.0
1010
- Import date: 2026-07-22
1111

plugins/context-mode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "context-mode",
3-
"version": "1.0.169-dyne.4",
3+
"version": "2.0.0",
44
"private": true,
55
"type": "module",
66
"description": "Codex MCP plugin for sandboxed output analysis, FTS5 search, and session continuity.",

plugins/gestalt/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gestalt",
3-
"version": "0.12.4",
3+
"version": "2.0.0",
44
"description": "Dyne.org Gestalt planning, debugging, testing, verification, and skill-authoring workflows",
55
"author": {
66
"name": "Dyne.org",

scripts/set-plugin-version.py

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
import argparse
7+
import hashlib
78
import json
89
import os
910
import re
@@ -37,12 +38,31 @@ def prepare_updates(root: Path, version: str) -> list[tuple[Path, str, int]]:
3738
raise ValueError(f"invalid strict semantic version: {version}")
3839

3940
owned = root / "plugins" / "gestalt" / ".codex-plugin" / "plugin.json"
40-
manifests = [owned] if owned.exists() else sorted((root / "plugins").glob("*/.codex-plugin/plugin.json"))
41+
context_root = root / "plugins" / "context-mode"
42+
context_manifest = context_root / ".codex-plugin" / "plugin.json"
43+
context_package = context_root / "package.json"
44+
context_provenance = context_root / "UPSTREAM.md"
45+
checksum_fixture = (
46+
root
47+
/ "tests"
48+
/ "plugins"
49+
/ "context-mode"
50+
/ "fixtures"
51+
/ "context-mode-codex-hardening-4b1348d.sha256"
52+
)
53+
repository_layout = owned.exists()
54+
manifests = (
55+
[owned, context_manifest, context_package]
56+
if repository_layout
57+
else sorted((root / "plugins").glob("*/.codex-plugin/plugin.json"))
58+
)
4159
manifests = [path for path in manifests if path.is_file() and not path.is_symlink()]
42-
if not manifests:
60+
expected_manifest_count = 3 if repository_layout else 1
61+
if len(manifests) < expected_manifest_count:
4362
raise ValueError("no Dyne-owned plugin manifests found")
4463

45-
updates = []
64+
updates: list[tuple[Path, str, int]] = []
65+
context_replacements: dict[str, bytes] = {}
4666
for path in manifests:
4767
try:
4868
manifest = json.loads(path.read_text(encoding="utf-8"))
@@ -53,6 +73,55 @@ def prepare_updates(root: Path, version: str) -> list[tuple[Path, str, int]]:
5373
manifest["version"] = version
5474
serialized = json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
5575
updates.append((path, serialized, stat.S_IMODE(path.stat().st_mode)))
76+
if repository_layout and path.is_relative_to(context_root):
77+
context_replacements[str(path.relative_to(context_root))] = serialized.encode()
78+
79+
if repository_layout:
80+
try:
81+
provenance = context_provenance.read_text(encoding="utf-8")
82+
except OSError as error:
83+
raise ValueError(f"cannot read {context_provenance}: {error}") from error
84+
provenance, replacements = re.subn(
85+
r"(?m)^- Downstream package version: `[^`]+`$",
86+
f"- Downstream package version: `{version}`",
87+
provenance,
88+
)
89+
if replacements != 1:
90+
raise ValueError(f"expected one downstream package version in {context_provenance}")
91+
updates.append(
92+
(context_provenance, provenance, stat.S_IMODE(context_provenance.stat().st_mode))
93+
)
94+
context_replacements["UPSTREAM.md"] = provenance.encode()
95+
96+
try:
97+
fixture_lines = checksum_fixture.read_text(encoding="utf-8").splitlines()
98+
except OSError as error:
99+
raise ValueError(f"cannot read {checksum_fixture}: {error}") from error
100+
fixture_targets = set(context_replacements)
101+
fixture_seen: set[str] = set()
102+
rewritten_fixture: list[str] = []
103+
for line in fixture_lines:
104+
try:
105+
mode, remainder = line.split(" ", 1)
106+
_, relative = remainder.split(" ", 1)
107+
except ValueError as error:
108+
raise ValueError(f"malformed checksum fixture line: {line}") from error
109+
replacement = context_replacements.get(relative)
110+
if replacement is not None:
111+
digest = hashlib.sha256(replacement).hexdigest()
112+
line = f"{mode} {digest} {relative}"
113+
fixture_seen.add(relative)
114+
rewritten_fixture.append(line)
115+
if fixture_seen != fixture_targets:
116+
missing = sorted(fixture_targets - fixture_seen)
117+
raise ValueError(f"checksum fixture is missing versioned paths: {missing}")
118+
updates.append(
119+
(
120+
checksum_fixture,
121+
"\n".join(rewritten_fixture) + "\n",
122+
stat.S_IMODE(checksum_fixture.stat().st_mode),
123+
)
124+
)
56125
return updates
57126

58127

tests/plugins/context-mode/fixtures/context-mode-codex-hardening-4b1348d.sha256

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
0644 2b807b5fc9500bf8696505fff1245c71509a34196e6fb119d500ddcf009686cf .codex-plugin/plugin.json
1+
0644 c31b0aeae639e71b91ab56683ddbcf74221630a1c8fdb42f0a4b96920d9ec84b .codex-plugin/plugin.json
22
0644 3e70c5ec0bf019b6e87b35a29887cf262f00eb0d7f5eb752fb1782968a987fb2 .gitattributes
33
0644 1e55a43f3a8bcac25ea350e7e360258c91dba0a3abfaf60da6d3a55cf8a34386 .gitignore
44
0644 0a755f13da488a21ac9fb019c74cb0130c25df48fce325419bdc5528e376a2c9 .mcp.json
55
0644 92f11a867c41c9e575ccb978ff205ae2011c244a14aa3c576e914e537105caa0 LICENSE
66
0644 af9586c4db513a40b02464c60c77d3337b3e00802829e8899b996579f6787298 README.md
7-
0644 cabe665ae8a3a74d4fdc5799ef9075ce85f1e0fb3a507d040d9317903ef1fa67 UPSTREAM.md
7+
0644 5fb7026a9c843c3ac92e445c03fd6fb7aa8aa32e31a6d540878b48c39ea75559 UPSTREAM.md
88
0644 310d4ae12ca0d37a4ae1325cfb5112a607e6789fc15d9bfc700b77af7380be8b bun.lock
99
0644 3f06cfbce24fdfde17b4ff03bb25796471c8a9eadd671c125cd8ec6ba1bacb91 configs/codex/AGENTS.md
1010
0644 9759c9222618a521cc836cf6c451d031d821333cfd3bac74efcb1467a940d0e1 configs/codex/config.toml
@@ -43,7 +43,7 @@
4343
0644 b740f288239fa5b05b3db36e9ab39ea2495e454df88ac5895f2a58e7a7762e06 hooks/stop.mjs
4444
0644 767faa2ceda55b7857f8d36f5ce6c3b123e7a8442005b875c7052cee486d324b hooks/suppress-stderr.mjs
4545
0755 d777abc6bfa2350526facc34f1f4502bc351dc01e67ad5a21244bc58cfbb3e9b hooks/userpromptsubmit.mjs
46-
0644 c5ccf1e0ee1143003964843a577386014277f54e28b91974dcd81e4b33731323 package.json
46+
0644 ca846a35dfbf0e2a24b74db13acf764da3d0f680fd76fa9cded30a3fa95ca9e9 package.json
4747
0644 0e0693d6159e36922528e78c13078596c55c4dd6b8563d568fec596aacad2e24 scripts/assert-bundle.mjs
4848
0755 86ffe608f4667edbfc60c2a21254659d92f26beabf210a6416aa7e676ab09386 scripts/assert-codex-package.mjs
4949
0755 3b0ef506a9c5dce94489443a7140e1e009c4ce9f1611f38d763451d40aa5a84a scripts/ctx-debug.sh

tests/repository/test-plugin-layout.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ manifests = {
4343
}
4444
assert set(manifests) == {"gestalt", "context-mode"}, f"unexpected plugin manifests: {sorted(manifests)}"
4545
assert all(manifest["name"] == name for name, manifest in manifests.items())
46+
assert manifests["gestalt"]["version"] == manifests["context-mode"]["version"], (
47+
"marketplace plugin versions must stay synchronized"
48+
)
49+
context_package = json.loads((root / "plugins/context-mode/package.json").read_text())
50+
assert context_package["version"] == manifests["gestalt"]["version"], (
51+
"context-mode runtime and marketplace versions must stay synchronized"
52+
)
4653
assert "skills" not in manifests["context-mode"], "context-mode skills must be owned by Gestalt"
4754
for name, manifest in manifests.items():
4855
assert re.fullmatch(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?", manifest["version"]), (

0 commit comments

Comments
 (0)