feat: add bin/gstack-repo-mode — solo vs collaborative detection with caching

Detects whether a repo is solo-dev (one person does 80%+ of recent commits)
or collaborative. Uses 90-day git shortlog window with 7-day cache in
~/.gstack/projects/{SLUG}/repo-mode.json. Config override via
`gstack-config set repo_mode solo|collaborative` takes precedence over
the heuristic. Minimum 5 commits required to classify (otherwise unknown).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-20 08:33:41 -07:00
parent 91bea06675
commit 5edf4856ff
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# gstack-repo-mode — detect solo vs collaborative repo mode
# Usage: source <(gstack-repo-mode) → sets REPO_MODE variable
# Or: gstack-repo-mode → prints REPO_MODE=... line
#
# Detection heuristic (90-day window):
# Solo: top author >= 80% of commits AND <= 2 distinct authors
# Collaborative: everything else
#
# Override: gstack-config set repo_mode solo|collaborative
# Cache: ~/.gstack/projects/$SLUG/repo-mode.json (7-day TTL)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
eval $("$SCRIPT_DIR/gstack-slug" 2>/dev/null) || { echo "REPO_MODE=unknown"; exit 0; }
[ -z "${SLUG:-}" ] && { echo "REPO_MODE=unknown"; exit 0; }
# Config override takes precedence
OVERRIDE=$("$SCRIPT_DIR/gstack-config" get repo_mode 2>/dev/null || true)
if [ -n "$OVERRIDE" ] && [ "$OVERRIDE" != "null" ]; then
echo "REPO_MODE=$OVERRIDE"
exit 0
fi
# Check cache (7-day TTL)
CACHE_DIR="$HOME/.gstack/projects/$SLUG"
CACHE_FILE="$CACHE_DIR/repo-mode.json"
if [ -f "$CACHE_FILE" ]; then
CACHE_AGE=$(( $(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
if [ "$CACHE_AGE" -lt 604800 ]; then # 7 days in seconds
MODE=$(grep -o '"mode":"[^"]*"' "$CACHE_FILE" | head -1 | cut -d'"' -f4)
[ -n "$MODE" ] && echo "REPO_MODE=$MODE" && exit 0
fi
fi
# Compute from git history (90-day window)
SHORTLOG=$(git shortlog -sn --since="90 days ago" --no-merges HEAD 2>/dev/null | head -20)
if [ -z "$SHORTLOG" ]; then
echo "REPO_MODE=unknown"
exit 0
fi
TOTAL=$(echo "$SHORTLOG" | awk '{s+=$1} END {print s}')
TOP=$(echo "$SHORTLOG" | head -1 | awk '{print $1}')
AUTHORS=$(echo "$SHORTLOG" | wc -l | tr -d ' ')
# Minimum sample: need at least 5 commits to classify
if [ "$TOTAL" -lt 5 ]; then
echo "REPO_MODE=unknown"
exit 0
fi
TOP_PCT=$(( TOP * 100 / TOTAL ))
# Solo: top author >= 80% of commits (occasional outside PRs don't change mode)
if [ "$TOP_PCT" -ge 80 ]; then
MODE=solo
else
MODE=collaborative
fi
# Cache result (fail silently if ~/.gstack is unwritable)
mkdir -p "$CACHE_DIR" 2>/dev/null || true
echo "{\"mode\":\"$MODE\",\"top_pct\":$TOP_PCT,\"authors\":$AUTHORS,\"total\":$TOTAL,\"computed\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$CACHE_FILE" 2>/dev/null || true
echo "REPO_MODE=$MODE"