Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dirsize-baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Directory-size baseline (docs/patterns/07) - flat SOURCE-file counts > 15
# that are tolerated until split (ratchet: may only shrink, never grow; a NEW
# oversized directory fails `check-directory-size.sh --gate`). This file IS the
# god-folder whitelist. components/ is the EXAMPLE-DOMAIN demo surface and
# shrinks naturally as you replace the example domain with your own.
# Format: <path> # reason
frontend/src/components # EXAMPLE-DOMAIN demo components (shrinks on customization)
frontend/src/hooks # shared hooks
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
audit-backend audit-frontend bandit-backend security-backend check-security \
release-prepare release-finish release-publish \
check-file-sizes check-complexity check-complexity-gate check-complexity-gate-update \
check-directory-size check-directory-size-gate \
clean prod prod-down prod-logs help

# --- Development ---
Expand Down Expand Up @@ -276,6 +277,12 @@ check-complexity-gate: ## Complexity ratchet gate: fail on new/regressed offende
check-complexity-gate-update: ## Regenerate .complexity-baseline from current offenders (ratchet may only shrink)
@bash scripts/check-complexity.sh --update-baseline

check-directory-size: ## God-folder watcher (warn-only): >15 flat source files per dir under frontend/src
@bash scripts/check-directory-size.sh

check-directory-size-gate: ## God-folder ratchet gate: fail on a NEW oversized dir vs .dirsize-baseline
@bash scripts/check-directory-size.sh --gate

# --- E2E Tests ---

test-e2e: ## Run Playwright e2e tests (starts servers automatically)
Expand Down
10 changes: 6 additions & 4 deletions docs/patterns/07-complexity-cohesion-ratchet.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
`radon_warn.py` / `complexity_gate.py`), with `make check-file-sizes` /
`check-complexity` / `check-complexity-gate` / `check-complexity-gate-update`
and shrink-only `.filesize-baseline` + `.complexity-baseline` (plus a
`.filesize-whitelist`). radon is a backend dev-dependency. The **TypeScript
(eslint) half of the complexity gate degrades gracefully** and is inactive
until the template has an ESLint flat config (`eslint.config.js`); the
**god-folder watcher** is still documented-only - add it as below.
`.filesize-whitelist`). radon is a backend dev-dependency. The **god-folder
watcher** also ships - `scripts/check-directory-size.sh` + `make
check-directory-size` / `check-directory-size-gate` with a shrink-only
`.dirsize-baseline`. The only remaining gap is the **TypeScript (eslint) half
of the complexity gate**, which degrades gracefully and stays inactive until
the template has an ESLint flat config (`eslint.config.js`).

---

Expand Down
72 changes: 72 additions & 0 deletions scripts/check-directory-size.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# check-directory-size.sh - god-folder watcher (docs/patterns/07).
#
# Counts the FLAT (maxdepth 1) source files (*.ts / *.tsx, tests excluded) in
# each directory under frontend/src and warns when a directory holds too many.
# Like the file-size watcher, this prevents god-folders from returning after
# they were grouped by concern.
#
# Two modes:
# (default) WARN_THRESHOLD (default 15) - warn, no fail.
# --gate exit 1 when a directory NOT listed in .dirsize-baseline is over
# the threshold (ratchet: existing, not-yet-split god-folders are
# tolerated, but a NEW one fails).
#
# Only version-controlled sources under frontend/src (git ls-files), so
# generated / gitignored trees drop out automatically.
#
# Exit codes:
# 0 = clean, warnings only, or only tolerated baseline directories
# 1 = (only with --gate) a new directory over the threshold

set -euo pipefail

WARN_THRESHOLD="${WARN_THRESHOLD:-15}"
ROOT_DIR="${ROOT_DIR:-frontend/src}"
BASELINE_FILE=".dirsize-baseline"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$REPO_ROOT"

GATE=0
[[ "${1:-}" == "--gate" ]] && GATE=1

# Tolerated (not-yet-split) directories, one path per line, '#' comments ok.
declare -A BASELINE=()
if [[ -f "$BASELINE_FILE" ]]; then
while IFS= read -r line; do
line="${line%%#*}"; line="$(echo -n "$line" | xargs || true)"
[[ -n "$line" ]] && BASELINE["$line"]=1
done < "$BASELINE_FILE"
fi

# Count flat (maxdepth 1) tracked *.ts/*.tsx per directory. Co-located test
# files (*.test.ts/x) are excluded: the guard is about grouping SOURCE files
# by concern, and tests live next to their subject by convention.
declare -A COUNT=()
while IFS= read -r f; do
[[ "$f" == */* ]] || continue
case "$f" in *.test.ts|*.test.tsx) continue;; esac
dir="${f%/*}"
COUNT["$dir"]=$(( ${COUNT["$dir"]:-0} + 1 ))
done < <(git ls-files "$ROOT_DIR" | grep -E '\.tsx?$' || true)

status=0
warned=0
for dir in $(printf '%s\n' "${!COUNT[@]}" | sort); do
n="${COUNT[$dir]}"
(( n > WARN_THRESHOLD )) || continue
if [[ -n "${BASELINE[$dir]:-}" ]]; then
echo "BASELINE (tolerated): $dir has $n files (max $WARN_THRESHOLD)"
continue
fi
echo "WARNING: $dir has $n files (max $WARN_THRESHOLD) - group by concern"
warned=1
(( GATE == 1 )) && status=1
done

if (( warned == 0 )); then
echo "check-directory-size: OK - no un-baselined directory over $WARN_THRESHOLD flat files."
fi
exit $status