diff --git a/plugins/compound-engineering/skills/ce-update/SKILL.md b/plugins/compound-engineering/skills/ce-update/SKILL.md index 18c90de..2210309 100644 --- a/plugins/compound-engineering/skills/ce-update/SKILL.md +++ b/plugins/compound-engineering/skills/ce-update/SKILL.md @@ -17,21 +17,28 @@ version, and fix stale marketplace/cache state if it doesn't. Claude Code only. ## Pre-resolved context -The three sections below contain pre-resolved data. Only the **Plugin root -path** determines whether this session is Claude Code — if it contains an error +The sections below contain pre-resolved data. Only the **Plugin root path** +determines whether this session is Claude Code — if it contains an error sentinel, an empty value, or a literal `${CLAUDE_PLUGIN_ROOT}` string, tell the -user this skill only works in Claude Code and stop. The other two sections may +user this skill only works in Claude Code and stop. The other sections may contain error sentinels even in valid Claude Code sessions; the decision logic below handles those cases. +`CLAUDE_PLUGIN_ROOT` points at the currently-loaded plugin version directory +(e.g. `~/.claude/plugins/cache//compound-engineering/`), +so the plugin cache directory that holds every cached version is its parent. + **Plugin root path:** !`echo "${CLAUDE_PLUGIN_ROOT}" 2>/dev/null || echo '__CE_UPDATE_ROOT_FAILED__'` **Latest released version:** !`gh release list --repo EveryInc/compound-engineering-plugin --limit 30 --json tagName --jq '[.[] | select(.tagName | startswith("compound-engineering-v"))][0].tagName | sub("compound-engineering-v";"")' 2>/dev/null || echo '__CE_UPDATE_VERSION_FAILED__'` +**Plugin cache directory:** +!`case "$(dirname "${CLAUDE_PLUGIN_ROOT:-}")" in */cache/*/compound-engineering) dirname "${CLAUDE_PLUGIN_ROOT}" ;; *) echo '__CE_UPDATE_CACHE_FAILED__' ;; esac` + **Cached version folder(s):** -!`ls "${CLAUDE_PLUGIN_ROOT}/cache/compound-engineering-plugin/compound-engineering/" 2>/dev/null || echo '__CE_UPDATE_CACHE_FAILED__'` +!`case "$(dirname "${CLAUDE_PLUGIN_ROOT:-}")" in */cache/*/compound-engineering) ls "$(dirname "${CLAUDE_PLUGIN_ROOT}")" 2>/dev/null ;; *) echo '__CE_UPDATE_CACHE_FAILED__' ;; esac` ## Decision logic @@ -56,12 +63,12 @@ Take the **latest released version** and the **cached folder list**. - Tell the user: "compound-engineering **v{version}** is installed and up to date." **Out of date or corrupted** — multiple cached folders exist, OR the single folder name -does not match the latest version. Use the **Plugin root path** value from above to -construct the delete path. +does not match the latest version. Use the **Plugin cache directory** value from above +as the delete path. **Clear the stale cache:** ```bash -rm -rf "/cache/compound-engineering-plugin/compound-engineering" +rm -rf "" ``` Tell the user: diff --git a/tests/skills/ce-update.test.ts b/tests/skills/ce-update.test.ts new file mode 100644 index 0000000..179713e --- /dev/null +++ b/tests/skills/ce-update.test.ts @@ -0,0 +1,32 @@ +import { readFileSync } from "fs" +import path from "path" +import { describe, expect, test } from "bun:test" + +// Regression guard for https://github.com/EveryInc/compound-engineering-plugin/issues/556. +// +// `CLAUDE_PLUGIN_ROOT` is set by the Claude Code harness to the currently-loaded +// plugin version directory (e.g. `~/.claude/plugins/cache//compound-engineering/`). +// It is NOT the plugins cache root. Appending `/cache//compound-engineering/` +// onto it produces a path that never exists, which causes the cache-probe to fail +// and emit the `__CE_UPDATE_CACHE_FAILED__` sentinel on every healthy install. +// +// This has regressed twice. Fail fast if the antipattern reappears. + +describe("ce-update SKILL.md", () => { + const skillPath = path.join( + process.cwd(), + "plugins/compound-engineering/skills/ce-update/SKILL.md", + ) + const body = readFileSync(skillPath, "utf8") + + test("does not append a /cache// suffix onto CLAUDE_PLUGIN_ROOT", () => { + // Matches any `${CLAUDE_PLUGIN_ROOT}/cache//` or `${CLAUDE_PLUGIN_ROOT}/cache/"` + // pattern. The harness variable is already under `cache//`, + // so concatenating another `cache/...` segment is always wrong. + const antiPattern = /\$\{CLAUDE_PLUGIN_ROOT\}\/cache\// + expect( + antiPattern.test(body), + "ce-update/SKILL.md reintroduced the ${CLAUDE_PLUGIN_ROOT}/cache/... antipattern — derive the cache dir from dirname \"${CLAUDE_PLUGIN_ROOT}\" instead.", + ).toBe(false) + }) +})