mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
fix: clean stale plugin command files from the installed plugin checkout (#817)
The installer already manages hooks.json inside ~/.claude/plugins/marketplaces/plannotator/apps/hook/, so the earlier "don't reach into plugin storage" rationale for leaving the old namespaced plannotator:* command files there was inconsistent. All three installers now remove them — same replacement-skill guard as the bare ~/.claude/commands cleanup — making the #817 duplicate menu entries die on a single installer run + restart instead of waiting for /plugin marketplace update. Hints/docs reworded accordingly.
This commit is contained in:
@@ -113,7 +113,7 @@ claude --plugin-dir ./apps/hook
|
||||
|
||||
Plannotator's slash commands (`/plannotator-review`, `/plannotator-annotate`, `/plannotator-last`, `/plannotator-archive`) are installed as Claude Code skills in `~/.claude/skills` by the install script — Claude Code skills are user-invocable by directory name, so the command names are unchanged. There is no separate `~/.claude/commands` step.
|
||||
|
||||
Upgrading from an older version? The installer removes the legacy `~/.claude/commands/plannotator-*.md` files automatically, but the marketplace plugin's old namespaced `plannotator:*` command entries are managed by Claude Code — run `/plugin marketplace update` once so they disappear from the `/` menu.
|
||||
Upgrading from an older version? The installer removes the legacy `~/.claude/commands/plannotator-*.md` files **and** the old namespaced `plannotator:*` command files from the installed plugin automatically (both only once the replacement skills are on disk). Restart Claude Code afterward, and run `/plugin marketplace update` when convenient to pull the latest hooks-only plugin.
|
||||
|
||||
Optional extra skills (compound planning, setup-goal, visual explainer) are not installed by default. Add them with:
|
||||
|
||||
|
||||
+21
-5
@@ -783,12 +783,28 @@ REM Claude Code commands are deprecated in favor of skills. Remove a legacy
|
||||
REM command file only once its replacement skill is actually on disk — running
|
||||
REM AFTER the install above guarantees a failed or skipped skill install never
|
||||
REM leaves users with neither the command nor the skill.
|
||||
REM Older marketplace plugin versions shipped their own commands dir (the
|
||||
REM namespaced plannotator:* entries); newer plugin versions are hooks-only.
|
||||
REM We already manage hooks.json inside this same checkout, so remove the
|
||||
REM stale plugin command files too — same replacement-skill guard.
|
||||
if defined CLAUDE_CONFIG_DIR (
|
||||
set "PLUGIN_COMMANDS_DIR=%CLAUDE_CONFIG_DIR%\plugins\marketplaces\plannotator\apps\hook\commands"
|
||||
) else (
|
||||
set "PLUGIN_COMMANDS_DIR=%USERPROFILE%\.claude\plugins\marketplaces\plannotator\apps\hook\commands"
|
||||
)
|
||||
for %%C in (plannotator-review plannotator-annotate plannotator-last plannotator-archive) do (
|
||||
if exist "!CLAUDE_SKILLS_DIR!\%%C" if exist "!CLAUDE_COMMANDS_DIR!\%%C.md" (
|
||||
del /q "!CLAUDE_COMMANDS_DIR!\%%C.md" >nul 2>&1
|
||||
echo Removed deprecated Claude command !CLAUDE_COMMANDS_DIR!\%%C.md ^(replaced by the %%C skill^)
|
||||
if exist "!CLAUDE_SKILLS_DIR!\%%C" (
|
||||
if exist "!CLAUDE_COMMANDS_DIR!\%%C.md" (
|
||||
del /q "!CLAUDE_COMMANDS_DIR!\%%C.md" >nul 2>&1
|
||||
echo Removed deprecated Claude command !CLAUDE_COMMANDS_DIR!\%%C.md ^(replaced by the %%C skill^)
|
||||
)
|
||||
if exist "!PLUGIN_COMMANDS_DIR!\%%C.md" (
|
||||
del /q "!PLUGIN_COMMANDS_DIR!\%%C.md" >nul 2>&1
|
||||
echo Removed stale plugin command !PLUGIN_COMMANDS_DIR!\%%C.md ^(plugin is hooks-only now^)
|
||||
)
|
||||
)
|
||||
)
|
||||
rmdir "!PLUGIN_COMMANDS_DIR!" >nul 2>&1
|
||||
|
||||
REM Codex no longer hosts core skills (they live in %%USERPROFILE%%\.agents\skills).
|
||||
REM Core skills are removed only once their replacement exists; the stale
|
||||
@@ -933,8 +949,8 @@ echo Then install the Claude Code plugin:
|
||||
echo /plugin marketplace add backnotprop/plannotator
|
||||
echo /plugin install plannotator@plannotator
|
||||
echo.
|
||||
echo Upgrading from an older version? Also run /plugin marketplace update
|
||||
echo so the plugin drops its old plannotator:* command entries.
|
||||
echo Upgrading from an older version? Run /plugin marketplace update to pull
|
||||
echo the latest hooks-only plugin ^(stale plugin commands are cleaned up here^).
|
||||
echo.
|
||||
echo The /plannotator-review, /plannotator-annotate, /plannotator-last, and /plannotator-archive skills are ready to use!
|
||||
if not "!EXTRAS_CHOICE!"=="yes" (
|
||||
|
||||
+22
-6
@@ -727,14 +727,30 @@ if ($checkoutFailed) {
|
||||
# command file only once its replacement skill is actually on disk — running
|
||||
# AFTER the install above guarantees a failed or skipped skill install never
|
||||
# leaves users with neither the command nor the skill.
|
||||
# Older marketplace plugin versions shipped their own commands/ dir (the
|
||||
# namespaced plannotator:* entries); newer plugin versions are hooks-only.
|
||||
# We already manage hooks.json inside this same checkout, so remove the stale
|
||||
# plugin command files too — same replacement-skill guard as the bare ones.
|
||||
$claudeConfigRoot = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { "$env:USERPROFILE\.claude" }
|
||||
$pluginCommandsDir = Join-Path $claudeConfigRoot "plugins\marketplaces\plannotator\apps\hook\commands"
|
||||
foreach ($cmd in @("plannotator-review", "plannotator-annotate", "plannotator-last", "plannotator-archive")) {
|
||||
$cmdPath = Join-Path $claudeCommandsDir "$cmd.md"
|
||||
$skillPath = Join-Path $claudeSkillsDir $cmd
|
||||
if ((Test-Path $skillPath) -and (Test-Path $cmdPath)) {
|
||||
Write-Host "Removing stale Claude command $cmdPath (replaced by the $cmd skill)"
|
||||
Remove-Item -Force $cmdPath -ErrorAction SilentlyContinue
|
||||
if (Test-Path $skillPath) {
|
||||
$cmdPath = Join-Path $claudeCommandsDir "$cmd.md"
|
||||
if (Test-Path $cmdPath) {
|
||||
Write-Host "Removing stale Claude command $cmdPath (replaced by the $cmd skill)"
|
||||
Remove-Item -Force $cmdPath -ErrorAction SilentlyContinue
|
||||
}
|
||||
$pluginCmdPath = Join-Path $pluginCommandsDir "$cmd.md"
|
||||
if (Test-Path $pluginCmdPath) {
|
||||
Write-Host "Removing stale plugin command $pluginCmdPath (plugin is hooks-only now)"
|
||||
Remove-Item -Force $pluginCmdPath -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((Test-Path $pluginCommandsDir) -and -not (Get-ChildItem $pluginCommandsDir -ErrorAction SilentlyContinue)) {
|
||||
Remove-Item -Force $pluginCommandsDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Codex no longer hosts core skills (they now live in ~/.agents/skills).
|
||||
# Core skills are removed only once their replacement exists; the stale
|
||||
@@ -894,8 +910,8 @@ Write-Host "Install the Claude Code plugin:"
|
||||
Write-Host " /plugin marketplace add backnotprop/plannotator"
|
||||
Write-Host " /plugin install plannotator@plannotator"
|
||||
Write-Host ""
|
||||
Write-Host "Upgrading from an older version? Also run /plugin marketplace update"
|
||||
Write-Host "so the plugin drops its old plannotator:* command entries."
|
||||
Write-Host "Upgrading from an older version? Run /plugin marketplace update to pull"
|
||||
Write-Host "the latest hooks-only plugin (stale plugin commands are cleaned up here)."
|
||||
Write-Host ""
|
||||
Write-Host "The /plannotator-review, /plannotator-annotate, /plannotator-last, and /plannotator-archive commands are ready to use after you restart Claude Code!"
|
||||
|
||||
|
||||
+17
-5
@@ -1030,12 +1030,24 @@ fi
|
||||
# AFTER the install above guarantees a failed or skipped skill install never
|
||||
# leaves users with neither the command nor the skill.
|
||||
CLAUDE_COMMANDS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/commands"
|
||||
# Older marketplace plugin versions shipped their own commands/ dir (the
|
||||
# namespaced plannotator:* entries); newer plugin versions are hooks-only.
|
||||
# We already manage hooks.json inside this same checkout, so remove the stale
|
||||
# plugin command files too — same replacement-skill guard as the bare ones.
|
||||
PLUGIN_COMMANDS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/plugins/marketplaces/plannotator/apps/hook/commands"
|
||||
for cmd in plannotator-review plannotator-annotate plannotator-last plannotator-archive; do
|
||||
if [ -d "$CLAUDE_SKILLS_DIR/$cmd" ] && [ -f "$CLAUDE_COMMANDS_DIR/$cmd.md" ]; then
|
||||
rm -f "$CLAUDE_COMMANDS_DIR/$cmd.md"
|
||||
echo "Removed legacy Claude command ${CLAUDE_COMMANDS_DIR}/$cmd.md (replaced by the $cmd skill)"
|
||||
if [ -d "$CLAUDE_SKILLS_DIR/$cmd" ]; then
|
||||
if [ -f "$CLAUDE_COMMANDS_DIR/$cmd.md" ]; then
|
||||
rm -f "$CLAUDE_COMMANDS_DIR/$cmd.md"
|
||||
echo "Removed legacy Claude command ${CLAUDE_COMMANDS_DIR}/$cmd.md (replaced by the $cmd skill)"
|
||||
fi
|
||||
if [ -f "$PLUGIN_COMMANDS_DIR/$cmd.md" ]; then
|
||||
rm -f "$PLUGIN_COMMANDS_DIR/$cmd.md"
|
||||
echo "Removed stale plugin command ${PLUGIN_COMMANDS_DIR}/$cmd.md (plugin is hooks-only now)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
rmdir "$PLUGIN_COMMANDS_DIR" 2>/dev/null || true
|
||||
|
||||
# Codex no longer hosts core skills (they now live in ~/.agents/skills).
|
||||
# Core skills are removed only once their replacement exists; the stale
|
||||
@@ -1221,8 +1233,8 @@ echo "Install the Claude Code plugin:"
|
||||
echo " /plugin marketplace add backnotprop/plannotator"
|
||||
echo " /plugin install plannotator@plannotator"
|
||||
echo ""
|
||||
echo "Upgrading from an older version? Also run /plugin marketplace update"
|
||||
echo "so the plugin drops its old plannotator:* command entries."
|
||||
echo "Upgrading from an older version? Run /plugin marketplace update to pull"
|
||||
echo "the latest hooks-only plugin (stale plugin commands are cleaned up here)."
|
||||
echo ""
|
||||
echo "The /plannotator-review, /plannotator-annotate, /plannotator-last, and /plannotator-archive commands are ready to use after you restart Claude Code!"
|
||||
|
||||
|
||||
+27
-3
@@ -98,7 +98,11 @@ describe("install.sh", () => {
|
||||
// A command file may only be removed once its same-name skill exists on
|
||||
// disk, and the cleanup must run AFTER the skill install — so a failed
|
||||
// fetch or an old pinned tag never deletes commands without replacement.
|
||||
expect(script).toContain('if [ -d "$CLAUDE_SKILLS_DIR/$cmd" ] && [ -f "$CLAUDE_COMMANDS_DIR/$cmd.md" ]');
|
||||
// Outer guard: the replacement skill must exist before any command file
|
||||
// (bare or plugin-checkout) is removed.
|
||||
expect(script).toContain('if [ -d "$CLAUDE_SKILLS_DIR/$cmd" ]');
|
||||
expect(script).toContain('if [ -f "$CLAUDE_COMMANDS_DIR/$cmd.md" ]');
|
||||
expect(script).toContain('if [ -f "$PLUGIN_COMMANDS_DIR/$cmd.md" ]');
|
||||
const cleanupIndex = script.indexOf('Removed legacy Claude command');
|
||||
const installIndex = script.indexOf('copy_skill_if_present apps/skills/core/plannotator-review');
|
||||
expect(installIndex).toBeGreaterThan(0);
|
||||
@@ -370,7 +374,11 @@ describe("install.ps1", () => {
|
||||
expect(script).toContain("claudeCommandsDir");
|
||||
// Command cleanup is guarded on the replacement skill existing and runs
|
||||
// after the skill install (parity with install.sh).
|
||||
expect(script).toContain("(Test-Path $skillPath) -and (Test-Path $cmdPath)");
|
||||
// Outer guard: the replacement skill must exist before any command file
|
||||
// (bare or plugin-checkout) is removed.
|
||||
expect(script).toContain("if (Test-Path $skillPath) {");
|
||||
expect(script).toContain("if (Test-Path $cmdPath) {");
|
||||
expect(script).toContain("if (Test-Path $pluginCmdPath) {");
|
||||
// Legacy ~/.agents review/annotate/last cleanup is gone.
|
||||
expect(script).not.toContain("legacyAgentsSkillsDir");
|
||||
// Codex cleanup includes the per-command skills now.
|
||||
@@ -488,7 +496,11 @@ describe("install.cmd", () => {
|
||||
expect(script).toContain("CLAUDE_COMMANDS_DIR");
|
||||
// Command cleanup is guarded on the replacement skill existing and runs
|
||||
// after the skill install (parity with install.sh / install.ps1).
|
||||
expect(script).toContain('if exist "!CLAUDE_SKILLS_DIR!\\%%C" if exist "!CLAUDE_COMMANDS_DIR!\\%%C.md"');
|
||||
// Outer guard: the replacement skill must exist before any command file
|
||||
// (bare or plugin-checkout) is removed.
|
||||
expect(script).toContain('if exist "!CLAUDE_SKILLS_DIR!\\%%C" (');
|
||||
expect(script).toContain('if exist "!CLAUDE_COMMANDS_DIR!\\%%C.md"');
|
||||
expect(script).toContain('if exist "!PLUGIN_COMMANDS_DIR!\\%%C.md"');
|
||||
// Legacy ~/.agents review/annotate/last cleanup is gone.
|
||||
expect(script).not.toContain("LEGACY_AGENTS_SKILLS_DIR");
|
||||
// Codex cleanup includes the per-command skills now.
|
||||
@@ -648,6 +660,18 @@ describe("install shared behavior", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("stale plugin command files are cleaned from the plugin checkout", () => {
|
||||
// The installer already manages hooks.json inside the installed plugin
|
||||
// checkout, so it removes the old namespaced plannotator:* command files
|
||||
// there too — same replacement-skill guard as the bare commands. This is
|
||||
// what lets #817's duplicate menu entries die on a single installer run
|
||||
// instead of waiting for /plugin marketplace update.
|
||||
const cmdScript = readFileSync(join(scriptsDir, "install.cmd"), "utf-8");
|
||||
expect(sh).toContain("plugins/marketplaces/plannotator/apps/hook/commands");
|
||||
expect(ps).toContain("plugins\\marketplaces\\plannotator\\apps\\hook\\commands");
|
||||
expect(cmdScript).toContain("plugins\\marketplaces\\plannotator\\apps\\hook\\commands");
|
||||
});
|
||||
|
||||
test("all installers respect CODEX_HOME for the Codex home directory", () => {
|
||||
// Codex stores config and state under $CODEX_HOME when set, falling back
|
||||
// to ~/.codex (developers.openai.com/codex/config-advanced). #852
|
||||
|
||||
Reference in New Issue
Block a user