diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index ae00a4f..c462aa9 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "scribe", "description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your always-on rules + active-project context, process-skills (writing-plans, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.", - "version": "2026.09.02.0415", + "version": "2026.09.02.0438", "author": { "name": "Bryan Van Deusen" }, diff --git a/plugin/README.md b/plugin/README.md index 14a1fdc..adbd6b6 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -78,8 +78,13 @@ On install you'll be asked for: ## Notes -- Set a `version` bump in `.claude-plugin/plugin.json` per release so clients - pick up changes. +- **Do not hand-edit `version` in `.claude-plugin/plugin.json`.** It is minted + from the clock — run `make mint-plugin` (or `python3 + scripts/mint_plugin_version.py`) after changing anything under `plugin/`, and + commit the result. The installer decides whether to refresh the cache it + executes from by comparing that string, so content that ships without a new + version reaches the repo and stops there (#2209). CI fails the lane if you + forget. - The session-start, auto-inject and prior-art hooks need only a **read**-scoped key; the MCP tools need **write** scope to create/update. Every hook is a GET for that reason — a read key cannot POST. diff --git a/scripts/check_plugin.py b/scripts/check_plugin.py index 9066a7a..dd381ba 100755 --- a/scripts/check_plugin.py +++ b/scripts/check_plugin.py @@ -13,9 +13,20 @@ separate defects have reached a live install through that path: install, because `plugin.json`'s version wasn't bumped and the installer compares versions to decide whether to refresh its cache. -The rule for the second one was already written down and was still missed. A -written rule that depends on being remembered during a long session is not a -control; this is. +Both were fixed. The second was fixed TWICE — once by bumping the number, and +then properly, by removing the class it came from: `plugin.json`'s version is +no longer a value anybody chooses. `scripts/mint_plugin_version.py` derives it +from the clock (`make mint-plugin`), and `check_version_is_minted` below fails +the lane when shipped content moved and the version did not. + +State exactly what that did and did not remove, because a rationale that +overstates its own control is how the control gets trusted past its limit, and +because the paragraph this replaces was itself read that way. Gone: having to +remember which NUMBER to write, and the whole question of whether a chosen +number was the right one. Not gone: the mint still has to be RUN, and +forgetting to run it is still possible. What changed is that forgetting is now +LOUD — a red lane on the batch that forgot, instead of a silent no-op found +weeks later when somebody says "I don't think it updated" (#2220). shellcheck and jq are NOT in `ci-python` (verified against CI-runner's Dockerfile and scripts/install-common.sh, not from memory — rule #37). CI installs both @@ -31,8 +42,15 @@ itself loudly, because a check that quietly no-ops is the failure mode this whole file exists to prevent. Usage: - python3 scripts/check_plugin.py # all checks - python3 scripts/check_plugin.py --no-version # skip the version check + python3 scripts/check_plugin.py # all checks + python3 scripts/check_plugin.py --no-version # on `main` only — see below + +`--no-version` exists for ONE case. The version is measured against +`origin/main`, so on `main` itself the comparison is against itself and answers +nothing; the syntax, pattern and marker checks are the only ones that mean +anything there. It is NOT a way past a red lane — see +`check_version_is_minted`, whose whole design is shaped by keeping this flag +out of anyone's muscle memory. """ from __future__ import annotations @@ -217,8 +235,6 @@ def check_patterns() -> None: ok(f"{rel}: no known-bad patterns") -# --- the version bump ------------------------------------------------------ - # --- shellcheck ------------------------------------------------------------ def check_shellcheck() -> None: @@ -668,7 +684,8 @@ def check_version_is_minted(base: str = "origin/main") -> None: def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--no-version", action="store_true", - help="skip the minted-version check") + help="skip the minted-version check; for `main`, where " + "it would be measured against itself") parser.add_argument("--base", default="origin/main", help="branch the version is measured against") args = parser.parse_args() diff --git a/tests/test_plugin_shipped_set.py b/tests/test_plugin_shipped_set.py index 0a8f46e..2587f6e 100644 --- a/tests/test_plugin_shipped_set.py +++ b/tests/test_plugin_shipped_set.py @@ -203,7 +203,7 @@ def test_shipped_content_changed_reports_a_version_only_commit_as_unchanged(monk """End to end through the git seam, with git stubbed. The unit above proves the comparison; this proves it is actually WIRED to - the path that `check_version_bump` reads. A correct helper nobody calls + the path that `check_version_is_minted` reads. A correct helper nobody calls would leave the circular check exactly as it was. """ from scripts import check_plugin diff --git a/tests/test_write_path_trigger.py b/tests/test_write_path_trigger.py index f84b2cd..c2c47c1 100644 --- a/tests/test_write_path_trigger.py +++ b/tests/test_write_path_trigger.py @@ -867,14 +867,6 @@ def test_hook_skips_prose_and_data_files(): assert '/scribe_defs.sh"' in src # sourced, not copied -def test_plugin_version_bumped_with_the_hook(): - """The #1040 lesson: a plugin change clients can't see is a change that didn't - ship.""" - manifest = json.loads((PLUGIN / ".claude-plugin" / "plugin.json").read_text()) - version = tuple(int(p) for p in manifest["version"].split(".")) - assert version >= (0, 1, 31) - - def test_hook_keeps_sync_and_reuse_dedup_apart(): """#2708's dedup audit, pinned: the hook holds TWO per-session id files and feeds each its own class — sync ids (snippets recording the edited file) to