
Git Activity
- 8 installs
- 76 repo stars
- Updated July 31, 2026
- basecamp/house-skills
git-activity is a Claude Code skill that fetches git log from local repos into day-cached JSON atoms.
About
git-activity is a Claude Code skill that reads git commit history from local repositories into per-day cached JSON atoms. A developer uses it to feed the recap orchestrator for daily, weekly, or monthly digests. It is idempotent, day-cached, and makes no API calls.
- Fetches git log from local repos into day-cached JSON atoms
- Simplest fetcher - no API calls or pagination, reads local repos only
- Day-sized atoms enable progressive weekly and monthly aggregation
Git Activity by the numbers
- 8 all-time installs (skills.sh)
- Ranked #1,519 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
git-activity capabilities & compatibility
- Capabilities
- git log fetch · data caching
- Use cases
- data analysis
What git-activity says it does
Fetch git log from local repos into day-cached JSON atoms. Simplest activity fetcher — no API calls, no pagination.
Day-sized atoms enable progressive aggregation: a weekly digest reads 7 daily atoms. A monthly digest reads ~30.
npx skills add https://github.com/basecamp/house-skills --skill git-activityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 76 |
| Last updated | July 31, 2026 |
| Repository | basecamp/house-skills ↗ |
What it does
Fetch git commit history from local repos into day-cached JSON atoms for recap digests.
Who is it for?
Caching local git commit history into per-day atoms for recap digests.
When should I use this skill?
You need commit history from local repos for a recap.
What you get
Per-day cached JSON atoms of commit history ready for aggregation.
- day-cached log.json atoms
By the numbers
- one JSON file per repo per day
Files
Git Activity Fetcher
Fetch git commit history from local repositories into per-day cached JSON atoms at ~/.cache/recap/git/{repo}/{YYYY-MM-DD}/log.json. These atoms feed the /recap orchestrator for progressive timescale synthesis.
Invocation
/recap:git-activity --repos coworker:~/Work/basecamp/coworker --since 2026-03-23
/recap:git-activity --repos coworker:~/Work/basecamp/coworker,house-skills:~/Work/basecamp/house-skills --since 2026-03-23 --until 2026-03-30
/recap:git-activity --repos coworker:~/Work/basecamp/coworker --since 2026-03-23 --reuseContract
- Idempotent: same input produces same output, safe to re-run
- Day-cached: one JSON file per repo per day at
~/.cache/recap/git/{repo}/{YYYY-MM-DD}/log.json - `--reuse`: skip fetch if cache exists and is marked complete
- No API calls: reads local git repos only
Quick Run
# 1. Determine date range
SINCE=$(date -d "7 days ago" +%Y-%m-%d)
UNTIL=$(date +%Y-%m-%d)
# 2. Run the fetcher
"$SKILL_DIR/scripts/git-activity.sh" \
--repos "coworker:$HOME/Work/basecamp/coworker" \
--since "$SINCE" --until "$UNTIL"
# 3. Verify cache
ls ~/.cache/recap/git/coworker/
cat ~/.cache/recap/git/coworker/$SINCE/log.json | jq '.metadata'Where $SKILL_DIR = directory containing this SKILL.md.
Output Format
Each log.json contains:
{
"repo": "coworker",
"date": "2026-03-24",
"commits": [
{
"hash": "abc123...",
"short_hash": "abc123",
"subject": "Add weekly digest skill",
"author": "Jeremy",
"date": "2026-03-24T10:30:00-05:00",
"body": "Extended commit message..."
}
],
"metadata": { "complete": true, "count": 3 }
}Arguments
| Argument | Required | Description |
|---|---|---|
--repos | Yes | Comma-separated name:path pairs |
--since | Yes | Start date (YYYY-MM-DD) |
--until | No | End date (default: today) |
--reuse | No | Skip fetch if cache exists and is complete |
Cache Structure
~/.cache/recap/git/
coworker/
2026-03-24/log.json
2026-03-25/log.json
house-skills/
2026-03-24/log.jsonDay-sized atoms enable progressive aggregation: a weekly digest reads 7 daily atoms. A monthly digest reads ~30. Different timescales, same data.
Failure Modes
| Symptom | Cause | Fix |
|---|---|---|
| "not a git repo" | Path doesn't point to repo | Check --repos paths |
| Empty commits for a day | No commits on that date | Expected — empty days are cached as complete |
| awk/jq parse error | Unusual characters in commit messages | Falls back to simpler format automatically |
#!/usr/bin/env bash
#
# Git activity fetcher — local git log to day-cached JSON.
#
# Usage:
# ./git-activity.sh --repos name:path[,name:path,...] --since DATE --until DATE [--reuse]
#
# Output:
# ~/.cache/recap/git/{repo}/{YYYY-MM-DD}/log.json per-day per-repo
# Prints cache paths to stdout when done.
#
set -euo pipefail
REPOS=""
SINCE_DATE=""
UNTIL_DATE=""
REUSE=false
while [[ $# -gt 0 ]]; do
case $1 in
--repos) REPOS="$2"; shift 2 ;;
--since) SINCE_DATE="$2"; shift 2 ;;
--until) UNTIL_DATE="$2"; shift 2 ;;
--reuse) REUSE=true; shift ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$REPOS" ]]; then
echo "Error: --repos name:path[,name:path,...] is required" >&2
exit 1
fi
if [[ -z "$SINCE_DATE" ]]; then
echo "Error: --since DATE is required" >&2
exit 1
fi
[[ -z "$UNTIL_DATE" ]] && UNTIL_DATE=$(date -u +%Y-%m-%d)
# Normalize to YYYY-MM-DD
SINCE_DAY="${SINCE_DATE:0:10}"
UNTIL_DAY="${UNTIL_DATE:0:10}"
CACHE_BASE="$HOME/.cache/recap/git"
# Generate list of days in range
days_in_range() {
local current="$1" end="$2"
while [[ "$current" < "$end" || "$current" == "$end" ]]; do
echo "$current"
current=$(date -d "$current + 1 day" +%Y-%m-%d 2>/dev/null || date -j -v+1d -f "%Y-%m-%d" "$current" +%Y-%m-%d)
done
}
# Parse repos: "name:path,name:path" → iterate
IFS=',' read -ra REPO_PAIRS <<< "$REPOS"
for pair in "${REPO_PAIRS[@]}"; do
REPO_NAME="${pair%%:*}"
REPO_PATH="${pair#*:}"
# Expand ~ in path
REPO_PATH="${REPO_PATH/#\~/$HOME}"
if [[ ! -d "$REPO_PATH/.git" && ! -f "$REPO_PATH/.git" ]]; then
echo "WARN: $REPO_PATH is not a git repo, skipping $REPO_NAME" >&2
continue
fi
echo "Fetching git log for $REPO_NAME ($REPO_PATH)..." >&2
for day in $(days_in_range "$SINCE_DAY" "$UNTIL_DAY"); do
NEXT_DAY=$(date -d "$day + 1 day" +%Y-%m-%d 2>/dev/null || date -j -v+1d -f "%Y-%m-%d" "$day" +%Y-%m-%d)
CACHE_DIR="$CACHE_BASE/$REPO_NAME/$day"
CACHE_FILE="$CACHE_DIR/log.json"
# Skip if reuse and cache exists with complete flag
if [[ "$REUSE" == "true" && -f "$CACHE_FILE" ]]; then
if jq -e '.metadata.complete == true' "$CACHE_FILE" >/dev/null 2>&1; then
echo " $day: reusing cache" >&2
continue
fi
fi
mkdir -p "$CACHE_DIR"
# Fetch git log for this day — write to file to avoid null byte issues
COMMITS_FILE=$(mktemp)
GIT_STDERR=$(mktemp)
GIT_EXIT=0
git -C "$REPO_PATH" log \
--format='%H%x1f%h%x1f%s%x1f%an%x1f%aI%x1f%b%x1e' \
--since="${day}T00:00:00" --until="${NEXT_DAY}T00:00:00" \
--no-merges > "$COMMITS_FILE" 2>"$GIT_STDERR" || GIT_EXIT=$?
if [[ "$GIT_EXIT" -ne 0 ]]; then
echo " ERROR: git log failed for $REPO_NAME/$day (exit $GIT_EXIT): $(cat "$GIT_STDERR")" >&2
rm -f "$COMMITS_FILE" "$GIT_STDERR"
exit 1
fi
rm -f "$GIT_STDERR"
if [[ ! -s "$COMMITS_FILE" ]]; then
# No commits (exit 0, empty output) — write empty but complete
rm -f "$COMMITS_FILE"
jq -n --arg repo "$REPO_NAME" --arg day "$day" '{
repo: $repo,
date: $day,
commits: [],
metadata: { complete: true, count: 0 }
}' > "$CACHE_FILE"
continue
fi
# Parse commits into JSON — let jq handle all string escaping to avoid
# control character / newline issues that break awk-based JSON serialization
jq -R -s --arg repo "$REPO_NAME" --arg day "$day" '
split("\u001e") | map(select(length > 0)) |
map(split("\u001f")) |
map(select(length >= 5) | {
hash: .[0],
short_hash: .[1],
subject: .[2],
author: .[3],
date: .[4],
body: (.[5] // "" | gsub("^\\s+|\\s+$"; ""))
}) |
{
repo: $repo,
date: $day,
commits: .,
metadata: { complete: true, count: length }
}
' "$COMMITS_FILE" > "$CACHE_FILE"
rm -f "$COMMITS_FILE"
COUNT=$(jq '.metadata.count' "$CACHE_FILE")
echo " $day: $COUNT commits" >&2
done
echo "$CACHE_BASE/$REPO_NAME" >&2
done
# Print summary to stdout as JSON
echo '{"status":"complete","cache_base":"'"$CACHE_BASE"'"}'