Skip to content

Commit daf194f

Browse files
committed
feat(git): attribution guard scripts and cursor rules
1 parent ed2a13e commit daf194f

12 files changed

Lines changed: 445 additions & 0 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
description: Load gitignored private attribution lessons before any git commit
3+
alwaysApply: true
4+
---
5+
6+
# Private attribution (never publish tokens)
7+
8+
Before `git commit` or `git push`:
9+
10+
1. Read **`.cursor/private/agent-lesson-git-attribution.md`** (gitignored — not on GitHub).
11+
2. Read **`.cursor/private/banned-attribution-patterns`** for the live token list.
12+
3. If missing: `bash scripts/cursor/install-user-git-environment.sh` (pulls from orama + sync).
13+
4. **Never** copy banned tokens into tracked files, commit messages, PR text, or LESSONS.
14+
15+
Public policy (no token examples): `../orama-system/docs/wiki/08-git-hygiene-and-branching.md`
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
description: Re-introducing expunged commit attribution forces destructive main rewrites — never do it
3+
alwaysApply: true
4+
---
5+
6+
# Attribution expunge is fragile — do not undo it
7+
8+
## The loop you must break
9+
10+
Every time an agent adds **forbidden** `Co-authored-by` trailers (listed in `.cursor/private/banned-attribution-patterns`, never on GitHub), the owner must:
11+
12+
1. Rewrite history (`bash scripts/git/expunge-all-workspace-repos.sh` or per-repo expunge)
13+
2. Force-push **`main` and every branch** again
14+
15+
That is destructive for all collaborators. **Do not cause another rewrite.**
16+
17+
## Non-negotiable agent behavior
18+
19+
| Do | Do not |
20+
|----|--------|
21+
| Read `.cursor/private/agent-lesson-git-attribution.md` before any git write | Copy forbidden tokens into tracked files, commits, PRs, or LESSONS |
22+
| Commit with `bash scripts/git/commit-clean.sh -m "..."` after `git add` | Raw `git commit` in Cursor cloud |
23+
| Push with `bash scripts/git/publish-clean-branch.sh <branch> main origin` | Raw `git push` without audit |
24+
| Run `bash scripts/git/daily-attribution-guard.sh` at session start | "Document" forbidden emails in repo docs so you remember |
25+
26+
## Session checklist (every day)
27+
28+
```bash
29+
bash scripts/git/daily-attribution-guard.sh
30+
bash scripts/git/install-local-hooks.sh
31+
```
32+
33+
If CI or scan fails: fix before any new commits. Never patch by echoing forbidden identities in commit messages.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
description: Never add Cursor or third-party co-author trailers to git commits
3+
alwaysApply: true
4+
---
5+
6+
# Git commit attribution (cloud + agent)
7+
8+
When committing in this repository:
9+
10+
1. **Never** add `Co-authored-by:`, `Made-with: Cursor`, or any Cursor/agent attribution trailer to commit messages.
11+
2. **Cursor cloud** installs guards automatically: `bash scripts/cursor/install-user-git-environment.sh` (also via `.cursor/environment.json` → `cloud-bootstrap.sh` on VM start).
12+
3. **Every clone** must run `bash scripts/git/install-local-hooks.sh` (enforced by pre-commit + CI).
13+
4. Use only approved **author** identity: `cyre <Lawrence@cyre.me>` or `cyre <diazMelgarejo@gmail.com>` (or `Codex <codex@openai.com>`). Never `Cursor Agent` as author.
14+
5. **Before every push:** `pre-push` runs attribution audit — banned `Co-authored-by` never reaches GitHub.
15+
6. To publish: `bash scripts/git/publish-clean-branch.sh <branch>` (neutralize → verify → audit → push).
16+
7. **Banned identities:** read `.cursor/private/agent-lesson-git-attribution.md` (gitignored).
17+
Do not echo banned tokens in tracked docs. Use `bash scripts/git/commit-clean.sh` if Cursor injects trailers.
18+
8. **Never** `git push` without hooks installed (`bash scripts/git/install-local-hooks.sh`).
19+
20+
Desktop (optional): Cursor Settings → Agents → Attribution → OFF (IDE/CLI only; cloud may still inject until guards run).

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
/testfixture
66
*.test.exe
77

8+
# Cursor private attribution (never commit)
9+
.cursor/private/
10+
811
# Frontend
912
node_modules/
1013
frontend/dist/
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env bash
2+
# Apply mandatory git hooks to Perpetua-Tools + sibling repos (Cursor session + manual).
3+
set -euo pipefail
4+
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
PT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7+
DISABLE="$SCRIPT_DIR/disable-cursor-commit-attribution.sh"
8+
INSTALL="$SCRIPT_DIR/install-local-hooks.sh"
9+
SYNC="$SCRIPT_DIR/sync-attribution-guard-scripts.sh"
10+
11+
OPENCLAW_HOME="${OPENCLAW_HOME:-$HOME/openclaw-v1}"
12+
13+
resolve_git_repo() {
14+
local r="$1"
15+
[[ -n "$r" ]] || return 1
16+
[[ "$r" == *'${'* ]] && return 1
17+
[[ -d "$r/.git" ]] || return 1
18+
local abs
19+
abs="$(cd "$r" && pwd)" || return 1
20+
printf '%s' "$abs"
21+
}
22+
23+
raw_candidates=(
24+
"$PT_ROOT"
25+
"${PERPETUA_TOOLS_PATH:-$PT_ROOT}"
26+
"${PERPETUA_TOOLS_ROOT:-$PT_ROOT}"
27+
"${ORAMA_SYSTEM_PATH:-$OPENCLAW_HOME/orama-system}"
28+
"${ALPHACLAW_INSTALL_DIR:-$OPENCLAW_HOME/AlphaClaw}"
29+
"/agent/repos/Perpetua-Tools"
30+
"/agent/repos/orama-system"
31+
"/agent/repos/AlphaClaw"
32+
"/agent/repos/periscope"
33+
)
34+
35+
if [[ -d /agent/repos ]]; then
36+
for d in /agent/repos/*; do
37+
raw_candidates+=("$d")
38+
done
39+
fi
40+
41+
declare -A seen=()
42+
unique=()
43+
for r in "${raw_candidates[@]}"; do
44+
resolved="$(resolve_git_repo "$r" 2>/dev/null || true)"
45+
[[ -n "$resolved" ]] || continue
46+
if [[ -n "${seen[$resolved]+x}" ]]; then
47+
continue
48+
fi
49+
seen[$resolved]=1
50+
unique+=("$resolved")
51+
done
52+
53+
if [[ -x "$SYNC" ]]; then
54+
for r in "${unique[@]}"; do
55+
[[ "$r" == "$PT_ROOT" ]] && continue
56+
bash "$SYNC" "$r" 2>/dev/null || true
57+
done
58+
fi
59+
60+
for r in "${unique[@]}"; do
61+
bash "$DISABLE" "$r"
62+
if [[ -x "$INSTALL" && -x "$r/scripts/git/ensure_hooks_installed.sh" ]]; then
63+
bash "$INSTALL" "$r" || echo "warn: install-local-hooks failed: $r" >&2
64+
elif [[ -x "$DISABLE" ]]; then
65+
:
66+
fi
67+
git -C "$r" config --local user.name "cyre" 2>/dev/null || true
68+
git -C "$r" config --local user.email "Lawrence@cyre.me" 2>/dev/null || true
69+
done
70+
71+
echo "OK: mandatory hooks applied for ${#unique[@]} repo(s)"

scripts/git/commit-clean.sh

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env bash
2+
# Create a commit without running git commit hooks (avoids Cursor co-author injection).
3+
set -euo pipefail
4+
5+
usage() {
6+
cat <<'EOF'
7+
Usage: scripts/git/commit-clean.sh -m "message" [--amend]
8+
9+
Stages must already reflect the desired tree (git add …).
10+
Uses git commit-tree so Cursor commit-msg hooks never run.
11+
12+
Environment overrides: GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL
13+
EOF
14+
}
15+
16+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || {
17+
echo "error: not inside a git repository" >&2
18+
exit 1
19+
}
20+
cd "$repo_root"
21+
22+
message=""
23+
amend=0
24+
while [[ $# -gt 0 ]]; do
25+
case "$1" in
26+
-m)
27+
message="${2:-}"
28+
shift 2
29+
;;
30+
--amend)
31+
amend=1
32+
shift
33+
;;
34+
-h|--help)
35+
usage
36+
exit 0
37+
;;
38+
*)
39+
echo "error: unknown argument: $1" >&2
40+
usage
41+
exit 1
42+
;;
43+
esac
44+
done
45+
46+
[[ -n "$message" ]] || {
47+
echo "error: -m message is required" >&2
48+
usage
49+
exit 1
50+
}
51+
52+
author_name="${GIT_AUTHOR_NAME:-$(git config user.name)}"
53+
author_email="${GIT_AUTHOR_EMAIL:-$(git config user.email)}"
54+
[[ -n "$author_name" && -n "$author_email" ]] || {
55+
echo "error: configure user.name and user.email or set GIT_AUTHOR_*" >&2
56+
exit 1
57+
}
58+
author_email_lc="$(printf '%s' "$author_email" | tr '[:upper:]' '[:lower:]')"
59+
author_domain_ok() {
60+
local email_lc="$1"
61+
local domain="${email_lc#*@}"
62+
[[ -z "$domain" || "$domain" == "$email_lc" ]] && return 1
63+
case "$domain" in
64+
openai.com|*.openai.com|anthropic.com|*.anthropic.com|cursor.com|*.cursor.com|cursor.sh|*.cursor.sh|google.com|*.google.com|google.dev|*.google.dev|github.com|*.github.com|microsoft.com|*.microsoft.com|azure.com|*.azure.com|perplexity.ai|*.perplexity.ai|x.ai|*.x.ai)
65+
return 0
66+
;;
67+
esac
68+
return 1
69+
}
70+
case "$author_email_lc" in
71+
diazmelgarejo@gmail.com|lawrence@cyre.me|codex@openai.com)
72+
;;
73+
*)
74+
if ! author_domain_ok "$author_email_lc"; then
75+
echo "error: commit author email must be diazMelgarejo@gmail.com, Lawrence@cyre.me, codex@openai.com, or a well-known AI/vendor domain" >&2
76+
exit 1
77+
fi
78+
;;
79+
esac
80+
81+
if git diff-index --quiet HEAD -- 2>/dev/null && [[ "$amend" -eq 0 ]]; then
82+
if git diff-index --quiet --cached HEAD -- 2>/dev/null; then
83+
echo "error: nothing staged to commit" >&2
84+
exit 1
85+
fi
86+
fi
87+
88+
tree="$(git write-tree)"
89+
if [[ "$amend" -eq 1 ]]; then
90+
parent="$(git rev-parse HEAD^)"
91+
else
92+
if git rev-parse HEAD >/dev/null 2>&1; then
93+
parent="$(git rev-parse HEAD)"
94+
else
95+
parent=""
96+
fi
97+
fi
98+
99+
if [[ -n "$parent" ]]; then
100+
new_sha="$(
101+
printf '%s\n' "$message" |
102+
GIT_AUTHOR_NAME="$author_name" GIT_AUTHOR_EMAIL="$author_email" \
103+
git commit-tree "$tree" -p "$parent" -F -
104+
)"
105+
else
106+
new_sha="$(
107+
printf '%s\n' "$message" |
108+
GIT_AUTHOR_NAME="$author_name" GIT_AUTHOR_EMAIL="$author_email" \
109+
git commit-tree "$tree" -F -
110+
)"
111+
fi
112+
113+
branch="$(git symbolic-ref --short HEAD 2>/dev/null || true)"
114+
if [[ -n "$branch" ]]; then
115+
git update-ref "refs/heads/${branch}" "$new_sha"
116+
else
117+
git update-ref HEAD "$new_sha"
118+
fi
119+
120+
git reset --hard "$new_sha" >/dev/null
121+
echo "$new_sha"

scripts/git/cursor-hooks-id.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
# Resolve Cursor cloud-agent git hooks directory id (base64 of absolute repo path).
3+
set -euo pipefail
4+
5+
cursor_hooks_id() {
6+
local repo_path="${1:?repo path required}"
7+
local abs
8+
abs="$(cd "$repo_path" && pwd)"
9+
python3 - "$abs" <<'PY'
10+
import base64
11+
import sys
12+
13+
print(base64.b64encode(sys.argv[1].encode()).decode().rstrip("="))
14+
PY
15+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
PT="${PERPETUA_TOOLS_PATH:-/agent/repos/Perpetua-Tools}"
4+
exec bash "$PT/scripts/git/daily-attribution-guard.sh"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
# Disable Cursor cloud-agent automatic Co-authored-by injection for one git repo.
3+
set -euo pipefail
4+
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
# shellcheck source=cursor-hooks-id.sh
7+
source "$SCRIPT_DIR/cursor-hooks-id.sh"
8+
9+
repo="${1:-.}"
10+
repo="$(cd "$repo" && pwd)"
11+
12+
if ! git -C "$repo" rev-parse --git-dir >/dev/null 2>&1; then
13+
echo "skip: not a git repo: $repo" >&2
14+
exit 0
15+
fi
16+
17+
git_dir="$(git -C "$repo" rev-parse --git-dir)"
18+
hooks_dir="$(cd "$git_dir" && pwd)/hooks"
19+
mkdir -p "$hooks_dir"
20+
21+
# 1) Neutralize Cursor-managed co-author hook (overwrite; chmod -x alone is insufficient).
22+
NEUTRALIZE="$SCRIPT_DIR/neutralize-cursor-coauthor-hook.sh"
23+
if [[ -x "$NEUTRALIZE" ]]; then
24+
bash "$NEUTRALIZE" --repo "$repo"
25+
else
26+
ws_id="$(cursor_hooks_id "$repo")"
27+
coauthor_hook="${HOME}/.cursor/agent-hooks/${ws_id}/commit-msg.cursor.co-author"
28+
if [[ -f "$coauthor_hook" ]]; then
29+
printf '%s\n' '#!/usr/bin/env bash' '# Neutralized — no Co-authored-by injection.' 'exit 0' >"$coauthor_hook"
30+
chmod -x "$coauthor_hook" 2>/dev/null || true
31+
echo "neutralized: $coauthor_hook"
32+
fi
33+
fi
34+
35+
# 2) Keep mandatory hooks on .githooks (strip runs inside .githooks/commit-msg).
36+
git -C "$repo" config --local core.hooksPath .githooks
37+
38+
# 3) Prefer approved cyre identity when unset locally.
39+
if [[ -z "$(git -C "$repo" config --local user.name 2>/dev/null || true)" ]]; then
40+
git -C "$repo" config --local user.name "cyre"
41+
fi
42+
if [[ -z "$(git -C "$repo" config --local user.email 2>/dev/null || true)" ]]; then
43+
git -C "$repo" config --local user.email "diazMelgarejo@gmail.com"
44+
fi
45+
46+
echo "OK: attribution guards applied in $repo"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# Strip Cursor-injected commit attribution trailers (commit-msg hook).
3+
set -euo pipefail
4+
5+
msg_file="${1:?commit message file required}"
6+
[[ -f "$msg_file" ]] || exit 0
7+
8+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
9+
LIB="${ROOT}/scripts/git/banned_attribution_lib.sh"
10+
if [[ -f "$LIB" ]]; then
11+
# shellcheck source=banned_attribution_lib.sh
12+
source "$LIB"
13+
if banned_patterns_ready "$ROOT"; then
14+
while IFS= read -r token; do
15+
token_esc="$(printf '%s' "$token" | sed 's/[.[\*^$()+?{|]/\\&/g')"
16+
sed -i "/${token_esc}/Id" "$msg_file" 2>/dev/null || true
17+
done < <(list_banned_pattern_tokens "$ROOT")
18+
fi
19+
fi
20+
21+
sed -i \
22+
-e '/^Co-authored-by:.*cursoragent@cursor\.com/Id' \
23+
-e '/^Co-authored-by:.*@bettermind\.ph/Id' \
24+
-e '/^Co-authored-by:.*[Cc]ursor[[:space:]]*</d' \
25+
-e '/^Made-with:.*[Cc]ursor/Id' \
26+
"$msg_file"
27+
28+
sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$msg_file"
29+
exit 0

0 commit comments

Comments
 (0)