Problem
The Claude Code slash command definitions (plannotator-review.md, plannotator-annotate.md, plannotator-last.md) exist in four places in the repository:
- Canonical source:
apps/hook/commands/*.md — used by the Claude Code plugin install path.
- Duplicated inline in
scripts/install.sh — hand-typed heredoc blocks, lines ~313-360.
- Duplicated inline in
scripts/install.ps1 — hand-typed here-string blocks, lines ~225-275.
- Duplicated inline in
scripts/install.cmd — hand-typed echo blocks, lines ~326-375.
The three installer copies must be kept in sync manually with the canonical version, and there is no CI check that enforces this. If someone edits apps/hook/commands/plannotator-review.md (to adjust the prompt, add a flag, update the description), the three installer copies silently continue shipping the old content on every subsequent install. The drift is invisible until a user reports behavior mismatch between plugin-installed and script-installed plannotator.
Visible symptom that exposed the latent duplication
PR #512 found that install.cmd was writing the Claude Code slash command files with the ! shell-invocation prefix stripped — a cmd.exe enabledelayedexpansion parser quirk — which made the files silently no-op when invoked in Claude Code on Windows cmd. The three-character echo ^! fix shipped in #512 closes the visible Windows bug but doesn't address the underlying duplication. The same class of bug could recur anywhere any of the three installers touch these inline copies.
Proposed approach
Replace the three installers' inline slash command definitions with a fetch from the canonical apps/hook/commands/ directory at install time. Two feasible paths:
- Reuse the existing
git clone --depth 1 --sparse mechanism. The installers already sparse-checkout apps/skills/ for the skills install step. Expand the sparse pattern to also include apps/hook/commands/, then copy those files to \$CLAUDE_COMMANDS_DIR/. Requires git on the user's machine (already the case for the skills path; other code paths gracefully skip).
- Fetch from
raw.githubusercontent.com at the installed tag. The installer knows \$latest_tag at this point and can curl -fsSL https://raw.githubusercontent.com/backnotprop/plannotator/<tag>/apps/hook/commands/<file>.md -o ... per command file. Adds network calls but works without git.
Approach 1 is preferred because it reuses existing infrastructure and doesn't add a new authenticated fetch surface.
CI changes required
Add an assertion to the Windows integration job (.github/workflows/test.yml → install-cmd-windows job) and equivalent checks for the other installers if possible:
- After install, read back each generated
.md file from \$CLAUDE_COMMANDS_DIR/ and compare it byte-for-byte against apps/hook/commands/<same-file> from the checked-out repo. Any drift fails CI.
This is stronger than the cycle-6 fix in PR #512, which only asserts each file contains the ! prefix — a byte-equality check catches every future class of divergence, not just the one we've seen.
Acceptance criteria
Out of scope
- Adopting fixture-file architecture for the Gemini settings merge (separate concern, different fragility).
- Migrating the skills install path itself to a different mechanism.
- Changes to the Claude Code plugin install path (which already uses the canonical
apps/hook/commands/ files correctly).
Related
Problem
The Claude Code slash command definitions (
plannotator-review.md,plannotator-annotate.md,plannotator-last.md) exist in four places in the repository:apps/hook/commands/*.md— used by the Claude Code plugin install path.scripts/install.sh— hand-typed heredoc blocks, lines ~313-360.scripts/install.ps1— hand-typed here-string blocks, lines ~225-275.scripts/install.cmd— hand-typedechoblocks, lines ~326-375.The three installer copies must be kept in sync manually with the canonical version, and there is no CI check that enforces this. If someone edits
apps/hook/commands/plannotator-review.md(to adjust the prompt, add a flag, update the description), the three installer copies silently continue shipping the old content on every subsequent install. The drift is invisible until a user reports behavior mismatch between plugin-installed and script-installed plannotator.Visible symptom that exposed the latent duplication
PR #512 found that
install.cmdwas writing the Claude Code slash command files with the!shell-invocation prefix stripped — a cmd.exeenabledelayedexpansionparser quirk — which made the files silently no-op when invoked in Claude Code on Windows cmd. The three-characterecho ^!fix shipped in #512 closes the visible Windows bug but doesn't address the underlying duplication. The same class of bug could recur anywhere any of the three installers touch these inline copies.Proposed approach
Replace the three installers' inline slash command definitions with a fetch from the canonical
apps/hook/commands/directory at install time. Two feasible paths:git clone --depth 1 --sparsemechanism. The installers already sparse-checkoutapps/skills/for the skills install step. Expand the sparse pattern to also includeapps/hook/commands/, then copy those files to\$CLAUDE_COMMANDS_DIR/. Requiresgiton the user's machine (already the case for the skills path; other code paths gracefully skip).raw.githubusercontent.comat the installed tag. The installer knows\$latest_tagat this point and cancurl -fsSL https://raw.githubusercontent.com/backnotprop/plannotator/<tag>/apps/hook/commands/<file>.md -o ...per command file. Adds network calls but works withoutgit.Approach 1 is preferred because it reuses existing infrastructure and doesn't add a new authenticated fetch surface.
CI changes required
Add an assertion to the Windows integration job (
.github/workflows/test.yml→install-cmd-windowsjob) and equivalent checks for the other installers if possible:.mdfile from\$CLAUDE_COMMANDS_DIR/and compare it byte-for-byte againstapps/hook/commands/<same-file>from the checked-out repo. Any drift fails CI.This is stronger than the cycle-6 fix in PR #512, which only asserts each file contains the
!prefix — a byte-equality check catches every future class of divergence, not just the one we've seen.Acceptance criteria
apps/hook/commands/*.mdis the single source of truth for Claude Code slash command definitions.scripts/install.sh,scripts/install.ps1, andscripts/install.cmdfetch (not inline-duplicate) these files during the Claude Code slash command install step.apps/hook/commands/*.mdon at least the Windows cmd integration path.Out of scope
apps/hook/commands/files correctly).Related