mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-25 14:00:40 +00:00
fix(plugin): drop the pinned version so installs follow the commit (BUG-3463) (#1848)
A version in plugin.json pins every installed copy until the string changes (code.claude.com/docs/en/plugins/loading, "Versions and updates"). 0.3.3 stood from 2026-08-27 while 8 commits changed plugin/, so marketplace installs kept the August skills. With no version, a relative-path plugin in a git-hosted marketplace is versioned by its commit, and `claude plugin update` picks up every change. Measured in a scratch CLAUDE_CONFIG_DIR against a git marketplace with a commit to plugin/ after install: pinned 0.3.3 answered "already at the latest version (0.3.3)" and kept the old copy; unpinned updated be0122112e5a -> d6312f1d8c6d with the change in the cache. A guard test refuses a version in plugin.json or the marketplace entry. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// BUG-3463: the Claude Code plugin must not pin a version. A `version` in
|
||||
// plugin.json (or in its marketplace entry) pins every installed copy until
|
||||
// the string changes, however many commits touch plugin/; with none, a
|
||||
// relative-path plugin in a git-hosted marketplace is versioned by its
|
||||
// commit, so `/plugin update` picks up every change. 0.3.3 stood from
|
||||
// 2026-08-27 while 8 commits changed plugin/ under it.
|
||||
// https://code.claude.com/docs/en/plugins/loading#versions-and-updates
|
||||
|
||||
const (
|
||||
claudePluginManifest = "../../plugin/.claude-plugin/plugin.json"
|
||||
claudeMarketplace = "../../.claude-plugin/marketplace.json"
|
||||
)
|
||||
|
||||
func pinnedVersion(t *testing.T, path string, entries func(map[string]any) []map[string]any) []string {
|
||||
t.Helper()
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", path, err)
|
||||
}
|
||||
var doc map[string]any
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
t.Fatalf("%s: %v", path, err)
|
||||
}
|
||||
var pinned []string
|
||||
for _, e := range entries(doc) {
|
||||
if v, ok := e["version"]; ok {
|
||||
pinned = append(pinned, path+": "+jsonString(v))
|
||||
}
|
||||
}
|
||||
return pinned
|
||||
}
|
||||
|
||||
func jsonString(v any) string {
|
||||
b, _ := json.Marshal(v)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func manifestEntries(doc map[string]any) []map[string]any { return []map[string]any{doc} }
|
||||
|
||||
func marketplaceEntries(doc map[string]any) []map[string]any {
|
||||
var out []map[string]any
|
||||
plugins, _ := doc["plugins"].([]any)
|
||||
for _, p := range plugins {
|
||||
if m, ok := p.(map[string]any); ok {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestClaudePlugin_NoPinnedVersion(t *testing.T) {
|
||||
pinned := pinnedVersion(t, claudePluginManifest, manifestEntries)
|
||||
pinned = append(pinned, pinnedVersion(t, claudeMarketplace, marketplaceEntries)...)
|
||||
for _, p := range pinned {
|
||||
t.Errorf("plugin version pinned at %s: installed copies stop updating until it changes; remove it (BUG-3463)", p)
|
||||
}
|
||||
// Control: the marketplace walk must reach the pad entry, or an
|
||||
// entry gaining a version would pass unseen.
|
||||
b, _ := os.ReadFile(claudeMarketplace)
|
||||
var doc map[string]any
|
||||
_ = json.Unmarshal(b, &doc)
|
||||
if n := len(marketplaceEntries(doc)); n == 0 {
|
||||
t.Fatal("marketplace.json lists no plugins; the guard is reading the wrong file")
|
||||
}
|
||||
}
|
||||
@@ -720,6 +720,8 @@ func watchNotificationVisible(watches map[string]string, vis watchAccessVisibili
|
||||
// traffic until PLAN-2558 S6 (TASK-2591) reworded it and bumped the
|
||||
// plugin version — installed plugins are version-pinned at install
|
||||
// (live-proven day-33), so the bump is what made the fix reach users.
|
||||
// The manifest no longer carries a version (BUG-3463), so an update
|
||||
// now follows the commit that changed plugin/.
|
||||
|
||||
// Push (IDEA-2544 Phase 1) is semantically DIFFERENT from assignment,
|
||||
// not just a same-shaped variant, and that difference is why this
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "pad",
|
||||
"version": "0.3.3",
|
||||
"description": "Talk to your project. Pad is local-first project management for developers and AI agents — this plugin gives Claude Code a conversational Pad skill, typed shortcuts (/pad:status, /pad:capture, /pad:onboard, /pad:connect, /pad:disconnect), and consent-gated live item-change notifications.",
|
||||
"author": {
|
||||
"name": "Perpetual Software LLC",
|
||||
|
||||
Reference in New Issue
Block a user