-
Notifications
You must be signed in to change notification settings - Fork 0
feat(ops): agent identity scheme (trailer hook + PR wrapper + docs) #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # Agent identity in git & GitHub | ||
|
|
||
| How FlopBut's local agents are distinguished in git history and on GitHub. | ||
|
|
||
| > Decision: [FLO-209](/FLO/issues/FLO-209) — env-aliases + commit trailers + PR | ||
| > attribution. We do **not** create separate GitHub accounts. Every push and PR | ||
| > happens under the board's token; agents are distinguished at the commit-author, | ||
| > trailer, and PR-metadata level. | ||
|
|
||
| ## Canonical identities | ||
|
|
||
| Slug is the lowercased name. The email is a label for git history only — it is | ||
| intentionally **not** a mailbox and **not** linked to any GitHub account. The | ||
| domain `agents.flopbut.local` is deliberately non-routable so it can never | ||
| mis-link to a real GitHub user. | ||
|
|
||
| | Agent | NAME | Email | | ||
| |----------|------------|--------------------------------| | ||
| | Prospero | `Prospero` | `prospero@agents.flopbut.local` | | ||
| | Daedalus | `Daedalus` | `daedalus@agents.flopbut.local` | | ||
| | Wayland | `Wayland` | `wayland@agents.flopbut.local` | | ||
| | Aldric | `Aldric` | `aldric@agents.flopbut.local` | | ||
| | Soren | `Soren` | `soren@agents.flopbut.local` | | ||
| | Lyra | `Lyra` | `lyra@agents.flopbut.local` | | ||
| | Iris | `Iris` | `iris@agents.flopbut.local` | | ||
| | Orion | `Orion` | `orion@agents.flopbut.local` | | ||
|
|
||
| If the board later wants a real domain, change the values everywhere — the | ||
| scheme is unchanged. | ||
|
|
||
| ## 1. Env aliases (author/committer identity) | ||
|
|
||
| Each local agent's Paperclip `adapterConfig.env` carries four variables: | ||
|
|
||
| ```json | ||
| "env": { | ||
| "GIT_AUTHOR_NAME": { "type": "plain", "value": "Wayland" }, | ||
| "GIT_AUTHOR_EMAIL": { "type": "plain", "value": "wayland@agents.flopbut.local" }, | ||
| "GIT_COMMITTER_NAME": { "type": "plain", "value": "Wayland" }, | ||
| "GIT_COMMITTER_EMAIL": { "type": "plain", "value": "wayland@agents.flopbut.local" } | ||
| } | ||
| ``` | ||
|
|
||
| `GIT_*` env vars override `git config` in **every** repository, so no per-repo | ||
| setup is needed. New values take effect on the agent's **next** run (the current | ||
| process inherited its env at spawn). | ||
|
|
||
| ### Rolling it out — agents self-apply | ||
|
|
||
| Each agent runs this once, inside a heartbeat: | ||
|
|
||
| ```bash | ||
| scripts/agent-set-git-identity.sh | ||
| ``` | ||
|
|
||
| It reads the agent's own name from `/api/agents/me`, derives the email, and | ||
| **merges** the four `GIT_*` vars into its own `adapterConfig.env`. Idempotent. | ||
|
|
||
| > **Why self-apply, not a central rollout?** A `PATCH` to `adapterConfig` | ||
| > *replaces* the whole `env` object, and the API redacts other agents' configs | ||
| > and rejects cross-agent edits unless the caller holds `agents:create` (most | ||
| > agents, including the CTO, do not). Even an authorized caller cannot read | ||
| > another agent's *secret* env (e.g. an adapter API key) to merge it back, so a | ||
| > blind cross-agent write would silently wipe secrets. An agent editing itself | ||
| > can read its full env and merge safely — so self-apply is both the only | ||
| > permitted path and the only safe one. | ||
|
|
||
| ## 2. Commit trailers | ||
|
|
||
| In addition to the mandatory skill trailer: | ||
|
|
||
| ``` | ||
| Co-Authored-By: Paperclip <noreply@paperclip.ing> | ||
| ``` | ||
|
|
||
| each commit gets a second co-author line for the authoring agent: | ||
|
|
||
| ``` | ||
| Co-authored-by: Wayland <wayland@agents.flopbut.local> | ||
| ``` | ||
|
|
||
| The robust way to add this is the **`prepare-commit-msg` hook** in | ||
| `scripts/git-hooks/`, which reads `$GIT_AUTHOR_NAME` / `$GIT_AUTHOR_EMAIL` and | ||
| appends the trailer idempotently (it never duplicates). Install it per repo: | ||
|
|
||
| ```bash | ||
| scripts/install-agent-git-hooks.sh # current repo | ||
| scripts/install-agent-git-hooks.sh /path/repo # another checkout | ||
| ``` | ||
|
|
||
| This points `core.hooksPath` at `scripts/git-hooks`. The hook is a no-op when no | ||
| agent identity is in the env, so it is safe for human contributors too. | ||
|
|
||
| ## 3. PR attribution | ||
|
|
||
| Because PRs are always opened under the board's token, the authoring agent is | ||
| recorded with: | ||
|
|
||
| - an **`Agent: <Name>`** line in the PR body, and | ||
| - an **`agent:<slug>`** label (e.g. `agent:wayland`). | ||
|
|
||
| Use the wrapper, which sets both from `$GIT_AUTHOR_NAME` and creates the label | ||
| idempotently: | ||
|
|
||
| ```bash | ||
| scripts/agent-pr.sh --title "feat: ..." --body "Closes FLO-NNN" --base main | ||
| ``` | ||
|
|
||
| Or do it by hand: | ||
|
|
||
| ```bash | ||
| gh label create "agent:wayland" --color BFD4F2 -f | ||
| gh pr create --title "..." --body $'...\n\nAgent: Wayland' --label agent:wayland | ||
| ``` | ||
|
|
||
| ## Scope | ||
|
|
||
| Repositories in scope: `cotel`, `ksef-docs`, and any new repo under | ||
| `~/projects/`. Run `scripts/install-agent-git-hooks.sh` once per repo (or per | ||
| worktree) to enable the trailer hook there. | ||
|
|
||
| `pi_local` (Orion) is in `error`/trial status; its env aliases are applied last | ||
| or skipped until the adapter is healthy. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # agent-pr.sh — open a PR with FlopBut agent attribution. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wraps `gh pr create`. Since every PR is opened under the board's token, we | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # attribute the authoring agent via: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1. an `Agent: <Name>` line appended to the PR body, and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2. an `agent:<slug>` label (created idempotently before the PR). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # The agent name comes from $GIT_AUTHOR_NAME (set by the Paperclip adapter env). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # See docs/operations/agent-identity.md. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Usage: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # scripts/agent-pr.sh --title "feat: ..." --body "..." [extra gh pr create flags] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Any flags not consumed here are passed straight through to `gh pr create`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NAME="${GIT_AUTHOR_NAME:-}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -z "$NAME" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "error: GIT_AUTHOR_NAME is not set — cannot attribute the PR to an agent." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " Run inside a Paperclip agent env, or export GIT_AUTHOR_NAME first." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SLUG="$(printf '%s' "$NAME" | tr '[:upper:]' '[:lower:]')" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LABEL="agent:${SLUG}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TITLE="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BODY="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PASS=() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while [ $# -gt 0 ]; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "$1" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --title) TITLE="$2"; shift 2 ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --body) BODY="$2"; shift 2 ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *) PASS+=("$1"); shift ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Argument parsing doesn't handle The parser only handles space-separated flags ( Additionally, 🛡️ Proposed fix with validation and better parsing TITLE=""
BODY=""
PASS=()
while [ $# -gt 0 ]; do
case "$1" in
- --title) TITLE="$2"; shift 2 ;;
- --body) BODY="$2"; shift 2 ;;
+ --title)
+ if [ $# -lt 2 ]; then
+ echo "error: --title requires a value" >&2
+ exit 1
+ fi
+ TITLE="$2"; shift 2 ;;
+ --title=*) TITLE="${1#--title=}"; shift ;;
+ --body)
+ if [ $# -lt 2 ]; then
+ echo "error: --body requires a value" >&2
+ exit 1
+ fi
+ BODY="$2"; shift 2 ;;
+ --body=*) BODY="${1#--body=}"; shift ;;
*) PASS+=("$1"); shift ;;
esac
done
+
+if [ -z "$TITLE" ]; then
+ echo "error: --title is required" >&2
+ exit 1
+fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Append the attribution line to the body (idempotent). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ATTR="Agent: ${NAME}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! printf '%s' "$BODY" | grep -qiF "$ATTR"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -n "$BODY" ]; then BODY="${BODY}"$'\n\n'"${ATTR}"; else BODY="${ATTR}"; fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create the label idempotently (-f updates if it already exists). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gh label create "$LABEL" --color BFD4F2 --description "PR authored by agent ${NAME}" -f >/dev/null 2>&1 || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exec gh pr create --title "$TITLE" --body "$BODY" --label "$LABEL" "${PASS[@]}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # agent-set-git-identity.sh — give the running agent its own git identity. | ||
| # | ||
| # All FlopBut agents push through the board's single GitHub token, so commits | ||
| # and PRs would otherwise all look like the same author. This script makes each | ||
| # agent self-attribute by writing GIT_AUTHOR_* / GIT_COMMITTER_* into ITS OWN | ||
| # Paperclip adapter env, so every future run commits as that agent. | ||
| # | ||
| # Why self-apply (not a central rollout): the Paperclip API redacts other | ||
| # agents' adapterConfig and rejects cross-agent edits (needs `agents:create`). | ||
| # Each agent, however, can read its own full config — including secret env like | ||
| # adapter API keys — and merge the GIT_* vars in WITHOUT wiping those secrets. | ||
| # That makes self-apply both the only permitted path and the only safe one. | ||
| # | ||
| # Idempotent: re-running is a no-op once the four vars already match. | ||
| # | ||
| # Requires the Paperclip runtime env (auto-injected in a heartbeat run): | ||
| # PAPERCLIP_AGENT_ID, PAPERCLIP_API_URL, PAPERCLIP_API_KEY | ||
| # Optional: PAPERCLIP_RUN_ID (added to the audit trail when present). | ||
| # | ||
| # Usage (inside an agent heartbeat): | ||
| # scripts/agent-set-git-identity.sh | ||
| # | ||
| # See docs/operations/agent-identity.md. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| : "${PAPERCLIP_AGENT_ID:?must run inside a Paperclip agent (PAPERCLIP_AGENT_ID unset)}" | ||
| : "${PAPERCLIP_API_URL:?PAPERCLIP_API_URL unset}" | ||
| : "${PAPERCLIP_API_KEY:?PAPERCLIP_API_KEY unset}" | ||
|
|
||
| EMAIL_DOMAIN="agents.flopbut.local" | ||
|
|
||
| python3 - "$PAPERCLIP_AGENT_ID" "$EMAIL_DOMAIN" <<'PY' | ||
| import json, os, sys, urllib.request | ||
|
|
||
| agent_id, domain = sys.argv[1], sys.argv[2] | ||
| api = os.environ["PAPERCLIP_API_URL"].rstrip("/") | ||
| key = os.environ["PAPERCLIP_API_KEY"] | ||
| run = os.environ.get("PAPERCLIP_RUN_ID") | ||
|
|
||
| def call(method, path, body=None): | ||
| data = json.dumps(body).encode() if body is not None else None | ||
| headers = {"Authorization": f"Bearer {key}"} | ||
| if data is not None: | ||
| headers["Content-Type"] = "application/json" | ||
| if run: | ||
| headers["X-Paperclip-Run-Id"] = run | ||
| req = urllib.request.Request(f"{api}{path}", data=data, method=method, headers=headers) | ||
| with urllib.request.urlopen(req) as r: | ||
| return json.load(r) | ||
|
|
||
| me = call("GET", "/api/agents/me") | ||
| name = me.get("name") | ||
| if not name: | ||
| sys.exit("could not resolve agent name from /api/agents/me") | ||
| slug = name.strip().lower() | ||
| email = f"{slug}@{domain}" | ||
|
|
||
| ac = me.get("adapterConfig") or {} | ||
| env = dict(ac.get("env") or {}) # readable for SELF — preserve existing (incl. secrets) | ||
| want = { | ||
| "GIT_AUTHOR_NAME": name, | ||
| "GIT_AUTHOR_EMAIL": email, | ||
| "GIT_COMMITTER_NAME": name, | ||
| "GIT_COMMITTER_EMAIL": email, | ||
| } | ||
|
|
||
| def current(k): | ||
| v = env.get(k) | ||
| return v.get("value") if isinstance(v, dict) else v | ||
|
|
||
| if all(current(k) == v for k, v in want.items()): | ||
| print(f"git identity already set for {name} <{email}> — no change") | ||
| sys.exit(0) | ||
|
|
||
| for k, v in want.items(): | ||
| env[k] = {"type": "plain", "value": v} | ||
| ac["env"] = env | ||
|
|
||
| call("PATCH", f"/api/agents/{agent_id}", {"adapterConfig": ac}) | ||
| print(f"set git identity: {name} <{email}> (applies on the next run)") | ||
| PY |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # prepare-commit-msg — append the per-agent co-author trailer idempotently. | ||
| # | ||
| # Part of the FlopBut agent-identity scheme (see docs/operations/agent-identity.md). | ||
| # Each local agent runs with GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL injected by its | ||
| # Paperclip adapter env. This hook turns those into a second co-author trailer so | ||
| # the agent is credited in the commit body in addition to the author/committer | ||
| # fields. The mandatory `Co-Authored-By: Paperclip <noreply@paperclip.ing>` line | ||
| # is added by the agent itself per the skill rule and is left untouched here. | ||
| # | ||
| # Idempotent: re-running never duplicates a trailer. Safe to install in every repo. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| COMMIT_MSG_FILE="$1" | ||
| COMMIT_SOURCE="${2:-}" | ||
|
|
||
| # Skip for merge/squash commits and when amending an existing message — those | ||
| # already carry trailers or are machine-generated. | ||
| case "$COMMIT_SOURCE" in | ||
| merge|squash) exit 0 ;; | ||
| esac | ||
|
|
||
| NAME="${GIT_AUTHOR_NAME:-}" | ||
| EMAIL="${GIT_AUTHOR_EMAIL:-}" | ||
|
|
||
| # No agent identity in env → nothing to attribute. Leave the message untouched. | ||
| [ -n "$NAME" ] && [ -n "$EMAIL" ] || exit 0 | ||
|
|
||
| TRAILER="Co-authored-by: ${NAME} <${EMAIL}>" | ||
|
|
||
| # Already present (case-insensitive on the trailer key, exact on name+email)? | ||
| if grep -qiF "$TRAILER" "$COMMIT_MSG_FILE"; then | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Ensure a blank line separates the body from the trailer block, then append. | ||
| if [ -s "$COMMIT_MSG_FILE" ] && [ -n "$(tail -c1 "$COMMIT_MSG_FILE")" ]; then | ||
| printf '\n' >> "$COMMIT_MSG_FILE" | ||
| fi | ||
| printf '%s\n' "$TRAILER" >> "$COMMIT_MSG_FILE" | ||
|
Comment on lines
+38
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blank line logic doesn't ensure trailer separation. The comment at line 38 states the intent to "ensure a blank line separates the body from the trailer block," but the implementation only ensures the file ends with a single newline, not two. Git trailers should be preceded by a blank line. Current behavior:
Expected:
🐛 Proposed fix-# Ensure a blank line separates the body from the trailer block, then append.
-if [ -s "$COMMIT_MSG_FILE" ] && [ -n "$(tail -c1 "$COMMIT_MSG_FILE")" ]; then
- printf '\n' >> "$COMMIT_MSG_FILE"
-fi
-printf '%s\n' "$TRAILER" >> "$COMMIT_MSG_FILE"
+# Ensure a blank line separates the body from the trailer block, then append.
+if [ -s "$COMMIT_MSG_FILE" ]; then
+ # Add one newline if file doesn't end with one, then always add blank line
+ if [ -n "$(tail -c1 "$COMMIT_MSG_FILE")" ]; then
+ printf '\n' >> "$COMMIT_MSG_FILE"
+ fi
+ printf '\n' >> "$COMMIT_MSG_FILE"
+fi
+printf '%s\n' "$TRAILER" >> "$COMMIT_MSG_FILE"🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # install-agent-git-hooks.sh — wire the agent-identity hooks into a repo. | ||
| # | ||
| # Usage: | ||
| # scripts/install-agent-git-hooks.sh [REPO_DIR] | ||
| # | ||
| # Points the repo's core.hooksPath at scripts/git-hooks so the | ||
| # prepare-commit-msg trailer hook runs on every commit. Idempotent: safe to | ||
| # re-run. Works for the current repo (default) or any other checkout passed as | ||
| # the first argument. See docs/operations/agent-identity.md. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| HOOKS_DIR="${SCRIPT_DIR}/git-hooks" | ||
| REPO_DIR="${1:-$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)}" | ||
|
|
||
| if ! git -C "$REPO_DIR" rev-parse --git-dir >/dev/null 2>&1; then | ||
| echo "error: '$REPO_DIR' is not a git repository" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| chmod +x "$HOOKS_DIR"/* 2>/dev/null || true | ||
| git -C "$REPO_DIR" config core.hooksPath "$HOOKS_DIR" | ||
|
|
||
| echo "Installed agent git hooks for $REPO_DIR" | ||
| echo " core.hooksPath -> $HOOKS_DIR" | ||
| echo "Verify: GIT_AUTHOR_NAME=Test GIT_AUTHOR_EMAIL=test@agents.flopbut.local git commit --allow-empty -m demo" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent trailer capitalization and missing language specifiers.
Lines 56 and 62 show different capitalizations: "Co-Authored-By" vs "Co-authored-by". While Git treats trailer keys case-insensitively, the documentation should be consistent. The hook at line 31 in
scripts/git-hooks/prepare-commit-msgoutputs "Co-authored-by" (capital C, lowercase a/b), so this example should match.Additionally, both code blocks are missing language specifiers, which reduces syntax highlighting and accessibility.
📝 Proposed fix
each commit gets a second co-author line for the authoring agent:
-
-Co-authored-by: Wayland <wayland@agents.flopbut.local> +text+Co-Authored-By: Wayland wayland@agents.flopbut.local
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 55-55: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 61-61: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents