refactor(plugin): retire what the hand-bump scheme left behind — the README that taught it, the floor test, the stale rationale (#3328)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / integration (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 16s

#3127 checklist 19. The step's own deletion list turned out to be largely
spent: `check_version_bump()` came out with #3327, and the machinery the step
expected to delete alongside it is load-bearing for its replacement.

`manifest_version(ref=…)`, `--base`, `--no-version` and the `origin/main`
resolve path all STAY. Derivation makes the value right; it does not make the
comparison unnecessary. `check_version_is_minted` still has to ask "did the
version move when the shipped content did?", and that is a base-branch
question no matter who chose the number. The step was planned before #3327
landed, when the assumption was that these died with the guard.

What was actually still standing, all of it teaching or asserting the retired
scheme:

- `plugin/README.md` told the reader to "set a `version` bump per release."
  A shipped file, instructing the exact act the mint replaced — this is how a
  deleted control gets re-added by someone following the docs. Now says not to
  hand-edit the field, names `make mint-plugin`, and says what a forgotten
  mint costs. (`make` is not installed on every workstation, so the direct
  script invocation is given too.)
- `test_plugin_version_bumped_with_the_hook` asserted `version >= (0,1,31)`
  as a tuple of ints. Under a minted value it passes vacuously — every date
  clears a floor of 0.1.31 — and `int("0415")` silently eats the padding the
  format exists to keep. Superseded by
  `test_the_shipped_manifest_carries_a_minted_version`, which asserts the
  canonical shape instead of an ordering the comparator does not perform.
  Removed whole (rule 22).
- The module preamble still ended on "a written rule that depends on being
  remembered is not a control; this is" — true of the bump guard, and read as
  a stronger claim than the mint can support. Replaced with what the change
  did and did not remove: choosing a number is gone, running the mint is not,
  and the difference is that forgetting is now loud rather than silent.
- An orphaned `# --- the version bump ---` section header with nothing under
  it, and a test docstring still naming `check_version_bump`.

`--no-version` keeps its one legitimate case — on `main` the version is
measured against itself — and now says so in both the usage block and its
`--help`, so it does not read as an escape hatch. `check_session_context_
reports_its_version` stays untouched: a different check with a different job,
and the only thing that makes step 6 readable from a transcript (#2220).

Version minted 2026.09.02.0415 -> 2026.09.02.0438 for the README change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcCs1CcQ1ormdnzSshKqvN
This commit is contained in:
2026-09-02 00:38:50 -04:00
co-authored by Claude Opus 5
parent 64cb719a12
commit f5a3643da8
5 changed files with 34 additions and 20 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "scribe", "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.", "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": { "author": {
"name": "Bryan Van Deusen" "name": "Bryan Van Deusen"
}, },
+7 -2
View File
@@ -78,8 +78,13 @@ On install you'll be asked for:
## Notes ## Notes
- Set a `version` bump in `.claude-plugin/plugin.json` per release so clients - **Do not hand-edit `version` in `.claude-plugin/plugin.json`.** It is minted
pick up changes. 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 - 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 key; the MCP tools need **write** scope to create/update. Every hook is a GET
for that reason — a read key cannot POST. for that reason — a read key cannot POST.
+25 -8
View File
@@ -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 install, because `plugin.json`'s version wasn't bumped and the installer
compares versions to decide whether to refresh its cache. compares versions to decide whether to refresh its cache.
The rule for the second one was already written down and was still missed. A Both were fixed. The second was fixed TWICE — once by bumping the number, and
written rule that depends on being remembered during a long session is not a then properly, by removing the class it came from: `plugin.json`'s version is
control; this 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 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 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. whole file exists to prevent.
Usage: Usage:
python3 scripts/check_plugin.py # all checks python3 scripts/check_plugin.py # all checks
python3 scripts/check_plugin.py --no-version # skip the version check 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 from __future__ import annotations
@@ -217,8 +235,6 @@ def check_patterns() -> None:
ok(f"{rel}: no known-bad patterns") ok(f"{rel}: no known-bad patterns")
# --- the version bump ------------------------------------------------------
# --- shellcheck ------------------------------------------------------------ # --- shellcheck ------------------------------------------------------------
def check_shellcheck() -> None: def check_shellcheck() -> None:
@@ -668,7 +684,8 @@ def check_version_is_minted(base: str = "origin/main") -> None:
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--no-version", action="store_true", 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", parser.add_argument("--base", default="origin/main",
help="branch the version is measured against") help="branch the version is measured against")
args = parser.parse_args() args = parser.parse_args()
+1 -1
View File
@@ -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. """End to end through the git seam, with git stubbed.
The unit above proves the comparison; this proves it is actually WIRED to 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. would leave the circular check exactly as it was.
""" """
from scripts import check_plugin from scripts import check_plugin
-8
View File
@@ -867,14 +867,6 @@ def test_hook_skips_prose_and_data_files():
assert '/scribe_defs.sh"' in src # sourced, not copied 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(): def test_hook_keeps_sync_and_reuse_dedup_apart():
"""#2708's dedup audit, pinned: the hook holds TWO per-session id files and """#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 feeds each its own class — sync ids (snippets recording the edited file) to