Installs into .claude/skills of the current project.
Are you the author of Git?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mifunedev-git)
---
name: git
description: |
AGRO git workflow: issues, branches, commits, PR titles/bodies,
changelog discipline, worktrees, branch catch-up, stacked PRs, releases,
and post-push CI checks.
TRIGGER when: any chat mentions git, GitHub, branches, commits, pushes,
pulls, PRs, issues, worktrees, merge conflicts, dirty workspaces,
changelog entries, release branch/tag workflow, or project git conventions.
allowed-tools: Bash
---
# Git Workflow
## Always Load This Skill
Any time a chat mentions git, GitHub, branches, commits, pushes, pulls, PRs,
issues, worktrees, merge conflicts, releases, changelog entries, or dirty
workspace cleanup, read the git skill before acting. Treat this skill as the source of truth
for routing changes to the right remote and for preserving local work safely.
## Repository and Memory Routing
This checkout commonly has two remotes:
- `upstream` → `mifunedev/agro` (public template/canonical upstream)
- `origin` → your fork of `agro` (private/operator fork)
Before every commit or PR, inspect the changed paths and choose the remote
explicitly. Do not assume `origin` is the public target.
## Issue Titles
Format: `<prefix>: <shortdesc>`
`<prefix>` ∈ `feat` · `bug` · `task`
(matches `.github/ISSUE_TEMPLATE/<prefix>.md`)
Example: `feat: slack thread replies`
> Create the issue first so `<issue#>` exists, then branch.
## Branch Names
Format: `<prefix>/<issue#>-<short-desc>`
- `<short-desc>`: kebab-case, ≤5 words
- Base off default target branch (see below)
Example: `feat/42-slack-thread-replies`
## Default Target Branch
Use first existing in repo:
1. `development` (preferred)
2. `main` (fallback)
3. `master` (fallback)
Detect via `git show-ref --verify --quiet refs/heads/<name>` (or remote `refs/remotes/origin/<name>`). PRs target this branch; new branches cut from it.
## Git Authentication
Inside sandbox, run `gh auth login && gh auth setup-git` during onboarding. GitHub CLI installs credential helper — `git push` / `git fetch` use your GitHub token — no SSH keys required.
## PR Titles
Format: `FROM <source-branch> TO <target-branch>` (literal)
Example: `FROM feat/42-slack-thread-replies TO development`
## PR Bodies
- Link issue: `Closes #<issue#>` (or `Fixes`/`Resolves`) in the PR title or body
- Target default target branch (`development` → `main` → `master`, whichever exists)
> **`Closes #N` closes the issue on merge into `development` — the workflow does it, not
> GitHub.** GitHub honors a closing trailer only on merge into the **default** branch. PRs
> here target `development` while the default branch is `main`, so GitHub records the
> trailer as a `referenced` timeline event and nothing else. Verified on #759 (whose
> `closed` event carries `commit=none`) and independently on #753 from PR #757.
> `.github/workflows/close-issues-on-development.yml` closes the gap: on a **merged** PR
> into `development` it parses the title and body for the nine closing keywords and closes
> each referenced issue as `completed` (#841).
>
> The trailer is therefore load-bearing — write the trailer on every PR. Close by hand only when the
> automation cannot see the merge (a direct push, a merge into another branch, a PR whose
> body carried no trailer, or a PR opened from a **fork**, which gets a read-only token):
>
> ```bash
> gh issue close <N> --repo <owner/name> --comment "Merged in #<PR>."
> ```
## Commit Messages
Format: `<type>: <description>` where `<type>` ∈ `feat` · `fix` · `task`
## Changelog
Root `CHANGELOG.md` follows [Keep a Changelog](https://keepachangelog.com) with SemVer versions and `v`-prefixed tags.
Every PR with user-visible impact MUST add entry under `## [Unreleased]` heading, in same commit as change. Categories: `### Added` · `### Changed` · `### Fixed` · `### Removed` · `### Deprecated` · `### Security`.
Skip entries only for pure chores with no runtime or workflow effect (internal refactors, test-only changes, typo fixes). When in doubt, add entry.
Entry format: ONE sentence, imperative mood, **≤ 250 characters**, link the PR or issue.
An entry states WHAT changed and its user-visible effect. Never why. Never alternatives considered. Never implementation detail.
```markdown
### Added
- Slack thread replies in multi-channel mode ([#42](https://github.com/mifunedev/agro/pull/42)).
```
Displaced detail has a destination — put it there, not in the entry:
| Detail | Destination |
|--------|-------------|
| Rationale, rejected alternatives | The PR body — the `([#N])` link is the pointer |
| Task decisions | `.agro/tasks/<slug>/prd.md` |
| Architecture decisions | `docs/rfcs/` |
| Durable, generalized lessons | A minted probe under `.agro/evals/probes/` |
BAD (real entry, 3,579 chars — a design doc wearing a bullet):
```markdown
- Add `agro harness <list|install|status>` so installing an agent harness stops requiring a full image rebuild. Adding one of the four optional harnesses previously meant knowing that `harness.yaml` carries an `install:` section, …
```
GOOD (233 chars — same fact, rationale left to the PR):
```markdown
- Add `agro harness <list|install|status>` to install optional harnesses into a running sandbox without a rebuild, persisting the choice to `install.<key>` for the next build ([#821](https://github.com/mifunedev/agro/pull/821)).
```
Enforced by `.agro/evals/probes/changelog-entry-length.sh` (report-only) over `## [Unreleased]`.
Automatic branch-push releases use the matching `## [<VERSION>] - YYYY-MM-DD` section when one already exists; otherwise they publish the current `[Unreleased]` body. Do **not** hand-edit a versioned section after its tag ships, except for a repo-wide reformat that changes no facts.
## Worktrees
Path: `.worktrees/<branch>` at the root of the repository the branch belongs to. A project clone under `projects/` keeps its own worktrees the same way, at `projects/<owner>/<repo>/.worktrees/`. Independent project clones (own `.git`, not harness branches) live under `projects/<owner>/<repo>/`. Neither root is a setting; both roots are conventions — see `.worktrees/AGENTS.md` and `projects/AGENTS.md`.
```bash
WORKTREES_ROOT="$(bash .agro/scripts/agro-path worktrees --no-create 2>/dev/null || printf '%s' .worktrees)"
mkdir -p "$WORKTREES_ROOT"
git worktree add "$WORKTREES_ROOT/<branch>" <branch> # existing branch
git worktree add -b <prefix>/<issue#>-<short-desc> \
"$WORKTREES_ROOT/<prefix>/<issue#>-<short-desc>" $BASE # new branch off $BASE
```
Example path: `.worktrees/feat/42-slack-thread-replies`
Cleanup: `git worktree remove "$WORKTREES_ROOT/<branch>"`.
`.worktrees/` and `projects/` gitignored (see `.gitignore`); only `.worktrees/AGENTS.md` and `projects/AGENTS.md` tracked.
### Stale worktree policy
You may remove a worktree with `git worktree remove` when the worktree is older than 30 days and has no corresponding open PR. You may remove a corrupted worktree directory with `rm -rf` after you confirm that the directory is not a valid `git worktree list` entry. The `/audit harness` skill flags stale-worktree candidates for review before cleanup.
### Isolating in-flight work
When main checkout has unstaged changes you shouldn't commit in current PR, do **not** stash-and-switch-branches (risk of losing context). Instead:
1. Cut worktree off target base: `git worktree add -b <new> "$WORKTREES_ROOT/<new>" $BASE`.
2. Copy in-flight files into worktree: plain `cp` preserves main checkout's working tree untouched.
3. Commit in worktree. Main checkout stays exactly as-is.
Before discarding duplicated state from main checkout, verify byte-equivalence with committed branch:
```bash
for f in <changed-files>; do
a=$(md5sum "$f" | awk '{print $1}')
b=$(git show <branch>:"$f" | md5sum | awk '{print $1}')
[ "$a" = "$b" ] && echo "same: $f" || echo "DRIFT: $f"
done
```
Only after all files show `same:` run `git restore` / `rm -f` to clean main checkout.
## Destructive git under the cc-safety-net guard
`cc-safety-net` denies inline destructive git — `git reset --hard <ref>`, `git clean -f`, `git branch -D`, `git worktree remove --force`, `git push --force` — in every mode (its built-in git rules are not allowlistable). In agent (hook-mediated) contexts you must route these through the file-invoked shim instead:
```bash
bash .agro/scripts/git-maintenance.sh reset-hard <ref>
bash .agro/scripts/git-maintenance.sh clean
bash .agro/scripts/git-maintenance.sh branch-delete <branch>
bash .agro/scripts/git-maintenance.sh worktree-remove <path>
bash .agro/scripts/git-maintenance.sh push-force <remote> <branch> # uses --force-with-lease
```
**Scope rule:** only **non-agent-mediated** invocations — raw scheduler/tmux shell scripts that never spawn a provider — bypass the PreToolUse hooks. Agent-driven crons do **not** bypass the hooks. `cron-runtime.ts` runs agent-driven crons as `pi --continue` / `claude -p` prompts, so their Bash passes through the guard. Agent-driven crons must use the shim too. The shim is a compatibility shim, not a security control. The same script-file gap is also an evasion route; Docker is the security boundary.
## Catching Up Feature Branches
When an open feature branch falls behind `development`, prefer merging the target branch into the feature branch instead of rebasing it. A merge preserves the branch's published history and avoids force-push churn. A merge also keeps integration-conflict resolution on the feature branch. The final squash merge keeps `development` free of the catch-up merge commit.
```bash
git fetch origin development
git checkout <feature-branch> # or run inside its worktree
git merge origin/development # resolve conflicts on the feature branch
git push origin <feature-branch> # normal push; no --force-with-lease
```
After the merge, rerun the targeted checks and `/ci-status`/`/audit pr` before marking the PR ready or merging it.
Use rebase/force-push only for deliberate history surgery. Examples: before you share a branch, or when you explicitly manage a stacked PR by the procedure in § Stacked PRs.
## Stacked PRs
When PR needs work from another open PR (e.g. feature depending on in-flight docs or infra changes), stack instead of waiting:
1. `git fetch origin <parent-branch>`
2. In worktree: `git rebase origin/<parent-branch>`. Resolve conflicts; tests may need re-running.
3. `git push --force-with-lease`
4. `gh pr edit <pr#> --base <parent-branch>`
5. `gh pr edit <pr#> --title "FROM <branch> TO <parent-branch>"`
When parent PR merges, GitHub auto-rebases stacked PR's base to parent's target (`development`). Do **not** force-push again after parent merges — let GitHub handle retarget.
Keep stacks shallow: one level routine, two levels rare, three levels means a sequencing error.
## Releases
Every push to `main`, `master`, or `experiment/**` triggers `.github/workflows/release.yml`. The
workflow checks out the exact event SHA and requires validation, boot-path lint,
and eval probes to pass before it mutates a tag, GitHub Release, or package.
Do **not** manually pre-create a release tag or `release/<version>` branch.
Versioning is SemVer: `MAJOR.MINOR.PATCH`, tagged `vMAJOR.MINOR.PATCH`. Root
`package.json` holds the release version. The workflow reads that file and never
derives a version from the clock, so cutting a release is a deliberate bump, not
a side effect of pushing. A cut bumps root `package.json` and `.agro/cli/package.json`
(with its `package-lock.json`) to the same value. `version-parity.sh` fails the
build on that canonical drift. The retained shim at `.agro/cli/legacy/` keeps its
own version and an exact `@mifune/agro` pin that equals that shim version. The
shim version does not have to match a later canonical version.
Creating `refs/tags/v<version>` is the atomic reservation. A retry reads the same
version from the same commit, so it reuses a same-SHA draft, and a retry of an
already-published same-SHA release is a successful no-op.
**An unbumped push to `main` is a clean skip, not a failure.** When the tag
already exists on a different commit, the reserve step reports the version as
already released, sets `publishedNoop=true`, and the image, CLI, and finalize
jobs all skip. The run stays **green**. To publish again, bump the version.
The `v` prefix appears only in the git tag and the GitHub Release name. The step
output, the GHCR image tags, and the concurrency group all stay bare
(`ghcr.io/mifunedev/agro:0.1.0`, `ghcr.io/mifunedev/agro:0.1.0`).
One release publishes the canonical npm package `@mifune/agro` (`agro`). The
`@mifune/agro` shim source remains at `.agro/cli/legacy/` and already-published
shim versions remain on the registry. The release path does not publish, wait for,
or deprecate the shim. The same build still publishes the GHCR tags
`ghcr.io/mifunedev/agro:<version>`, `:sha-<sha>`,
`ghcr.io/mifunedev/agro:<version>`, `:sha-<sha>`, verified to share one digest,
plus `latest` on both repositories; and the release assets `agro.js`, `oh.js`,
`get-agro.sh`, `get-agro.sh`. Publishing `@mifune/agro` needs npm rights for that
name, and the package owner must make the GHCR package `mifunedev/agro` public after its first
push; neither is verifiable here. The compatibility SLA clock starts at the first
public AGRO release.
The artifact sequence is:
```
main|master push → validate + boot-lint + eval → read version from package.json
→ reserve v<version> tag + draft
→ build once + boot smoke + agro/agro version smoke
→ push agro + agro <version> and sha-<full-SHA> GHCR tags
→ verify one digest → canonical latest-by-digest on both
→ publish/no-op @mifune/agro
→ attach agro.js, oh.js, get-agro.sh, get-agro.sh
→ publish GitHub Release
```
The mutable/latest branch is canonically `main` when it exists, otherwise
`master`. A helper fetches both refs immediately before digest promotion, so a
`master` run can never regress `latest` when `main` exists; stale canonical runs
also skip it. GitHub `make_latest` repeats the same fresh canonical check and is
always false for the noncanonical branch. The GitHub Release stays draft until
immutable image and successful/no-op CLI publication finish. See `/release` for
the fast-forward promotion, monitoring, and verification procedure.
## Draft PR for a task
Run the steps below after the operator approves `.agro/tasks/<slug>/prd.md`.
Creating the issue and the PR is outward-facing. Create them only after explicit
operator approval of the plan. Writing a plan is not approval.
1. Open the issue. Use the `.github/ISSUE_TEMPLATE/feat.md` shape. Fill User
Stories, Summary, and Acceptance Criteria from `prd.md`. Record `<N>`:
```bash
gh issue create --title "<prefix>: <shortdesc>" --body-file <issue-body.md>
```
2. Create branch `<prefix>/<N>-<slug>` from `$BASE` in an isolated worktree
(see § Worktrees).
3. Commit the plan as the first commit. Push the branch:
```bash
git add .agro/tasks/<slug>/prd.md .agro/tasks/<slug>/prd.json
git commit -m "<prefix>: plan <slug>"
git push -u origin <prefix>/<N>-<slug>
```
4. Open the draft PR. Build the body from `.github/pull_request_template.md`.
If the target repository has no `.github/pull_request_template.md`, use
`.github/pull_request_template.md` of the AGRO harness.
Put `Closes #<N>` in the body. Add a `## Stories` checklist from `prd.json`:
```bash
jq -r '.userStories[] | "- [ ] \(.id): \(.title)"' .agro/tasks/<slug>/prd.json
gh pr create --draft --base $BASE --title "FROM <prefix>/<N>-<slug> TO $BASE" --body-file <pr-body.md>
```
The advisor commits each accepted story to this branch and does not push it.
The advisor pushes the task branch only two times: in step 3 for the draft PR,
and in § Ready for review before `gh pr ready`. Push at another time only when
the operator explicitly requires it.
### Ready for review
Do these steps in this sequence:
1. Examine the first three conditions below. If one is false, stop and keep the
PR in draft.
2. Push the task branch one time: `git push`.
3. Run `/ci-status` to examine the last condition.
4. Run `gh pr ready` only when all of these conditions are true. If one is
false, refuse and keep the PR in draft.
- Every story passes:
`jq -e 'all(.userStories[]; .passes == true)' .agro/tasks/<slug>/prd.json`
exits 0.
- `prd.md` ends with a non-empty `## Lessons` section. "None" is a valid body:
`awk '/^## /{s=($0=="## Lessons");n=0;next} s&&NF{n++} END{exit !(s&&n)}' .agro/tasks/<slug>/prd.md`
exits 0.
- The PR body evidence sections are non-empty: What the issue asked for,
`What was built`, Where it diverged, Manual review, What remains unverified,
Verification, and Lessons. "None" or "Nothing" is a valid body, except for
Manual review. Manual review follows
[references/manual-review.md](references/manual-review.md).
- The Manual review links and callouts pass. Write the PR body to a file, then
run `bash .agro/skills/git/scripts/manual-review-check.sh <body-file>`. The
command must exit 0.
- The repository's checks pass on the pushed branch (`/ci-status`).
### After the merge
Do these steps in this sequence:
1. Run the merge check as its own command. Do not chain it with a cleanup
command:
```bash
gh pr view <N> --json state -q .state
```
2. If the output is not `MERGED`, stop. Do no cleanup. An operator statement is
not evidence of the merge.
3. Remove the task worktree:
`bash .agro/scripts/git-maintenance.sh worktree-remove <path>`.
4. Delete the local task branch:
`bash .agro/scripts/git-maintenance.sh branch-delete <branch>`.
5. Confirm the issue closed (see § Workflow step 7).
Deleting the remote branch of an open PR closes the PR.
## After Push
If `.agro/skills/ci-status/` exists, invoke `/ci-status` after every `git push` to confirm pipeline green before declaring work done. Push failing CI is not done.
## Provider Portability
Provider-specific rule files are not loaded by every provider, so put active
instructions in skills. If you discover a provider-specific workflow dependency
hiding in a rules file, promote it to a skill and leave a short rule file that
points to the skill.
## Workflow
Let `$BASE` = default target branch (detected per rule above).
1. Create GitHub issue → record `<issue#>`
2. `git checkout -b <prefix>/<issue#>-<short-desc> $BASE`
3. Add `CHANGELOG.md` entry under `## [Unreleased]` (see § Changelog) — unless change is pure chore
4. Commit with `<type>: <description>`
5. `git push -u origin <branch>` → then `/ci-status` (if skill exists)
6. `gh pr create --base $BASE --title "FROM <branch> TO $BASE" --body "Closes #<issue#>"`
7. After the merge, confirm the issue closed. The `close-issues-on-development` workflow
closes the issue from the `Closes #N` trailer (see § PR Bodies). If the merge bypassed a PR into
`development`, close the issue by hand: `gh issue close <issue#> --repo <owner/name>`