diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4734efd..c40721f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,41 @@ jobs: - name: Format diff (informational) run: uv tool run ruff format --check --diff src/watchmen tests || true + plugin-version: + # The plugin ships to Claude Code / Codex via a marketplace clone, and + # `/plugin` only refreshes its cache when plugin.json's version changes. + # We once let plugin/bin drift a month ahead of a frozen version, so every + # installed user was stuck. Guard: if plugin payload changed vs base, the + # version must bump too. + name: plugin version bump + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Require a version bump when plugin payload changes + shell: bash + run: | + base="origin/${{ github.base_ref }}" + git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" + changed=$(git diff --name-only "$base"...HEAD) + payload=$(echo "$changed" | grep -E '^(plugin|plugin-codex)/(bin|hooks|skills)/' || true) + if [ -z "$payload" ]; then + echo "No plugin payload changed; version bump not required." + exit 0 + fi + echo "Plugin payload changed:"; echo "$payload" + for mf in plugin/.claude-plugin/plugin.json plugin-codex/.codex-plugin/plugin.json; do + old=$(git show "$base:$mf" | python3 -c "import json,sys; print(json.load(sys.stdin)['version'])") + new=$(python3 -c "import json; print(json.load(open('$mf'))['version'])") + echo "$mf: $old -> $new" + if [ "$old" = "$new" ]; then + echo "::error::$mf payload changed but version stayed $old. Bump it so /plugin picks up the update." + exit 1 + fi + done + tests: name: tests (${{ matrix.os }}, py${{ matrix.python }}) runs-on: ${{ matrix.os }} diff --git a/plugin-codex/.codex-plugin/plugin.json b/plugin-codex/.codex-plugin/plugin.json index 7193e46..d3b3003 100644 --- a/plugin-codex/.codex-plugin/plugin.json +++ b/plugin-codex/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "watchmen", - "version": "0.1.6", + "version": "0.1.7", "description": "Skill suggestions + workspace context from your watchmen sessions corpus, surfaced into Codex sessions.", "author": { "name": "Dria", diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 3e8f8b2..7bc013f 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "watchmen", "description": "Surface skill suggestions + workspace context from watchmen sessions intelligence into your Claude Code sessions", - "version": "0.1.6", + "version": "0.1.7", "author": { "name": "dria", "url": "https://github.com/firstbatchxyz/watchmen" diff --git a/tests/test_plugin_version.py b/tests/test_plugin_version.py new file mode 100644 index 0000000..07fd977 --- /dev/null +++ b/tests/test_plugin_version.py @@ -0,0 +1,36 @@ +"""The Claude Code and Codex plugin manifests must declare the same version. + +`/plugin` only refreshes its cache when the version changes, so a drift where +one harness bumps and the other doesn't would silently strand Codex (or Claude) +users on a stale plugin. CI also guards "payload changed → version bumped" +against the base branch; this is the cheap in-tree check that the two manifests +stay in lockstep. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +CLAUDE_MANIFEST = REPO / "plugin" / ".claude-plugin" / "plugin.json" +CODEX_MANIFEST = REPO / "plugin-codex" / ".codex-plugin" / "plugin.json" + + +def _version(path: Path) -> str: + return json.loads(path.read_text())["version"] + + +def test_plugin_manifests_share_one_version(): + claude = _version(CLAUDE_MANIFEST) + codex = _version(CODEX_MANIFEST) + assert claude == codex, ( + f"plugin versions drifted: Claude {claude} vs Codex {codex}. " + "Bump both together so /plugin refreshes the cache on every harness." + ) + + +def test_plugin_version_is_semver_ish(): + parts = _version(CLAUDE_MANIFEST).split(".") + assert len(parts) == 3 and all(p.isdigit() for p in parts), \ + "plugin version should be MAJOR.MINOR.PATCH"