Claude Code's (and Copilot's) native plugin installer has zero awareness of .apm/ nesting -- it convention-scans only flat skills/, agents/, commands/, hooks.json at each plugin's root. Confirmed via strings on the installed claude binary and live installs of git@holocron/gitea@holocron/kyberforge@ holocron, all reporting Skills(0) Agents(0) Hooks(0) post ADR-0015's apm conversion. Root cause (apm_cli/core/plugin_manifest.py): apm's plugin.json compiler deliberately strips skills/agents/commands keys, assuming the host already auto-discovers those convention directories -- it has no model of .apm/ being host-visible at all. Separately, apm's own bundle exporter (apm_cli/bundle/plugin_exporter.py, behind `apm pack --format plugin`) implements the correct .apm/ -> flat mapping, but only ever targeted build/<name>-<version>/, a path nothing in marketplace.json's source: points at. scripts/sync-plugin-content.sh wraps that bundle exporter and copies its agents/, skills/, commands/, instructions/, extensions/, and merged hooks.json back into each plugin's own root as a second tracked compiled-output category -- same governance status as .claude-plugin/plugin.json: generated from .apm/, never hand-edited. tests/ subdirectories are excluded from the mirror (dev fixtures, not host-visible runtime content; several hardcode a relative repo-root walk-up sized for the .apm/-nested depth, which breaks when duplicated one level shallower). Applied for real across all 6 plugins and verified two ways: `claude plugin validate --strict` passes on every real plugin directory, and a live `claude --plugin-dir <path> -p "list skills/agents"` behavioral test confirms content is now actually discovered. Also, from the same issue #90 review round: - scripts/check-manifests.sh pointed at each plugin's root-level plugin.json (checking skills/hooks/mcpServers/agents pointer fields) -- that file was a stale near-duplicate of .claude-plugin/plugin.json nothing else read or wrote, now deleted across all 6 plugins. check-manifests.sh is rewritten to validate .claude-plugin/plugin.json instead, and drops the pointer-field checks entirely (nothing to check -- those fields are correctly absent by design). Content-presence drift is now check-plugin-content-sync's job, a new pre-push hook wired in .pre-commit-config.yaml. docs/adr/0017 records the root cause and decision in full, including two rejected alternatives (patching plugin.json's path fields directly -- apm's compiler strips them on every run; pointing marketplace.json at apm pack's build/ output -- a version-suffixed non-source directory nothing can install from without an extra build step). ADR-0015 and CONTEXT.md are updated to point at it. Refs: #90
113 lines
8.5 KiB
Markdown
113 lines
8.5 KiB
Markdown
---
|
|
name: git-branches
|
|
|
|
description: >
|
|
Use when managing the full lifecycle of git branches: create feature/hotfix/release branches
|
|
(gitflow, GitHub Flow, or custom patterns from config), switch, delete, rename, and track branches,
|
|
or retrieve branch intent metadata. Handles branch protection safety checks and returns structured
|
|
results for agent composition. Use even if the user doesn't explicitly mention branch names — they
|
|
may be asking about "fixing something" or "shipping a feature", which implicitly requires branch
|
|
management. Do not use when the user needs only commit operations (use git-commits) or history
|
|
inspection (use git-history).
|
|
|
|
metadata:
|
|
category: git
|
|
source_keys:
|
|
- context7-git-htmldocs
|
|
- nvie-gitflow-post
|
|
- atlassian-gitflow-tutorial
|
|
- gitflow-cheatsheet
|
|
---
|
|
|
|
## Gotchas
|
|
|
|
- **Branches are cheap; deletion is cheap but risky.** Deleting one requires checking if commits on it are reachable elsewhere; always confirm before deleting, as it may lose unmerged work.
|
|
- **Uncommitted changes can block branch switches.** `git switch` aborts if local modifications conflict with the target branch. Offer to stash changes before switching when this happens, don't force a checkout.
|
|
- **Tracking relationships matter for coordination.** Agents pushing on behalf of users should always set tracking (`-u origin <branch>`) so later pushes/pulls know the target. Without it, commands fail or target the wrong remote branch.
|
|
- **Gitflow vs. GitHub Flow are not compatible.** Gitflow requires `develop` and `release/*` branches with `--no-ff` merges; GitHub Flow uses only `main` and feature branches with fast-forward. Read the repo's config or ask the orchestrator which pattern to use — don't guess.
|
|
- **Naming collisions with tags.** A branch and tag can have the same name. Prefer `git switch` over `git checkout` for branch operations — verify which ref you're targeting with `git branch --list <name>` / `git tag --list <name>` if the name could be ambiguous, and disambiguate explicitly with `refs/heads/<name>` (branch) or `refs/tags/<name>` (tag) where a command accepts either.
|
|
- **Never force-push `main` or `master`.** This is a hard refusal, not a confirmation gate — it applies even if the caller passes `confirm: true`. Deleting or renaming `main`/`master` in a way that would require a force-push to reconcile the remote (e.g. force-deleting and recreating it, or renaming it out from under in-flight work) must be rejected outright; explain why and suggest a non-destructive alternative (e.g. a new branch) instead of proceeding.
|
|
|
|
## Branch Patterns
|
|
|
|
Default to **GitHub Flow** (simpler, modern, CI/CD-friendly). Fall back to **Gitflow** only if the repo's config specifies it or the branch structure shows it in use (presence of `develop` or release branches).
|
|
|
|
**GitHub Flow:**
|
|
- Base: `main`
|
|
- Feature branches: `feature/<feature-name>` or `fix/<bug-name>`
|
|
- Merge: fast-forward when possible (preserves linear history)
|
|
- Delete after merge
|
|
|
|
**Gitflow:**
|
|
- Base: `main` (production) + `develop` (integration)
|
|
- Feature branches: `feature/<feature-name>` (from `develop`)
|
|
- Release branches: `release/X.Y.Z` (from `develop`, merged to `main` + `develop`)
|
|
- Hotfix branches: `hotfix/X.Y.Z` (from `main`, merged to `main` + `develop`)
|
|
- Merge: always use `--no-ff` to preserve branch structure
|
|
|
|
## Workflow
|
|
|
|
- [ ] **Determine pattern:** Check git plugin config (`.claude/plugins/git/config.json`, if present — see `config.example.json` in the plugin root for the expected shape) for `branching_pattern` (default: `github-flow`). If not set, inspect repo for `develop` branch or `release/*` branches; if present, assume Gitflow.
|
|
- [ ] **Create branch:** Use `git switch -c <branch> <base>`. Base defaults to config's `base_branch` (usually `main` or `develop`). Include intent metadata in branch name or return as structured result (e.g., `{ "branch": "feature/x", "intent": "implement feature X" }`).
|
|
- [ ] **Track remote:** If pushing, always use `git push -u origin <branch>` to establish tracking.
|
|
- [ ] **Safety checks before destructive ops:** Before delete/force-push/rebase with history loss, check: (1) Is this branch tracking a remote? Warn if yes. (2) Are there unpushed commits? Warn if yes. (3) Does the orchestrator call include `confirm: true`? Fail if not. For humans, prompt interactively.
|
|
- [ ] **Return structured results:** Always return branch operations as JSON or structured text: `{ "action": "create", "branch": "feature/x", "base": "main", "tracking": "origin/feature/x", "intent": "implement feature X" }`. Agents need to parse this for subsequent operations.
|
|
- [ ] **Retrieve intent (`get-intent`):** Git has no native field for free-text branch metadata — this skill doesn't persist it. On `create`, the `intent` value is only ever returned in the structured result; the caller (orchestrator or agent) is responsible for storing it if it needs to be looked up later. On `get-intent`, either parse it back out of the branch name convention (`feature/<intent-slug>`) or return `{ "intent": null }` if the caller never persisted the original create-time value — don't fabricate an intent.
|
|
|
|
### Command mapping for each action
|
|
|
|
- **delete:** `git branch -d <branch>` refuses if the branch has unmerged commits — prefer this by default. `git branch -D <branch>` forces deletion and discards unmerged work; only use it after the safety checks above pass and `confirm: true` is set. For a remote branch: `git push origin --delete <branch>`.
|
|
- **rename:** `git branch -m <old> <new>`.
|
|
- **list:** `git branch` (local only), `git branch -a` (all local + remote-tracking), `git branch -r` (remote-tracking only), `git branch --merged`/`--no-merged` (filter by merge status into current branch).
|
|
- **get-intent:** No git command — see Workflow step "Retrieve intent" for how this is resolved.
|
|
- **track (existing branch):** `git branch --set-upstream-to=origin/<branch>` sets tracking without a push; `git branch -vv` shows tracking state for all local branches.
|
|
- **switch (existing branch):** `git switch <branch>` — switches to an existing local branch (aborts on conflicting local changes, see Gotchas). `git switch -` switches back to the previously checked-out branch.
|
|
|
|
## Merging
|
|
|
|
Scope: fast-forward/merge-commit mechanics and conflict resolution only. Rebase, cherry-pick, and revert belong to `git-history`.
|
|
|
|
- **Fast-forward:** `git merge <branch>` — advances the pointer with no merge commit if the target hasn't diverged.
|
|
- **True merge:** `git merge --no-ff <branch>` — forces a merge commit even when fast-forward is possible; required by Gitflow on all supporting-branch merges.
|
|
- **Squash merge:** `git merge --squash <branch>` stages the combined diff without committing; follow with a manual `git commit`.
|
|
- **Octopus merge:** `git merge branch-a branch-b branch-c` merges more than two branches at once; fails outright on any conflict, so use sequential two-way merges if conflicts are expected.
|
|
|
|
**Conflict resolution:** when Git can't auto-merge, it inserts conflict markers and stops. Run `git status` to find conflicted files, edit them to resolve the markers, then `git add <file>` and `git merge --continue`. `git merge --abort` reverts to the pre-merge state. `git mergetool` opens the configured merge tool; `git diff --diff-filter=U` shows only conflicted files.
|
|
|
|
## Comparing Branches
|
|
|
|
- `git log main..feature` — commits in `feature` not in `main`.
|
|
- `git log feature..main` — commits in `main` not in `feature` (reverse direction).
|
|
- `git log --left-right main...feature` — both diverging sets (symmetric diff).
|
|
- `git diff main...feature` — diff from the common ancestor to `feature`'s tip.
|
|
- `git merge-base main feature` — print the common ancestor commit.
|
|
|
|
## Integration with Orchestrator
|
|
|
|
When invoked by `git-orchestrate`, accept requests in the form:
|
|
```json
|
|
{
|
|
"action": "create|switch|delete|rename|track|list|get-intent",
|
|
"branch": "<branch-name>",
|
|
"base": "<base-branch (optional, defaults to config)>",
|
|
"intent": "<human-readable intent (optional)>",
|
|
"confirm": "<true for destructive ops, omit for read ops>"
|
|
}
|
|
```
|
|
|
|
Return results as:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"action": "create|switch|...",
|
|
"branch": "<name>",
|
|
"message": "descriptive message",
|
|
"intent": "<intent if tracked>",
|
|
"tracking": "origin/<branch (if set)>",
|
|
"error": "<error message if success=false>",
|
|
"suggestion": "<recovery suggestion if applicable>"
|
|
}
|
|
```
|
|
|
|
If error is due to uncommitted changes, include `{ "suggestion": "stash changes and retry" }` so the orchestrator can offer automatic recovery.
|