Skip to content

feat(git-workflows): add git workflow plugin with invocable skills - #125

Open
cdcore09 wants to merge 1 commit into
mainfrom
feat/git-workflows-plugin
Open

cdcore09 wants to merge 1 commit into
mainfrom
feat/git-workflows-plugin

Conversation

@cdcore09

Copy link
Copy Markdown
Member

Summary

  • Converts nine personal git/GitHub slash commands into a shared git-workflows
    plugin, following the skills-first layout ai-research-workflows adopted in refactor(ai-research-workflows): skills-first redesign (10 skills, unified research, cross-agent portable) #113.
  • Behavior lives in skills, so the conventions apply on intent ("commit this",
    "open a PR") and not only when someone types the slash command. The nine
    commands remain as thin user-invocable wrappers, so existing muscle memory
    still works.
  • Generalizes assumptions the source commands had baked in from unrelated repos.

Closes #124

Changes

New plugin — plugins/git-workflows/

Skill Command Covers
using-git-workflows — Router plus the safety rules shared by all nine
committing-changes /commit Conventional messages, secret screening, staging by name
pushing-branches /push Upstream setup, push preview, rejection handling
opening-pull-requests /create-pr Branch-wide body, test plan, reviewers
merging-pull-requests /merge-pr Check gating, merge strategy, cleanup
filing-issues /create-issue Typed titles, checkable acceptance criteria
recording-decisions /create-discussion Decision records with rejected options
pruning-branches /clean-branches Categorization, confirmed deletion
publishing-releases /release Semver inference, changelog, tag, assets
documenting-work /docs Docs-system detection, nav wiring, build gate

Registered in .claude-plugin/marketplace.json (11 plugins now).

Generalized what was specific to one contributor's repos:

  • Commit scopes are read from the repo's own git log, not a fixed vocabulary
    (marketing, billing, worker, ...).
  • documenting-work detects MkDocs, Sphinx, Docusaurus, Quarto, or mdBook and
    prefers the project's task-runner target, instead of assuming MkDocs + pixi.
  • publishing-releases derives the compare URL from the current remote instead
    of hardcoding another project's, and detects what the project actually ships
    rather than assuming a plugins/ zip layout.
  • pruning-branches protects the discovered default branch plus anything the
    user names, rather than a hardcoded feedback-screenshots.

Resolved a contradiction in the sources. /commit, /create-pr, and
/create-discussion all forbade Co-Authored-By and AI-attribution trailers,
while /release wrote one into its changelog commit. Every skill now carries the
no-attribution rule.

Promoted a repeated snippet into scripts/default-branch.sh. Seven skills
had inlined the same default-branch derivation, and that one-liner silently fell
back to main whenever origin/HEAD was unset — which is common on fresh clones,
and is precisely the assumption this plugin exists to avoid. The script resolves
through origin/HEAD, then the remote, then the GitHub API, then a branch that
demonstrably exists, and exits non-zero rather than guessing.

Verification

Ran whetstone's skill-audit over the
new skills, and over the whole catalog to check the plugin does not compete with
skills already here:

whetstone skill audit -- 10 skills scanned        # plugins/git-workflows/skills
  missing description           0
  no trigger language           0
  description under 40 chars    0
  colliding pairs (>=0.35)      0
  duplicate names               0
  skills with >=1 defect        0  (0%)
whetstone skill audit -- 86 skills scanned        # plugins + community-plugins
  colliding pairs (>=0.35)      0
  duplicate names               0

Two defects surfaced during the run and were fixed rather than reported around:
using-git-workflows had no trigger clause in its description, and the
default-branch duplication above met whetstone's dry-promotion bar. The audit
was clean at threshold 0.20 as well, so the descriptions have margin — worth
checking, since nine skills sharing git vocabulary was the main routing risk.

The remaining 36 catalog defects are pre-existing in containerization and
project-management; none are in this plugin, and none are touched here.

Also verified locally:

  • validate-plugins.yml steps simulated: JSON valid, no duplicate keys,
    .claude-plugin/plugin.json and README.md present, scripts/*.sh
    executable, secret and dangerous-pattern scans clean
  • bash -n on default-branch.sh; shellcheck was not installed locally
  • default-branch.sh exercised against a throwaway repo whose default branch
    is trunk with origin/HEAD unset — resolves trunk, where the previous
    inline snippet returned main. Invalid remote exits 1 rather than falling
    through.
  • Every skill's frontmatter name matches its directory
  • All nine commands resolve to an existing skill; all nine originals covered

Test plan

  • claude plugin install git-workflows@rse-plugins, then confirm /commit,
    /push, /create-pr appear and delegate to their skills
  • Confirm the skills fire on intent ("commit this") without the slash command
  • Run plugins/git-workflows/scripts/default-branch.sh in a master-based
    repo and in one with origin/HEAD unset
  • /commit in a repo with no scopes in its history — confirm it omits the
    scope rather than inventing one
  • /docs in a Sphinx project — confirm it detects Sphinx, not MkDocs

Nine git/GitHub slash commands lived only in one contributor's
~/.claude/commands/ — unversioned, unshared, and carrying hardcoded
assumptions from unrelated repos. Port them to a skills-first plugin
following the ai-research-workflows layout, so the conventions apply on
intent ("commit this") and not only on an explicit slash command.

Generalized what was personal: commit scopes now come from the repo's
own log rather than a fixed vocabulary, the docs skill detects MkDocs,
Sphinx, Docusaurus, Quarto, or mdBook instead of assuming MkDocs plus
pixi, and the release skill derives its compare URL from the current
remote instead of hardcoding one project's.

Resolved a contradiction in the sources, where three commands forbade
Co-Authored-By trailers while /release wrote one; every skill now
carries the no-attribution rule.

Promoted the repeated default-branch one-liner into
scripts/default-branch.sh. The inline version silently fell back to
"main" whenever origin/HEAD was unset, which is the default-branch
assumption the plugin exists to avoid; the script resolves through the
remote and the GitHub API and exits non-zero rather than guessing.
@cdcore09 cdcore09 self-assigned this Sep 14, 2026
@cdcore09
cdcore09 requested a review from lsetiawan September 14, 2026 20:07
@github-actions

Copy link
Copy Markdown

🔍 Tessl Skill Review

plugins/git-workflows/skills/committing-changes/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/documenting-work/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/filing-issues/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/merging-pull-requests/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/opening-pull-requests/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/pruning-branches/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/publishing-releases/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/pushing-branches/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/recording-decisions/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


plugins/git-workflows/skills/using-git-workflows/SKILL.md

⚠️ Error: ✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.


To improve your score, point your agent at the Tessl optimization guide. Need help? Jump on our Discord.

Feedback

Report issues with this review at tesslio/skill-review, or send private feedback from your terminal with tessl feedback.

@cdcore09

Copy link
Copy Markdown
Member Author

Note on the red review check — it is not about this branch.

tessl-skill-review-pr.yml calls tesslio/skill-review@main without a
tessl-token input, so the action fails authentication for every skill it looks
at:

✘ Please authenticate with Tessl to continue. Run `tessl login` to sign up or log in.
##[error]Tessl authentication failed for 10 skill(s). Configure the tessl-token
input with a Tessl API token stored in a GitHub secret.

Every score comes back -1 (error), not a low score. The same check is currently
failing on main and on every recent PR that touches a SKILL.md — the last
green runs were on refactor/ai-research-workflows-skills. The workflow comment
says it is advisory via fail-threshold: 0, but that threshold only governs the
score gate; an auth failure still exits 1, so the check goes red regardless.

Worth a separate issue to either wire up the secret or drop the action. I have
left it alone here rather than widening this PR.

Skill quality on this branch was instead gated with whetstone's skill-audit
(0 defects, 0 collisions across the 10 new skills, and 0 collisions against all
86 skills in the catalog) — details in the PR description. validate passes.

@lsetiawan lsetiawan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @cdcore09 — reviewed this through the writing-for-agents lens, with a focus on making the skills portable across coding agents rather than tied to Claude Code's plugin commands.

Verdict: structurally sound. The split is by branch (the right cut), promoting default-branch.sh was the right call, and several skills carry exactly the non-obvious detail worth paying context for. The changes below are about making that work reachable from more than one harness, plus a writing pass.

Full writeup with the measured tables: https://claude.ai/code/artifact/adb01503-6ec2-4769-a119-145cdb12db01


The headline: keep every skill model-invoked

If the goal is "user-invocable skills instead of Claude plugin commands", the first half is right and the second half backfires. disable-model-invocation is itself Claude-Code-specific — reaching for it swaps one proprietary mechanism for another and moves nothing toward portability.

It also deletes the only invocation path guaranteed to exist. Model invocation needs one thing: the description sitting in context. User invocation needs the host to implement a skill-picker and agree on how a skill is addressed. Model invocation is the portable invocation.

There's a second cost inside Claude Code. Five skills are reached by a peer, not just the router:

committing-changes   ← opening-pull-requests, pushing-branches
pushing-branches     ← opening-pull-requests
documenting-work     ← recording-decisions
filing-issues        ← recording-decisions
recording-decisions  ← documenting-work

A skill with no description can't be reached by another skill. Disable any of these and opening-pull-requests tells the agent to publish via pushing-branches, then can't get there. The documenting-work ↔ recording-decisions pair is the sharpest — they cross-reference each other specifically to disambiguate, which is the best thing in the current descriptions.

Portability audit

Construct Sites Status Disposition
SKILL.md + name/description 10 ✅ Portable The core — keep all ten
${CLAUDE_PLUGIN_ROOT} 8 across 7 skills 🔴 Blocker See below
commands/*.md, $ARGUMENTS, user-invocable 9 🟡 Claude-only Delete — 8 of 9 duplicate their skill
git-workflows:<skill> namespacing 6 across 4 skills 🟡 Claude-only Use the bare skill name
.claude-plugin/, marketplace.json 2 🟡 Claude-only Keep — distribution channel, not content
disable-model-invocation 0 🔴 Don't add See above

.claude-plugin/ is how the skills get delivered to Claude Code; it says nothing about whether another agent can read a SKILL.md once the directory is on disk. The portability work belongs in the skill bodies, not the manifest.

1. ${CLAUDE_PLUGIN_ROOT} is the real blocker

${CLAUDE_PLUGIN_ROOT}/scripts/default-branch.sh appears at 8 sites across 7 skills. On any agent that doesn't define that variable it expands to /scripts/default-branch.sh and fails — and the failure lands on the plugin's one promoted shared asset, whose entire purpose is to stop the agent guessing main.

The portability fix and the duplication fix are the same move. The path is written out seven times, so re-addressing it for a second host is a seven-place edit:

plugins/git-workflows/reference/default-branch.md
  — resolution order, the script, and how to invoke it per host

# in each skill, at the point of use
Resolve the base branch by the procedure in `reference/default-branch.md`.

This is the principle the PR already applied when it promoted the script out of seven inlined one-liners — the promotion stopped one step short, at the address rather than the instruction.

Keep the script. The four-step resolution with a non-zero exit is genuinely good, and the throwaway-repo test (trunk, origin/HEAD unset) is the right kind of evidence. Only the addressing needs to move.

2. commands/ is 8/9 duplication

Each wrapper carries one behavioral instruction — the no-arguments fallback. Checked against the skill it wraps:

Wrapper Fallback Already in the skill?
/commit analyze all current changes §1 Survey
/push push current branch to its remote §1 Inspect
/merge-pr resolve the PR for the current branch §1 Identify
/create-issue ask what should be tracked §1 Clarify
/create-discussion draw from what was worked through §1, near-verbatim
/release infer the bump, state the reasoning §2, near-verbatim
/clean-branches present the table, ask which categories §3 + §4
/docs document the work from this session §2 Gather (roughly)
/create-pr ask who should review before opening No — stranded here

Nine files, ~90 lines, and exactly one instruction that isn't already in a skill. opening-pull-requests §4 shows --reviewer <login> but never says to ask. Port that line, then delete the directory.

Precedent: zarr-data-format already ships 5 skills and 0 commands, and its marketplace.json entry is the same shape as this one — neither enumerates commands, so deleting needs no manifest change.

3. What this costs, and the mitigation

/commit becomes /git-workflows:committing-changes. That's the only thing the wrapper layer bought, and it's a real cost if you have muscle memory in the short form.

Mitigation: rename the skills to the names the commands had. Names don't drive matching — the description does — so shortening costs nothing on the model-invocation side. The gerund convention comes from ai-research-workflows; it isn't repo-wide (containerization, zarr-*, uiux-design-team all use plain names).

committing-changes → commit, pushing-branches → push, opening-pull-requests → create-pr, merging-pull-requests → merge-pr, filing-issues → create-issue, recording-decisions → create-discussion, pruning-branches → clean-branches, publishing-releases → release, documenting-work → docs.

You'll also want a root AGENTS.md — deleting commands/ removes the typed-entry-point affordance on Claude Code, and nothing ever provided one on other agents. The repo has none at root today.

4. The router's shared rules are a coverage illusion

using-git-workflows ends with "Rules that hold across every skill." They don't hold — the router only loads when the router fires, which won't happen when a leaf description wins. Ask "merge this PR" and merging-pull-requests loads alone.

no-attribution rule present: committing-changes, filing-issues,
                             opening-pull-requests, publishing-releases,
                             recording-decisions
                     absent: merging-pull-requests, documenting-work,
                             pushing-branches, pruning-branches

merging-pull-requests composes a squash-commit message; documenting-work writes prose pages. Both write text, neither carries the rule.

The fix isn't "dedupe" — a must-have behind a pointer that may never fire is a variance bug, so the per-skill inlining is correct and should extend to those two. What should go is the router's copy.

The routing table is separately redundant: it instructs "invoke by fully-qualified id", which is plugin syntax, and nine live descriptions already do the routing. A router is the documented cure for user-invoked skills multiplying; with everything model-invoked it solves a problem that doesn't exist. What survives is the chains (commit → push → create-pr → merge-pr) — shrink it to that, or move them to the README.

5. Writing pass

Trigger synonyms — ~724 tokens every turn. 543 words of description across ten skills, ~56 trigger phrases resolving to ~18 distinct branches. pruning-branches writes one branch six times:

clean up branches, delete merged branches, prune stale branches, my branch list is a mess, remove old branches, tidy up refs

One trigger per branch halves this with no loss of reach. This is the lever for context cost, not disable-model-invocation.

Negation density. committing-changes runs 12 negations in 129 lines, and §3 names all three banned staging forms after showing the positive one — which makes the banned forms the most available thing in the section. Prompt the positive:

Current Positive
Never git add -A, git add ., or git add -u Stage each path by name: git add path/one.py path/two.md
Never skip hooks with --no-verify A failing hook is information: fix the cause and commit again
Never use --force unless… Force-writes use --force-with-lease, and only when asked in that exchange

Keep the guardrails that can't be phrased positively (refusing to force-write a protected branch), paired with the positive target.

Completion criteria. pruning-branches sets the bar — "every ref into exactly one bucket" is checkable and exhaustive. Weakest is documenting-work §3, "decide honestly whether this extends an existing page": a weak modifier over a fuzzy bound, gating the step the skill's own opening line calls the failure mode. Sharpen to "name the closest existing page and state why it is or is not the right home."

Leading words. "Read it off the repo, don't assume" is spelled out longhand at 7 sites and named only in the router, where it doesn't load. The word is already in the documents — committing-changes step 1 is Survey. Promote it. Likewise one idea wears three words across skills (Preview, Present, "Show before you write") — collapse to preview. Verify already works this way in all ten; copy that pattern.

The superseded originals. .claude/commands/ still holds all eight source commands on main and this PR deletes none. Renaming the skills removes the name collision but leaves two implementations of the same conventions, with the un-generalized one still live. Worth deleting in this branch.

Suggested order

Portability proper:

  1. Port "ask who should review before opening" into opening-pull-requests §4
  2. Delete plugins/git-workflows/commands/
  3. Rename skill directories to the short forms (+ name: frontmatter, + 6 cross-references)
  4. Drop the git-workflows: prefix from cross-references
  5. Move default-branch resolution into reference/default-branch.md

Writing pass:

  1. Inline the no-attribution rule in merge-pr and docs, delete the router's rules block
  2. Shrink the router to its chains
  3. Collapse trigger synonyms to one per branch
  4. Flip the negations in commit, push, merge-pr, release
  5. Add a root AGENTS.md
  6. Delete .claude/commands/

What's already right

  • The split is by branch — nine skills, one per distinct operation. Each path carries only what it needs.
  • default-branch.sh — four-step resolution, non-zero exit rather than a guess, tested against a trunk-based repo. Only its addressing needs to move.
  • Descriptions that name their sibling to disambiguate (documenting-work ↔ recording-decisions) put the disambiguation in the pointer, where it loads.
  • The expensive lookups are cached and the cheap ones aren't — ... vs .. for the reviewer's diff, "the third command fails when no upstream is set, that's the signal for -u", -d refusing after a squash merge. Exactly what a document should carry.

Nice piece of work overall — the generalization away from one contributor's repos is the part that makes this reusable, and it mostly landed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(git-workflows): add git workflow plugin with invocable skills

2 participants