# Recipe: standardized git configuration (AG-18)

> **Audience:** any agent or maintainer setting up — or auditing — a federation
> repo's git configuration.
> **Rule:** [AG-18](../standards/AGENTS.md#AG-18). Companion to
> [`commit-signing.md`](commit-signing.md) ([AG-03](../standards/AGENTS.md#AG-03)).

Every `mgf-*` repo is wired identically, so the working tree, the ignored
surface, and the commit format are the same everywhere. Three pieces.

## 1. The canonical `.gitignore`

Present at repo root. It MUST ignore build/test artifacts, IDE config,
Claude-local settings, and — the security-load-bearing line — `my_stuff/`:

```gitignore
# Local secrets / tokens — NEVER committed (AG-18, PKG-03, SECURITY)
my_stuff/

# IDE
.idea/
.vscode/
*.swp

# Local Claude overrides
.claude/settings.local.json
```

…plus the language block (Python `__pycache__/`, `dist/`, `*.egg-info/`,
`.venv/`…; Zig `.zig-cache/`, `zig-out/`, `zig-pkg/`…). New repos inherit the
full file from the cookiecutter; existing repos run:

```sh
mgf-fed catch-up --apply     # appends any MISSING standard ignore lines
```

`catch-up --apply` only *appends* missing lines — it never reorders or removes
a repo's own entries.

### Why `my_stuff/` is non-negotiable

`my_stuff/` is the repo-root home for local credentials — the devpi publish
password at `my_stuff/tokens/devpi_password.txt` (PKG-03; needed only for a
manual publish — CI publishes via the tag-scoped `devpi_password` org secret,
and install is **anonymous**), SSH keys under `my_stuff/ssh_key/`. Committing it leaks a credential, so the ignore is a
SECURITY boundary, not a preference. `mgf-fed app-doctor` fails a repo whose
`.gitignore` does not ignore `my_stuff/`.

## 2. The `.gitmessage` commit template

Wired once per clone so commit drafts carry the AG-03 trailers:

```sh
git config commit.template .gitmessage
```

## 3. Identity

```sh
git config user.name  "Bassam Alsanie"
git config user.email "bassam@magogi.org"
```

## 4. The `prepare-commit-msg` hook (AG-03 enforcement)

The `.gitmessage` template (§2) is consulted only for *interactive* commits —
`git commit -m` (the common agent/scripted path) bypasses it, so the AG-03
`Signed-off-by` is easy to drop. The committed `.githooks/prepare-commit-msg`
hook closes that gap: it appends the human signoff **idempotently** on every
commit, including `-m`.

There are **two wirings, and they are mutually exclusive** — pick by whether
the repo uses the [pre-commit framework](https://pre-commit.com)
(`.pre-commit-config.yaml` present):

### 4a. Repo WITHOUT the pre-commit framework

```sh
git config core.hooksPath .githooks
```

### 4b. Repo WITH the pre-commit framework (PAPER-117)

Do **NOT** set `core.hooksPath` — pre-commit hard-errors on it:

```
[ERROR] Cowardly refusing to install hooks with `core.hooksPath` set.
```

so a repo that sets it can never `pre-commit install` its own gate, and a repo
that skips §4a *silently* loses the AG-03 hook (`app-doctor` checks the hook
file is *present*, not that it is *active*). Instead, run the same committed
script as a `prepare-commit-msg`-stage pre-commit hook — add to
`.pre-commit-config.yaml`:

```yaml
  - repo: local
    hooks:
      - id: ag03-signoff
        name: AG-03 signoff trailer (prepare-commit-msg)
        entry: .githooks/prepare-commit-msg
        language: script
        stages: [prepare-commit-msg]
        always_run: true
```

and install the extra hook type alongside the usual ones:

```sh
pre-commit install --hook-type pre-commit --hook-type prepare-commit-msg
# + --hook-type pre-push if the repo uses pre-push stages
```

Verified end-to-end (2026-07-10, pre-commit 4.6.0): install is clean, `git
commit -m` gains the `Signed-off-by` trailer, and merge/squash messages are
left alone in both wirings (under pre-commit the script reads
`PRE_COMMIT_COMMIT_MSG_SOURCE`, which the framework exports in place of the
hook's `$2` source argument).

In both wirings the hook adds only the *constant* human `Signed-off-by`
(derived from `user.name`/`user.email`); the model-specific `Co-Authored-By`
stays agent-supplied. The **authoritative** enforcement is server-side and
identical for both: the generated `.woodpecker.yml` runs
`scripts/check_commit_trailers.py` as its `commit-trailers` step
(`templates/woodpecker/frag-commit-trailers.yml`), failing any branch whose
non-merge commits lack either trailer — independent of local config (AG-18(c)).

## One-liner for a fresh clone

```sh
git config commit.template .gitmessage
git config user.name  "Bassam Alsanie"
git config user.email "bassam@magogi.org"
# AG-03 signoff hook — exactly ONE of (§4):
git config core.hooksPath .githooks                               # no pre-commit framework
pre-commit install --hook-type pre-commit --hook-type prepare-commit-msg  # pre-commit repo
mgf-fed catch-up --apply      # ensure .gitignore carries the standard lines
```

## Verify

```sh
git check-ignore my_stuff/        # → "my_stuff/"  (ignored)   ✓
git config --get commit.template  # → .gitmessage              ✓
# AG-03 hook active, per wiring:
git config --get core.hooksPath   # → .githooks             (§4a)  ✓
ls .git/hooks/prepare-commit-msg  # → exists (pre-commit)   (§4b)  ✓
git commit --allow-empty -m "chore: hook check" \
  && git log -1 --format=%B | grep Signed-off-by   # → trailer present ✓  (then drop the test commit)
mgf-fed app-doctor                 # → AG-18 checks pass
```
