Skip to content

Commit ac1392a

Browse files
chore(hooks): version-controlled pre-commit + SPDX split by file-type (#48)
Ports the documented estate hook fix here and makes hooks reproducible. ## What - **Committed, installable hook**: `hooks/pre-commit` + `hooks/install.sh` (sets `core.hooksPath=hooks`). Fixes the *"hooks not reproducible across clones"* finding — the old enforcer lived only in un-versioned `.git/hooks/`. - **SPDX split by file-type** (LICENCE-POLICY Rule 1): prose docs (`.md`/`.adoc`) → **CC-BY-SA-4.0**, code/config → **MPL-2.0**. The old hook demanded MPL on *every* doc, contradicting the estate docs-licence policy and rejecting correctly CC-BY-SA-licensed docs. - **Owner-string check kept strict** on all files (deliberate LLM-edit diagnostic). - **No-C / SNIFs / Idris-ABI** policies preserved unchanged. ## Verified - CC-BY-SA doc + owner → ✓ accepted - MPL-only doc → ✓ correctly rejected (docs must be CC-BY-SA now) - MPL code + owner → ✓ accepted Installed locally (`core.hooksPath=hooks`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e8b8079 commit ac1392a

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

hooks/install.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+
# SPDX-License-Identifier: MPL-2.0
3+
# Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# Install the version-controlled git hooks for this repo.
6+
# Points git at the committed hooks/ directory (reproducible across clones),
7+
# rather than copying into the un-versioned .git/hooks/.
8+
9+
set -euo pipefail
10+
cd "$(git rev-parse --show-toplevel)"
11+
12+
git config core.hooksPath hooks
13+
chmod +x hooks/pre-commit
14+
15+
echo "Installed: core.hooksPath -> hooks/ (pre-commit active)"

hooks/pre-commit

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# Pre-commit enforcer: licence/owner headers + architecture policies.
6+
# Canonical, version-controlled hook. Install with: bash hooks/install.sh
7+
# (sets core.hooksPath=hooks so this file is used directly).
8+
9+
# 1. License Check
10+
# Determine if this is a SHARED (AGPL) or OWNED (MPL) repo
11+
REMOTE_URL=$(git remote get-url origin 2>/dev/null)
12+
DIR_NAME=$(basename "$(pwd)")
13+
14+
if [[ "$REMOTE_URL" == *"JoshuaJewell"* || "$DIR_NAME" == boj-* || "$DIR_NAME" == bofj-* ]]; then
15+
# Shared repos: components retain their original licences; verify root LICENSE is AGPL.
16+
if ! grep -q "Affero General Public License" LICENSE 2>/dev/null; then
17+
echo "ERROR: Shared repository must have AGPL-3.0-or-later LICENSE file."
18+
exit 1
19+
fi
20+
else
21+
EXPECTED_OWNER="Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"
22+
23+
# Check staged files for SPDX header, split by file-type per LICENCE-POLICY Rule 1:
24+
# prose docs (.md/.adoc) -> CC-BY-SA-4.0 ; everything else (code/config) -> MPL-2.0.
25+
# Owner-string is required on all (deliberate LLM-edit diagnostic).
26+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(zig|ex|idr|eph|py|js|ts|rs|c|h|adoc|md)$')
27+
28+
for file in $STAGED_FILES; do
29+
case "$file" in
30+
*.md|*.adoc) FILE_LICENSE="CC-BY-SA-4.0" ;;
31+
*) FILE_LICENSE="MPL-2.0" ;;
32+
esac
33+
if ! grep -q "SPDX-License-Identifier: $FILE_LICENSE" "$file"; then
34+
echo "ERROR: File $file is missing correct SPDX-License-Identifier: $FILE_LICENSE"
35+
exit 1
36+
fi
37+
if ! grep -q "$EXPECTED_OWNER" "$file"; then
38+
echo "ERROR: File $file is missing correct Owner: $EXPECTED_OWNER"
39+
exit 1
40+
fi
41+
done
42+
fi
43+
44+
# 2. No C Policy — all APIs/FFIs must be Zig (exceptions: proven, boj-server-cartridges)
45+
if [[ "$DIR_NAME" != "proven" && "$DIR_NAME" != "boj-server-cartridges" ]]; then
46+
STAGED_C_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.c$')
47+
if [ -n "$STAGED_C_FILES" ]; then
48+
echo "ERROR: Strict No C Policy. C files are not allowed in this repository."
49+
echo "Offending files: $STAGED_C_FILES"
50+
exit 1
51+
fi
52+
fi
53+
54+
# 3. SNIFs Policy — never raw NIFs; always Safe NIFs
55+
STAGED_BEAM_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ex|exs|erl)$')
56+
for file in $STAGED_BEAM_FILES; do
57+
if grep -qE "erlang:load_nif|:erlang\.load_nif" "$file"; then
58+
echo "ERROR: Raw NIFs are forbidden. Use SNIFs (Safe NIFs) from the snifs repository."
59+
exit 1
60+
fi
61+
done
62+
63+
# 4. Idris 2 ABI Policy — ABIs/capability matchers should be Idris 2 (Zig allowed for the FFI bridge)
64+
STAGED_ABI_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -i "abi")
65+
for file in $STAGED_ABI_FILES; do
66+
if [[ "$file" != *.idr && "$file" != *.zig && "$file" != *.adoc && "$file" != *.md ]]; then
67+
echo "WARNING: ABI definition found in non-Idris/Zig file: $file"
68+
fi
69+
done
70+
71+
exit 0

0 commit comments

Comments
 (0)