Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion plugin-codex/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
36 changes: 36 additions & 0 deletions tests/test_plugin_version.py
Original file line number Diff line number Diff line change
@@ -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"
Loading