Files
holocron/plugins/git/skills/git-remotes/SKILL.md
Defame1297 38f1ba4e03 fix(kyberforge): bridge apm content to Claude Code's flat plugin discovery
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
2026-08-13 16:59:03 +00:00

9.2 KiB

name, description, metadata
name description metadata
git-remotes Manage git remote repositories — add/remove/configure remotes, push/pull with safety checks, handle fetch patterns and tracking branch updates, support multi-remote workflows. Use when automating remote operations, pushing with force-push safety, fetching with pruning, pulling with divergence resolution, or managing multi-remote tracking. Include indirect triggers: any git operation that touches a remote, even if the user doesn't explicitly name the remote. Do not use when working with local git history, commits, branches, or staging — use git-history or git-branches instead.
category source_keys
git-workflow
git-scm-remote-docs
git-scm-fetch-docs
git-scm-push-docs
git-scm-pull-docs
context7-git-htmldocs

Gotchas

  • Never force-push main or master, under any circumstances — this is a hard refusal, not a confirm: true gate. If a force-push targets one of these branches, decline and explain why, regardless of how the request is confirmed.
  • Force-push to any other branch requires explicit confirmation — never execute git push --force or git push -f without user/agent approval. Always ask or require confirm: true flag first.
  • --force-with-lease alone is not safe — background processes (IDE plugins, cron jobs) that run git fetch silently defeat the protection. Always combine with --force-if-includes or use explicit SHA form --force-with-lease=<ref>:<sha>.
  • Prune doesn't touch tags by default — git fetch --prune leaves orphaned tags. Use git fetch --prune --prune-tags or configure fetch.pruneTags true globally.
  • Pull with rebase rewrites history — only safe for unpublished work. Rebasing already-pushed commits breaks everyone downstream. Check what's been pushed before rebasing.
  • git remote show requires network access — use -n flag for cached data if working offline. git remote -v lists URLs without network queries.
  • Pull behavior defaults shift between Git versions — older versions default to merge, newer versions to --ff-only. Always set pull.ff only explicitly for deterministic behavior.

Operations

Remote Management

Use these to configure which remotes you push to and pull from:

  • Add a remote: git remote add <name> <url> or git remote add -f <name> <url> to fetch immediately
  • Remove a remote: git remote remove <name> (deletes remote + all tracking refs + config)
  • Rename a remote: git remote rename <old> <new>
  • Inspect remotes: git remote -v (show URLs) or git remote show <name> (live tracking status, requires network)
  • Set-url separately for fetch vs. push: git remote set-url --push <name> <url> changes only where pushes go — but fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead; --push cannot do this. Full set-url variants (regex-targeted replace, --add, --delete): references/remotes.md.
  • Remove a stale URL: git remote set-url --delete <name> <regex>
  • Inspect effective URLs: git remote get-url <name> (shows URL after insteadOf rewrites) or git remote get-url --push --all <name> (all push URLs)
  • Track only one branch: git remote add -t <branch> <name> <url> (repeatable), or suppress tag import entirely with git remote add --no-tags <name> <url>
  • Mirror a remote: git remote add --mirror=fetch <name> <url> mirrors all refs locally (bare repos only); --mirror=push makes every push behave like --mirror
  • Prune stale tracking refs without fetching: git remote prune <name> (add --dry-run to preview first)
  • Set the remote's default branch pointer: git remote set-head <name> -a (auto-detect, requires a prior fetch), git remote set-head <name> <branch> (explicit), or git remote set-head <name> -d (delete refs/remotes/<name>/HEAD)

Fetch Operations

Use these to update your tracking branches without touching your local branches:

  • Fetch from one remote: git fetch <remote> — fetches all branches
  • Fetch one branch only: git fetch <remote> <branch> — stores the result in FETCH_HEAD, not a tracking ref
  • Fetch from all remotes: git fetch --all with optional --prune to clean up stale tracking refs
  • Prune properly: Use git fetch --all --prune --prune-tags to clean both branches and tags
  • Configure auto-prune: Set git config --global fetch.prune true to auto-prune on every fetch across all remotes (or remote.<name>.prune to scope it to one remote)
  • Shallow clones: --depth=<n> to deepen or create a shallow clone, --unshallow to convert to full history, --update-shallow to allow the shallow boundary to move. Details and the default fetch refspec: references/remotes.md.

Fetch never modifies your local branches — it only updates remote-tracking branches (refs/remotes/origin/*).

Push Operations

Use these to send your commits upstream. Default: safe push to same-named branch on the remote.

  • Basic push: git push <remote> <branch> — pushes to same-named remote branch
  • Set upstream: git push -u <remote> <branch> — push and configure this branch to track the remote
  • Multi-remote push: git push origin develop and git push staging develop sequentially, or use git remote set-url --add <name> <url> to push to multiple remotes with one command
  • Force-push safety: Always use git push --force-with-lease --force-if-includes <remote> <branch> over bare --force. Require explicit confirmation first — and never for main/master (see Gotchas). --force-if-includes is a no-op without --force-with-lease. If background tools (IDE, cron) auto-fetch and could poison the lease check, use a dedicated push-only remote instead — see references/remotes.md.
  • Server-side enforcement: receive.denyDeletes, receive.denyDeleteCurrent, and receive.denyNonFastForwards are enforced on the remote regardless of local flags — a hardened server rejects the push even with --force.
  • Delete remote branch: git push <remote> --delete <branch> (not :<branch> syntax; clearer and cleaner)
  • Push everything: git push --all (all local branches) or git push --tags (all tags)
  • Push a single tag: git push origin <tag>
  • Delete remote branches with no local counterpart: git push --prune origin 'refs/heads/*:refs/heads/*'
  • Force only part of a multi-ref push: prefix the one refspec that needs it with +, e.g. git push origin +main develop forces main while safe-pushing develop

Refspec syntax is [+]<src>[:<dst>]:

Pattern Meaning
<branch> Push to same-named remote branch
<src>:<dst> Push <src> local ref to <dst> remote ref
+<src>:<dst> Force this refspec (non-fast-forward allowed)
:<branch> Delete remote <branch>
refs/heads/*:refs/heads/* Glob: push all matching branches
^refs/heads/dev-* Negative: exclude matching refs
tag <name> Sugar for refs/tags/<name>:refs/tags/<name>

Pull Operations

Use these to fetch and integrate remote changes. Default strategy: --ff-only (fail if diverged, forcing a conscious choice).

  • Pull with fast-forward only: git pull --ff-only (recommended default — fails if you've diverged, forcing a rebase/merge decision)
  • Pull with rebase: git pull --rebase (replays your unpublished commits on top; linear history, but rewrites SHAs — only safe for unpublished work)
  • Pull with merge: git pull --no-rebase (three-way merge commit; preserves original commits, non-linear)
  • Pull with rebase, preserving merges: git pull --rebase=merges (like --rebase, but keeps intentional local merge commits during replay)
  • Pull without integrating: git pull --squash collapses incoming commits into staged changes without committing — you write the commit message
  • Set pull strategy globally: git config pull.ff only (or pull.rebase true; respects branch-specific overrides via branch.<name>.rebase). Full precedence order (CLI flag > pull.rebase > branch.<name>.rebase > branch.autoSetupRebase): references/remotes.md.
  • Check before rebasing: Always verify your commits haven't been pushed before using --rebase. Rebasing published commits breaks everyone downstream.
  • Merge strategy default: Git 2.34+ defaults to the ort merge strategy (recursive is now just an alias for it). Strategy options like -X ours, -X theirs, -X ignore-space-change still pass through unchanged.
  • Submodules on pull: --recurse-submodules only fetches submodules that are already checked out — newly added submodules are not initialized automatically. Use the git-submodules skill to initialize new ones.

If pull diverges and you haven't set a strategy, the operation fails — this is good, forces a conscious choice. Never auto-merge diverged branches without asking.

Return Format (for agents)

Return structured output:

{
  "success": true,
  "operation": "push",
  "remote": "origin",
  "branch": "main",
  "output": "...",
  "warnings": ["force-with-lease not confirmed"],
  "recommendations": ["set pull.ff=only globally"]
}

On failure, include error field with root cause and recovery suggestion.