plugins/commventional/skills/install-trailer-stripper/SKILL.md
Install consumer-side wirings (Claude Code hooks or .git/hooks/commit-msg) that invoke commventional's trailer-stripping capability. Per ADR-006 §1, the consumer types this slash command — that is the directing act that authorises the write.
npx skillsauth add acostanzo/quickstop install-trailer-stripperInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Install helper that writes a consumer-side wiring of commventional's trailer-stripping capability into the consumer's chosen surface — Claude Code settings, the consumer's git-hooks tree, or both. The consumer typing this slash command is the directing act that authorises the write per ADR-006 §1; the plugin install itself does not perform any wiring.
Parse $ARGUMENTS for:
--target <choice> — required. One of:
claude-settings-user — write a PreToolUse hook to ~/.claude/settings.json. Restores v1.x's enforce-ownership.sh scope (every Claude Bash call, globally).claude-settings-project — write the same PreToolUse hook to <repo>/.claude/settings.json (per-repo scope).git-commit-msg-hook — write a commit-msg script to <repo>/.git/hooks/commit-msg. Catches manual commits too — broader than v1.x's behaviour.pr-cleanup-hook-user — write a PostToolUse hook to ~/.claude/settings.json that calls :strip-pr-body on gh pr (create|edit) Bash calls. Restores v1.x's pr-ownership-check.sh scope (globally).pr-cleanup-hook-project — same, in project-scoped settings.all — write both Pre and Post entries into the consumer's chosen settings scope. Ask which scope.--force — optional. Without it, the helper refuses to overwrite any existing target file. With it, the helper still asks for an explicit confirmation per file before overwriting.
If --target is missing or unrecognised, use AskUserQuestion to ask the user to pick one of the choices above. List all six explicitly. Do not silently default.
Resolve the absolute target path(s) by --target. Use bash to expand ~ and locate the repo root via git rev-parse --show-toplevel.
| Target | Files written |
|---|---|
| claude-settings-user | ~/.claude/scripts/commventional-enforce-ownership.sh + merge into ~/.claude/settings.json |
| claude-settings-project | <repo>/.claude/scripts/commventional-enforce-ownership.sh + merge into <repo>/.claude/settings.json |
| git-commit-msg-hook | <repo>/.git/hooks/commit-msg |
| pr-cleanup-hook-user | ~/.claude/scripts/commventional-pr-cleanup.sh + merge into ~/.claude/settings.json |
| pr-cleanup-hook-project | <repo>/.claude/scripts/commventional-pr-cleanup.sh + merge into <repo>/.claude/settings.json |
| all | Both scripts above + both entries merged into the chosen settings.json (ask scope) |
For project-scoped targets, abort with a diagnostic if git rev-parse --show-toplevel fails — there is no repo to write into.
For each target file:
--force is not set, abort with a diagnostic: <path> exists. Re-run with --force to overwrite (you will still be asked to confirm before each write). Do not write.--force is set, use AskUserQuestion to confirm overwrite for that specific path. Refuse on a "no" answer.For settings.json paths, "exists" means the file exists. Merging into an existing settings.json is not an overwrite — see Phase 3 below for the merge rules. The --force gate applies to the standalone hook scripts (under scripts/) and to commit-msg, not to in-place JSON merges.
Use AskUserQuestion to show the user:
On "no", abort cleanly. On "yes", proceed.
enforce-ownership shape)Mirrors v1.x hooks/enforce-ownership.sh. Write this verbatim to the resolved commventional-enforce-ownership.sh path; chmod +x.
#!/usr/bin/env bash
# commventional-enforce-ownership.sh
# PreToolUse hook for Bash — strips Co-Authored-By trailers and "Generated
# with/by Claude" footers from git commit / gh pr create command payloads.
# Installed by /commventional:install-trailer-stripper.
set -euo pipefail
if ! command -v jq >/dev/null 2>&1; then
exit 0
fi
INPUT=$(cat)
COMMAND=$(jq -r '.tool_input.command // empty' <<< "$INPUT")
if ! printf '%s\n' "$COMMAND" | grep -qE '(git commit|gh pr create)'; then
exit 0
fi
CLEANED=$(printf '%s\n' "$COMMAND" | perl -pe '
s/[Cc]o-[Aa]uthored-[Bb]y:[^"\x27\\]*//g;
s/.*[Gg]enerated (?:with|by).*[Cc]laude[^"\x27\\]*//g;
')
CLEANED=$(printf '%s\n' "$CLEANED" | cat -s)
if [ "$COMMAND" = "$CLEANED" ]; then
exit 0
fi
jq -n --arg cmd "$CLEANED" '{
"hookSpecificOutput": {
"updatedInput": {
"command": $cmd
}
}
}'
pr-cleanup shape)Mirrors v1.x hooks/pr-ownership-check.sh. Write this verbatim to the resolved commventional-pr-cleanup.sh path; chmod +x.
#!/usr/bin/env bash
# commventional-pr-cleanup.sh
# PostToolUse hook for Bash — strips Co-Authored-By trailers and "Generated
# with/by Claude" footers from PR bodies after gh pr create / edit.
# Installed by /commventional:install-trailer-stripper.
set -euo pipefail
if ! command -v jq >/dev/null 2>&1; then
exit 0
fi
INPUT=$(cat)
if ! printf '%s\n' "$INPUT" | grep -q 'gh pr'; then
exit 0
fi
COMMAND=$(jq -r '.tool_input.command // empty' <<< "$INPUT")
STDOUT=$(jq -r '.tool_output.stdout // empty' <<< "$INPUT")
if ! printf '%s\n' "$COMMAND" | grep -qE 'gh pr (create|edit)'; then
exit 0
fi
PR_URL=$(printf '%s\n' "$STDOUT" | grep -oE 'https://github\.com/[^/]+/[^/]+/pull/[0-9]+' | head -1)
if [ -z "$PR_URL" ]; then
PR_URL=$(printf '%s\n' "$COMMAND" | grep -oE 'https://github\.com/[^/]+/[^/]+/pull/[0-9]+' | head -1)
fi
if [ -z "$PR_URL" ]; then
PR_URL=$(gh pr view --json url --jq '.url' 2>/dev/null) || true
fi
if [ -z "$PR_URL" ]; then
exit 0
fi
BODY=$(gh pr view "$PR_URL" --json body --jq '.body' 2>/dev/null) || exit 0
if ! printf '%s\n' "$BODY" | grep -qiE '(Co-Authored-By:|Generated (with|by).*Claude)'; then
exit 0
fi
CLEANED=$(printf '%s\n' "$BODY" | perl -pe '
s/[Cc]o-[Aa]uthored-[Bb]y:[^\n]*//g;
s/.*[Gg]enerated (?:with|by).*[Cc]laude[^\n]*//g;
')
CLEANED=$(printf '%s\n' "$CLEANED" | cat -s)
CLEANED=$(printf '%s\n' "$CLEANED" | sed -e 's/[[:space:]]*$//')
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
printf '%s' "$CLEANED" > "$TMPFILE"
gh pr edit "$PR_URL" --body-file "$TMPFILE" >/dev/null 2>&1 || true
commit-msg template (git-commit-msg-hook target)Write this verbatim to <repo>/.git/hooks/commit-msg; chmod +x.
#!/usr/bin/env bash
# commit-msg — strip engineering-ownership trailers/footers from commit messages.
# Installed by /commventional:install-trailer-stripper --target git-commit-msg-hook.
# Runs on every commit (manual or via Claude); broader than v1.x's PreToolUse hook.
set -euo pipefail
MSG_FILE="${1:?commit-msg expects a path argument}"
ORIGINAL=$(cat "$MSG_FILE")
CLEANED=$(printf '%s\n' "$ORIGINAL" | perl -pe '
s/[Cc]o-[Aa]uthored-[Bb]y:[^"\x27\\]*//g;
s/.*[Gg]enerated (?:with|by).*[Cc]laude[^"\x27\\]*//g;
')
CLEANED=$(printf '%s\n' "$CLEANED" | cat -s)
if [ "$ORIGINAL" = "$CLEANED" ]; then
exit 0
fi
printf '%s' "$CLEANED" > "$MSG_FILE"
settings.json mergeFor Claude-Code-settings targets, merge an entry into the existing hooks.PreToolUse[] (or hooks.PostToolUse[]) array of the resolved settings.json. Use jq for the merge — never hand-edit the JSON.
If the file does not exist, create it with the minimal shape:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "<absolute-path-to-commventional-enforce-ownership.sh>",
"timeout": 5
}
]
}
]
}
}
If the file already exists, parse it as JSON. Refuse and abort if parsing fails — do not attempt to recover. Otherwise:
.hooks.PreToolUse (or .PostToolUse) exists as an array.matcher == "Bash". If found, append a new hook entry to its inner hooks[] array — do not duplicate if a hook with the same command already exists."Bash" matcher entry exists, append a new one with the structure above.Show the user the exact jq invocation you will run before running it. Use --argjson / --arg to inject the absolute path as a string. Write atomically: jq ... > <tmp> && mv <tmp> <settings.json>.
For PostToolUse the structure is identical — substitute PostToolUse for PreToolUse and commventional-pr-cleanup.sh for commventional-enforce-ownership.sh.
For --target all, merge both entries in a single Phase 3 cycle: ask the user which scope (user vs project), then write both standalone scripts and merge both PreToolUse + PostToolUse entries into the chosen settings.json.
Print to stderr:
jq delta applied to settings.json).jq '.hooks' <settings.json> to confirm the entry is present.git-commit-msg-hook: cat <repo>/.git/hooks/commit-msg && ls -la <repo>/.git/hooks/commit-msg to confirm executable.bin/strip-trailers.sh, the consumer must re-run this helper to pick up the change. The installed script is decoupled from the plugin.This skill writes consumer state (.claude/scripts/, .claude/settings.json, .git/hooks/commit-msg) — that is permitted under ADR-006 §1 because the consumer's slash-command invocation is the directing act. The plugin install itself performs no writes; only this skill, only when invoked explicitly by the consumer with an explicit --target.
ADR-006 §3 invariants (no payload mutation, no persistent host state, no undeclared writes) are inherited by the installed hook scripts — they live in the consumer's surface, so the consumer owns their conformance. v1.x's enforce-ownership.sh violated §3 invariant 1 (updatedInput payload mutation) when the plugin owned the hook; the same payload mutation in a consumer-installed hook is the consumer's chosen behaviour, outside the §3 scope.
documentation
Surface (and optionally fix) doc-tree drift — duplicates, dead links, stale docs, template non-compliance, missing `## Related` blocks. Read-only by default; `--apply` does mechanical fixes; `--apply-semantic` emits diffs for human review.
documentation
Full-text search over the repo's `docs/` tree (FTS5-backed). Returns ranked hits with file paths, tags, and matching snippets.
testing
Retrieval-augmented Q&A over the repo's `docs/` tree. Returns a one-paragraph synthesis plus citations (doc path + heading anchor) and per-citation corroboration verdicts. Field shape and ordering are locked at M3; M5 populates the verdicts.
documentation
Scaffold a new doc under `docs/` from a Diátaxis template, or update an existing one and bump its `updated:` date. Suggests `## Related` candidates and refreshes the FTS5 index on write.