diff --git a/docs/superpowers/evidence/cap-artifact-growth-2026-08-23.json b/docs/superpowers/evidence/cap-artifact-growth-2026-08-23.json new file mode 100644 index 00000000..6a2a9067 --- /dev/null +++ b/docs/superpowers/evidence/cap-artifact-growth-2026-08-23.json @@ -0,0 +1,37 @@ +{ + "intent": "Stop the superpowers bookkeeping tree from silently regrowing to the 695 files that made a 655-file prune necessary, by capping it in the gate instead of leaving a note recommending it.", + "scope": [ + "meta/scripts/check_repo_size.sh - adds a per-directory artifact cap", + "Non-goal: changing the source-file ceiling or the excluded prefixes, both unchanged." + ], + "changes": [ + "Added ARTIFACT_DIRS + MAX_ARTIFACTS_PER_DIR=60, checked by a new check_artifact_dirs helper wired into main alongside the existing source-file ceiling.", + "main now accumulates failures rather than returning on the first, so a commit that trips both the source ceiling and the artifact cap reports both.", + "60 is the headroom the prune bought (21 files per dir today). The header says to prune rather than raise it when it fails." + ], + "verification": [ + { + "command": "./meta/scripts/check_repo_size.sh", + "result": "pass", + "evidence": "'1050 source files (ceiling 1200)' EXIT=0 with evidence/contracts at 21 each, under the 60 cap. --count still prints the bare number, EXIT=0." + }, + { + "command": "MAX_ARTIFACTS_PER_DIR=5 variant", + "result": "pass", + "evidence": "EXIT=1 listing BOTH dirs (evidence 22, contracts 21) plus the prune hint - confirms the new branch fires and that it does not stop at the first offender." + }, + { + "command": "pre-commit run repo-size-cap --all-files; pre-commit run --files meta/scripts/check_repo_size.sh", + "result": "pass", + "evidence": "Hook Passed; shfmt and shellcheck clean on the modified script." + } + ], + "risks": [ + "A legitimate burst of commits can now trip the cap and block a commit until artifacts are pruned. That is the intended trade: the previous behaviour was unbounded growth noticed only months later.", + "The cap counts tracked files per directory, so it does not distinguish a needed recent artifact from an old one; the operator chooses what to prune." + ], + "rollback": [ + "git revert the commit; the source-file ceiling and all other behaviour are untouched by this change.", + "After rollback, confirm ./meta/scripts/check_repo_size.sh still exits 0 and prints the source count." + ] +} diff --git a/meta/scripts/check_repo_size.sh b/meta/scripts/check_repo_size.sh index 2e5c2b8b..8da1626f 100755 --- a/meta/scripts/check_repo_size.sh +++ b/meta/scripts/check_repo_size.sh @@ -24,10 +24,15 @@ # The count is always printed, so the threshold can be audited from any CI log # rather than being taken on trust. # +# The generated trees are excluded from that count but capped separately: they +# grow about one file per commit, which is what made a 655-file prune +# necessary in the first place. Capping them here means the next overflow +# fails a commit instead of being noticed months later. +# # check_repo_size.sh # count and enforce # check_repo_size.sh --count # print the number only, always exit 0 # -# Exits 0 when at or under the ceiling, 1 when over. +# Exits 0 when at or under every ceiling, 1 when over any of them. # ============================================================================ set -euo pipefail @@ -45,6 +50,16 @@ readonly EXCLUDED_PREFIXES=( .hippo/ ) +# Per-commit bookkeeping. Pruned to 20 each (plus a template) on 2026-08-23; +# the cap is the headroom that prune bought, so the tree cannot silently +# return to the 695 files it held before. When this fails, prune - do not +# raise it. +readonly ARTIFACT_DIRS=( + docs/superpowers/evidence + docs/superpowers/contracts +) +readonly MAX_ARTIFACTS_PER_DIR=60 + COUNT_ONLY=0 usage() { @@ -68,6 +83,24 @@ source_files() { git ls-files | grep -v "${filter[@]}" || true } +# Fail when a bookkeeping directory has grown past its cap. Reports every +# offending directory rather than stopping at the first. +check_artifact_dirs() { + local dir n over=0 + for dir in "${ARTIFACT_DIRS[@]}"; do + n="$(git ls-files "$dir" | wc -l)" + if ((n > MAX_ARTIFACTS_PER_DIR)); then + echo "check_repo_size: $dir holds $n files, over the $MAX_ARTIFACTS_PER_DIR cap" >&2 + over=1 + fi + done + ((over)) && { + echo "Prune the oldest artifacts; they stay available in git history." >&2 + return 1 + } + return 0 +} + main() { local count count="$(source_files | wc -l)" @@ -77,12 +110,18 @@ main() { return 0 fi + local failed=0 + if ((count > MAX_SOURCE_FILES)); then echo "check_repo_size: $count source files, over the $MAX_SOURCE_FILES ceiling" >&2 echo "Extract a unit into its own repo rather than raising the cap." >&2 - return 1 + failed=1 fi + check_artifact_dirs || failed=1 + + ((failed)) && return 1 + echo "check_repo_size: $count source files (ceiling $MAX_SOURCE_FILES)" return 0 }