diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 79bef6c..21962fb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,6 @@ ## Checklist - [ ] The title follows Conventional Commits, e.g. `feat(api): add athlete height endpoint` -- [ ] No secrets, media or files over 5 MB +- [ ] No secrets - [ ] README.md and AGENTS.md still match how to install, run and test - [ ] DECISIONS.md has a line for any decision made here diff --git a/AGENTS.md b/AGENTS.md index f57443f..212f20d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,11 +32,11 @@ the reasons, is CONTRIBUTING.md in the BuildexLabsAI/.github repository. everything in docs/. Everywhere: no spaces, no non-ASCII characters, and no two names that differ only by letter case. Inside src/, tests/ and scripts/, follow the language's own convention (`snake_case.py`, `Button.tsx`). -- **Never commit** secrets (.env files, keys, tokens), video, audio, PDF, Office documents, - design source files, archives, or any file over 5 MB. Media lives outside git; link to it - from README.md. Small assets the app ships (icons, fonts, wasm) belong in src/ or public/. +- **Never commit** secrets (.env files, keys, tokens). Media, documents and large files may go + into git; GitHub itself refuses any file over 100 MiB. Small assets the app ships (icons, + fonts, wasm) belong in src/ or public/. - **Exceptions** go in `.github/standard-exceptions`, one path per line with its reason: - `docs/contract.pdf # signed contract, required for the audit`. + `prisma # Prisma reads its schema from a folder at the root`. - **Branches.** Never commit to main directly. Name branches `feat/…`, `fix/…`, `docs/…`, `chore/…`, `refactor/…` or `test/…`. Agent tools' own prefixes (`claude/…`, `codex/…`) are accepted. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b717acc..8e151b0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,12 +47,15 @@ BuildexLabsAI block in each repository's AGENTS.md. - **Secrets:** `.env` files, keys, certificates, tokens. Commit `.env.example` with placeholder values instead. If a secret is pushed, rotate it first: deleting the file does not remove it from git history. -- **Media and documents:** video, audio, PDF, Office files, design source files (Photoshop, - Figma, Blender) and archives. -- **Any file over 5 MB.** -Media lives outside git, and README.md links to where it is. Small files the app ships -(icons, fonts, wasm) belong in `src/` or `public/`. +**Allowed:** video, audio, PDF, Office files, design source files (Photoshop, Figma, Blender) +and archives, up to GitHub's limit. The check allows these file types and has no size limit. +Top-level images are rejected: app images go in `src/` or `public/`, other images in `docs/`. + +**GitHub's limit:** GitHub blocks files larger than 100 MiB. Git warns above 50 MiB. + +Small files the app ships (icons, fonts, wasm) belong in `src/` or `public/`. Each repository's +README.md has a "Media and large files" section saying where its media lives. ## Exceptions @@ -60,7 +63,6 @@ When a file really has to break a rule, list it in `.github/standard-exceptions` reason. The reason is required, and the pull request shows it to the reviewer. ``` -docs/contract.pdf # signed contract, required for the audit prisma # Prisma reads its schema from a folder at the root ``` @@ -122,7 +124,7 @@ git clone https://github.com/b1rdmania/readme-generator ~/.claude/skills/readme- | Where | When | What happens | |---|---|---| | Claude Code hook | Claude creates a file | A name that breaks the rules is blocked | -| git pre-commit hook | `git commit` | A commit with a bad new name, media or a file over 5 MB is blocked | +| git pre-commit hook | `git commit` | A commit with a bad new name is blocked | | CI (`standard`) | Every pull request and push to main | The whole repository, the pull request title and a secret scan; problems show as a red X | Run it yourself with `bash .github/scripts/check-standard.sh`. What is enforced on the diff --git a/DECISIONS.md b/DECISIONS.md index a940e87..7ec941a 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -13,3 +13,4 @@ Choices that neither the code nor the git log shows, newest first. One line each - 2026-10-07: Secrets are scanned with the gitleaks binary from its GitHub release, pinned by checksum. GitHub's push protection needs a paid add-on for private repositories, and gitleaks-action needs a license key for organizations. - 2026-10-07: Branches that agent tools create (claude/, codex/) are accepted, because Claude Code names its worktree branches that way. - 2026-10-07: Folder and file names may contain the route symbols frameworks use ([slug], (group), @slot, $param, {-$optional}); UAE-SponSlot's TanStack Router routes need $. +- 2026-10-16: Video, audio, PDF, Office files, design sources, archives and files of any size may be committed; the check rejects files by type only for secrets and operating system junk (images still stay out of the top level), and has no size limit, so GitHub's own 100 MiB block is the only ceiling. Esko lifted the 2026-10-07 ban because project repositories must hold their deliverables, such as a 9.6 MB client proposal PDF: PDFs and files over 5 MB "may and must be there". diff --git a/README.md b/README.md index 28b863f..2b7e12e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ check that enforces the standard in every repository. flowchart TD S[src/check-standard.sh on main] -- copy --> R[Each repository] R -- Claude Code creates a file --> H[Claude Code hook: blocks a name that breaks the rules] - R -- git commit --> P[pre-commit hook: blocks bad new names, media, files over 5 MB] + R -- git commit --> P[pre-commit hook: blocks bad new names] R -- pull request or push to main --> C[standard-check.yml: whole repository, pull request title, secret scan] S -- current check --> C C -- compares the repository's copies with src/ --> W[Warning when a copy is out of date] diff --git a/docs/enforcement.md b/docs/enforcement.md index a595a9c..459a681 100644 --- a/docs/enforcement.md +++ b/docs/enforcement.md @@ -7,7 +7,7 @@ on GitHub Team. |---|---|---|---| | AGENTS.md and CLAUDE.md | Tell every AI agent the rules | Instruction only | Instruction only | | Claude Code hook | Stops Claude from creating a badly named file | Blocks (Claude Code only) | Same | -| git pre-commit hook | Stops a commit with a bad new name, media or a file over 5 MB | Blocks, once switched on in the clone | Same | +| git pre-commit hook | Stops a commit with a bad new name | Blocks, once switched on in the clone | Same | | CI check `standard / check` | Checks the whole repository, the pull request title and secrets | Red X; merging still works | Blocks the merge | | Merge settings | Squash merge only; branches deleted after merge | Works | Works | | Repository creation | Only owners create repositories, so each one starts from the template | Works | Works | diff --git a/src/agents-block.md b/src/agents-block.md index 5dc30ca..1544e32 100644 --- a/src/agents-block.md +++ b/src/agents-block.md @@ -12,11 +12,11 @@ the reasons, is CONTRIBUTING.md in the BuildexLabsAI/.github repository. everything in docs/. Everywhere: no spaces, no non-ASCII characters, and no two names that differ only by letter case. Inside src/, tests/ and scripts/, follow the language's own convention (`snake_case.py`, `Button.tsx`). -- **Never commit** secrets (.env files, keys, tokens), video, audio, PDF, Office documents, - design source files, archives, or any file over 5 MB. Media lives outside git; link to it - from README.md. Small assets the app ships (icons, fonts, wasm) belong in src/ or public/. +- **Never commit** secrets (.env files, keys, tokens). Media, documents and large files may go + into git; GitHub itself refuses any file over 100 MiB. Small assets the app ships (icons, + fonts, wasm) belong in src/ or public/. - **Exceptions** go in `.github/standard-exceptions`, one path per line with its reason: - `docs/contract.pdf # signed contract, required for the audit`. + `prisma # Prisma reads its schema from a folder at the root`. - **Branches.** Never commit to main directly. Name branches `feat/…`, `fix/…`, `docs/…`, `chore/…`, `refactor/…` or `test/…`. Agent tools' own prefixes (`claude/…`, `codex/…`) are accepted. diff --git a/src/check-standard.sh b/src/check-standard.sh index 0f9dc55..e96a773 100755 --- a/src/check-standard.sh +++ b/src/check-standard.sh @@ -15,7 +15,6 @@ export LC_ALL=C -MAX_BYTES=$((5 * 1024 * 1024)) # Besides letters, digits and . _ - this allows the route symbols frameworks put in file names: # [slug] (group) @slot +page $param {-$optional}. # shellcheck disable=SC2016 # the $ is a literal character here, not an expansion @@ -130,12 +129,6 @@ set_kind() { # NAME — sets $kind for file types with their own rule, or emptie if [ -z "$kind" ]; then case "$ext" in pem|key|p12|pfx|jks|keystore) kind=secret ;; - mp4|mov|avi|mkv|webm|m4v|wmv|flv|mpg|mpeg) kind=Video ;; - mp3|wav|aac|flac|ogg|m4a|aif|aiff|wma) kind=Audio ;; - pdf) kind=PDF ;; - doc|docx|ppt|pptx|xls|xlsx|pages|numbers|odt|ods|odp|rtf) kind=Office ;; - psd|ai|fig|sketch|xd|indd|blend|afdesign|afphoto|aep|prproj) kind='Design source' ;; - zip|rar|7z|tar|gz|tgz|bz2|xz|dmg|iso) kind=Archive ;; png|jpg|jpeg|gif|webp|avif|svg|ico|bmp|tif|tiff|heic) kind=image ;; esac fi @@ -172,8 +165,7 @@ check_path() { # PATH — name and file-type rules for one repository-relative p '') ;; secret) report error "$path" "Secrets never go into git (.env files, keys, certificates). Commit .env.example with placeholders instead, and rotate anything already pushed." ;; junk) report error "$path" "Operating system junk file. Delete it and add it to .gitignore." ;; - image) [ "$depth" -eq 1 ] && report error "$path" "Images do not belong at the top level. App assets go in src/ or public/; other media stays outside git." ;; - *) report error "$path" "$kind files do not belong in git. Keep media outside the repository and link it from README.md, or list the file in $EXCEPTIONS_FILE with the reason." ;; + image) [ "$depth" -eq 1 ] && report error "$path" "Images do not belong at the top level. App assets go in src/ or public/, other images in docs/." ;; esac if [ "$depth" -eq 1 ] && [ -z "$kind" ]; then case "$comp" in @@ -193,11 +185,6 @@ note_top_dir() { # PATH — warns once about each unexpected top-level folder report warning "$dir/" "Unexpected top-level folder. Code goes in src/ and documents in docs/. If a tool needs this folder at the root, list it in $EXCEPTIONS_FILE with the reason." } -size_error() { # PATH BYTES - is_excepted "$1" && return - report error "$1" "File is $(awk -v b="$2" 'BEGIN { printf "%.1f", b / 1048576 }') MB; the limit is 5 MB. Large files live outside git (link them from README.md), or list the file in $EXCEPTIONS_FILE with the reason." -} - # --- whole-repository rules --------------------------------------------------------------- check_required() { @@ -227,22 +214,6 @@ check_collisions() { # names in $paths that differ only by letter case, at any f awk '{ l = tolower($0); if (l in seen) { if (seen[l] != $0) print seen[l] "\t" $0 } else seen[l] = $0 }') } -check_index_sizes() { # every file in the git index - local rec meta sha size i=0 - local shas=() names=() - while IFS= read -r -d '' rec; do - meta=${rec%%$'\t'*} - sha=${meta#* }; sha=${sha%% *} - shas+=("$sha"); names+=("${rec#*$'\t'}") - done < <(git ls-files -s -z) - [ "${#shas[@]}" -eq 0 ] && return - while read -r size; do - i=$((i + 1)) - case "$size" in ''|*[!0-9]*) continue ;; esac # e.g. a submodule commit - [ "$size" -gt "$MAX_BYTES" ] && size_error "${names[$((i - 1))]}" "$size" - done < <(printf '%s\n' "${shas[@]}" | git cat-file --batch-check='%(objectsize)') -} - check_drift() { # DIR — compare this repo's copies with the canonical ones in DIR local dir=$1 copy=.github/scripts/check-standard.sh if [ -f AGENTS.md ] && grep -q 'buildexlabs-standard:start' AGENTS.md && @@ -269,7 +240,7 @@ check_pull_request() { # --- modes -------------------------------------------------------------------------------- run_full() { - local p size + local p while IFS= read -r -d '' p; do paths+=("$p"); done < <(git ls-files -z --cached --others --exclude-standard) check_required for p in ${paths[@]+"${paths[@]}"}; do @@ -277,19 +248,12 @@ run_full() { note_top_dir "$p" done check_collisions - check_index_sizes - while IFS= read -r -d '' p; do # new files are not in the index yet - [ -f "$p" ] || continue - size=$(wc -c < "$p") - size=${size//[!0-9]/} # macOS wc pads the number with spaces - [ "$size" -gt "$MAX_BYTES" ] && size_error "$p" "$size" - done < <(git ls-files -z --others --exclude-standard) [ -n "$canonical" ] && check_drift "$canonical" check_pull_request } run_staged() { - local base p size + local base p if git rev-parse -q --verify HEAD >/dev/null 2>&1; then base=HEAD else base=$(git hash-object -t tree /dev/null); fi # first commit: compare with nothing # Only new names are checked, so a commit that edits an old, non-standard file still goes in. @@ -297,10 +261,6 @@ run_staged() { check_path "$p" note_top_dir "$p" done < <(git diff --cached --name-only -z --diff-filter=ACR "$base") - while IFS= read -r -d '' p; do - size=$(git cat-file -s ":$p" 2>/dev/null) || continue - [ "$size" -gt "$MAX_BYTES" ] && size_error "$p" "$size" - done < <(git diff --cached --name-only -z --diff-filter=ACMR "$base") while IFS= read -r -d '' p; do paths+=("$p"); done < <(git ls-files -z) check_collisions } diff --git a/tests/check-standard.test.sh b/tests/check-standard.test.sh index 396fc66..765192f 100755 --- a/tests/check-standard.test.sh +++ b/tests/check-standard.test.sh @@ -107,13 +107,12 @@ git update-index --add --cacheinfo "100644,$blob,src/assets/b.png" run; expect "names differing only by letter case fail" 1 "src/Assets" echo "== media, size, secrets ==" -new_repo; touch docs/demo.mp4; run; expect "video fails" 1 "docs/demo.mp4" -new_repo; touch docs/deck.pdf; run; expect "PDF fails" 1 "docs/deck.pdf" -new_repo; touch scripts/backup.zip; run; expect "archive fails" 1 "scripts/backup.zip" +new_repo; touch docs/demo.mp4 docs/voice.mp3 docs/deck.pdf docs/brief.docx docs/scene.blend scripts/backup.zip +run; expect_clean "video, audio, PDF, Office files, design sources and archives pass" new_repo; touch logo.png; run; expect "image at the top level fails" 1 "logo.png" new_repo; mkdir -p public; touch public/icon-192.png docs/architecture.png; run; expect_clean "images in public and docs pass" -new_repo; head -c 6000000 /dev/zero > src/model.bin; run; expect "file over 5 MB fails" 1 "src/model.bin" -new_repo; head -c 5000000 /dev/zero > src/small.wasm; run; expect_clean "file under 5 MB passes" +new_repo; head -c 10000000 /dev/zero > docs/deck.pdf; run; expect_clean "a 10 MB file passes" +new_repo; head -c 10000000 /dev/zero > docs/deck.pdf; git add -A; run; expect_clean "a 10 MB file in the index passes" new_repo; echo 'KEY=1' > .env; git add -f .env; run; expect "committed .env fails" 1 ".env" new_repo; echo 'KEY=' > .env.example; run; expect_clean ".env.example passes" new_repo; touch src/server.pem; run; expect "key file fails" 1 "src/server.pem" @@ -124,10 +123,10 @@ new_repo; mkdir tasks; touch tasks/todo.md; run; expect "unknown top-level folde new_repo; touch notes.md; run; expect "loose top-level document warns but passes" 0 "notes.md" echo "== exceptions ==" -new_repo; touch docs/contract.pdf -printf 'docs/contract.pdf # signed contract, required for the audit\n' > .github/standard-exceptions +new_repo; touch logo.png +printf 'logo.png # the hosting service reads it from the root\n' > .github/standard-exceptions run; expect_clean "listed exception passes" -new_repo; touch docs/contract.pdf; printf 'docs/contract.pdf\n' > .github/standard-exceptions +new_repo; touch logo.png; printf 'logo.png\n' > .github/standard-exceptions run; expect "exception without a reason fails" 1 "reason" new_repo; mkdir tasks; touch tasks/todo.md printf '# Folders a tool needs at the root\ntasks # the task runner reads it from the root\n' > .github/standard-exceptions @@ -159,7 +158,9 @@ new_repo; touch "docs/my notes.md"; git add -A; run --staged; expect "new bad na new_repo; touch docs/plan.md; git add -A; run --staged; expect_clean "new good name passes" new_repo; touch "docs/old notes.md"; git add -A; git_q commit -qm legacy echo change >> "docs/old notes.md"; git add -A; run --staged; expect_clean "editing a legacy file does not block the commit" -new_repo; head -c 6000000 /dev/zero > src/model.bin; git add -A; run --staged; expect "file over 5 MB blocks the commit" 1 "src/model.bin" +new_repo; head -c 10000000 /dev/zero > docs/deck.pdf; git add -A; run --staged; expect_clean "a 10 MB PDF commits" +new_repo; echo draft > docs/deck.pdf; git add -A; git_q commit -qm draft +head -c 10000000 /dev/zero > docs/deck.pdf; git add -A; run --staged; expect_clean "a committed file grown to 10 MB commits" new_repo; mkdir tasks; touch tasks/todo.md; git add -A; run --staged; expect "new unknown top-level folder warns but passes" 0 "tasks/" dir=$(mktemp -d "$TMP/empty.XXXXXX"); cd "$dir" || exit 1; git init -q -b main; touch "Bad Name.md"; git add -A run --staged; expect "first commit of an empty repository is checked" 1 "Bad Name.md" @@ -169,7 +170,8 @@ new_repo; run_hook "$PWD/My File.md"; expect "blocks a new file with a space" 2 new_repo; run_hook "$PWD/docs/athlete-model.md"; expect_clean "allows a good new file" new_repo; run_hook "$PWD/docs/new folder/plan.md"; expect "blocks a new folder with a space" 2 "new folder" new_repo; run_hook "$PWD/docs/tähti.md"; expect "blocks a non-ASCII name" 2 "docs/t" -new_repo; run_hook "$PWD/docs/demo.mp4"; expect "blocks a video" 2 "docs/demo.mp4" +new_repo; run_hook "$PWD/src/server.pem"; expect "blocks a key file" 2 "src/server.pem" +new_repo; run_hook "$PWD/docs/deck.pdf"; expect_clean "allows a PDF" new_repo; mkdir -p src/assets; touch src/assets/a.png; git add -A; git_q commit -qm assets run_hook "$PWD/src/Assets/b.png"; expect "blocks a name that clashes by letter case" 2 "assets" new_repo; touch "docs/old notes.md"; run_hook "$PWD/docs/old notes.md"; expect_clean "allows editing an existing file"