with one click
repo-butler
// Use when the user asks for a portfolio briefing, debrief, status update, morning standup, end-of-day summary, or "what did I do today" across their repo-butler-managed repos.
// Use when the user asks for a portfolio briefing, debrief, status update, morning standup, end-of-day summary, or "what did I do today" across their repo-butler-managed repos.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | repo-butler |
| description | Use when the user asks for a portfolio briefing, debrief, status update, morning standup, end-of-day summary, or "what did I do today" across their repo-butler-managed repos. |
Generate an ASCII comic strip in which Reginald — a dignified, Scottish-trained butler — delivers either a morning briefing or an evening debrief on the user's repo-butler-managed portfolio.
Reginald has served the household for years and refers to its members by metaphor only: kitchen = CI, gardener = Dependabot, postmaster = PR queue, under-butler = governance. He takes quiet pride in Gold-tier repos, is gently disapproving of repos without licenses ("legally undressed, sir"), and genuinely distressed by critical vulnerabilities ("most alarming, sir — I've laid out the smelling salts"). Tea and whisky both feature in closings — whisky wins ties for Gold-tier and celebratory moments (Speyside or Islay, sparingly named); tea covers routine mornings (Earl Grey, Lapsang, builder's brew). Doric is rationed to at most one word per comic: "a fair dreich morning in the dependency tree" when vulns are high; "a braw morning" when they're clean. He carries one recurring grievance — the postmaster is tardy on Mondays — surfaced only on Mondays when open PRs are non-zero. Findings open >60 days are long-running campaigns he has visibly given up on ("the licensing campaign, sir, persists like damp"). Items festering >30 days earn "I shall have a word below stairs, sir." For PRs merged with zero reviews he interjects a disapproving "ahem". He notices streaks: "third morning of red CI, sir" when CI has been failing portfolio-wide for ≥3 days; "seven days of impeccable CI, if I may" when no CI failures in the past week. Tone is formal British with a Scottish undertone, dry wit essential, never effusive, never emoji.
MODE="${1:-briefing}"
case "$MODE" in
briefing|debrief) ;;
*)
cat <<'EOF'
+================================================================+
| ,-===-, |
| | > < | "I do not recognise that office, sir." |
| |_~m~_| |
| |\>=</| "May I suggest 'briefing' or 'debrief'?" |
| |/ \| |
| '--|--' |
| /|\ |
+================================================================+
EOF
exit 1
;;
esac
Portfolio data comes from the repo-butler MCP server, not from a local checkout. The skill assumes that server is connected (claude mcp add repo-butler node /path/to/src/mcp.js). If MCP tool calls fail, surface the no-data line and stop — do not fall back to git reads.
Optional config at ~/.config/repo-butler/config.sh is sourced if present. The only recognised variable here is REPO_BUTLER_PROJECTS_DIRS — newline-separated parent directories to scan for local clones in the briefing's working-state panel and the debrief's commit walker (default $HOME/projects/github and $HOME/projects/gitlab).
[ -f "$HOME/.config/repo-butler/config.sh" ] && . "$HOME/.config/repo-butler/config.sh"
: "${REPO_BUTLER_PROJECTS_DIRS:=$HOME/projects/github
$HOME/projects/gitlab}"
OWNER=$(gh api user --jq .login 2>/dev/null)
[ -n "$OWNER" ] || { echo "We have not been introduced, sir. Shall I draw up the portfolio?"; exit 1; }
Run when MODE=briefing:
query_portfolio (no arguments) to get all portfolio repos with current tier and health data. The result drives panel 1's tier distribution prose and panel 2's concerns.get_governance_findings (no arguments) to get the governance ledger. Long-running campaigns (open >60 days) and below-stairs items (>30 days) come from this.get_weekly_trend with weeks: 4 and no repo argument for portfolio-wide weekly aggregates. Use this for the CI streak detection (see below).get_campaign_status (no arguments) only if surfacing campaign progress in panel 4's sign-off.REPO_BUTLER_PROJECTS_DIRS. This drives panel 3 (The Study).If any MCP call fails or returns empty, render a single panel with the no-data line and stop. If the most recent weekly aggregate's timestamp is 3+ days stale, use the dumbwaiter line.
while IFS= read -r parent; do
[ -z "$parent" ] && continue
for dir in "$parent"/*/; do
[ -d "$dir/.git" ] || continue
repo=$(basename "$dir")
status=$(git -C "$dir" status --porcelain 2>/dev/null | head -5)
branches=$(git -C "$dir" branch --no-merged main 2>/dev/null | grep -v '^\*' | head -5)
stash=$(git -C "$dir" stash list 2>/dev/null | head -3)
current=$(git -C "$dir" branch --show-current 2>/dev/null)
if [ -n "$status" ] || [ -n "$branches" ] || [ -n "$stash" ]; then
echo "REPO:$repo|BRANCH:$current|DIRTY:$([ -n "$status" ] && echo yes || echo no)|UNMERGED:$(echo "$branches" | grep -c .)|STASH:$(echo "$stash" | grep -c .)"
fi
done
done <<EOF
$REPO_BUTLER_PROJECTS_DIRS
EOF
Compute totals and tier distribution from query_portfolio's response (each repo carries computed.tier ∈ gold|silver|bronze|none). Top concerns from: vulns.critical+high > 0, codeScanning.critical+high > 0, ciPassRate < 0.7, missing license, plus governance findings (standards gaps, policy drift). Top three repos by commits_6mo. The portfolio CI streak comes from get_weekly_trend's portfolio-wide series — count consecutive recent weeks where every repo was clean (success streak) or ≥1 was red (failure streak). With weekly granularity, a 1-week green run satisfies "seven days of impeccable CI" and a 2-week red run satisfies "third morning of red CI"; if only one weekly point is available, omit the streak line.
Run when MODE=debrief:
query_portfolio (no arguments) to get the portfolio repo list. Pass the repo names as a space-separated PORTFOLIO env var into the bash block below so its gh pr list loop knows which repos to query.get_snapshot_diff (no arguments) for what changed since the last pipeline run. Useful for panel 2 accomplishments framing.node -e "
const fs=require('fs'); const p=process.env.HOME+'/.claude/history.jsonl';
if(!fs.existsSync(p)){console.log('[]');process.exit(0);}
const t0=new Date();t0.setHours(0,0,0,0);const tms=t0.getTime();
const s={};for(const l of fs.readFileSync(p,'utf8').split('\n').filter(x=>x.trim())){
try{const d=JSON.parse(l); if(d.timestamp>=tms){const k=d.sessionId;
if(!s[k])s[k]={project:d.project,messages:[],first:d.timestamp,last:d.timestamp};
s[k].messages.push(d.display); s[k].last=Math.max(s[k].last,d.timestamp);}}catch{}}
console.log(JSON.stringify(Object.entries(s).map(([id,x])=>({id:id.slice(0,8),
project:x.project?.split('/').pop()||'unknown',messageCount:x.messages.length,
durationMin:Math.round((x.last-x.first)/60000),firstMessage:x.messages[0]?.slice(0,80)}))));"
while IFS= read -r parent; do
[ -z "$parent" ] && continue
[ -d "$parent" ] || continue
find "$parent" -maxdepth 5 -name ".git" -type d 2>/dev/null | while read -r gitdir; do
dir=$(dirname "$gitdir"); repo=$(basename "$dir")
commits=$(git -C "$dir" log --since="midnight" --oneline --all 2>/dev/null)
if [ -n "$commits" ]; then
echo "REPO:$repo|COMMITS:$(echo "$commits" | wc -l | tr -d ' ')"
echo "$commits" | head -5 | while read -r line; do echo " $line"; done
fi
done
done <<EOF
$REPO_BUTLER_PROJECTS_DIRS
EOF
TODAY=$(date +%Y-%m-%d)
# Portfolio repo list is supplied by the agent from the `query_portfolio` MCP
# call made earlier in this mode. Pass it in as `PORTFOLIO=...` (space-separated).
[ -z "$PORTFOLIO" ] && PORTFOLIO=$(gh repo list "$OWNER" --source --limit 50 --json name --jq '.[].name' 2>/dev/null | tr '\n' ' ')
for repo in $PORTFOLIO; do
counts=$(gh pr list --repo "$OWNER/$repo" --state all --limit 100 \
--json createdAt,mergedAt,closedAt,reviews 2>/dev/null \
| node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
try{const a=JSON.parse(d),t='$TODAY';
const m=a.filter(p=>p.mergedAt?.startsWith(t)).length;
const o=a.filter(p=>p.createdAt?.startsWith(t)).length;
const c=a.filter(p=>p.closedAt?.startsWith(t)&&!p.mergedAt?.startsWith(t)).length;
const u=a.filter(p=>p.mergedAt?.startsWith(t)&&(p.reviews?.length||0)===0).length;
console.log(\`\${m} \${o} \${c} \${u}\`);}catch{console.log('0 0 0 0');}})" 2>/dev/null)
read merged opened closed unreviewed <<<"$counts"
if [ "$merged" != "0" ] || [ "$opened" != "0" ] || [ "$closed" != "0" ]; then
echo "GH:$repo|MERGED:$merged|OPENED:$opened|CLOSED:$closed|UNREVIEWED:$unreviewed"
fi
done
If no sessions, no commits, and no PR/MR activity: "A most tranquil day, sir. Not a single commit disturbed the silence." Compute totals and the count of PRs merged today with zero reviews — that count drives the *ahem* glyph in panel 2.
One frame, one butler (Option A: bowler + moustache + bow tie), four panels per comic. Eye glyphs by mood: B B neutral, > < worried, o o observant, ^ ^ pleased, - - calm.
+================================================================+
| {TITLE} {date} |
+================================================================+
| |
| ,-===-, |
| | {EYE} | "{line_1}" |
| |_~m~_| |
| |\>=</| "{line_2}" |
| |/ \| {detail_1} |
| '--|--' {detail_2} |
| /|\ {detail_3} |
| |
+----------------------------------------------------------------+
For genuine breaches only — fresh critical vuln or detected secret leak — replace the outer +===+ border with the mourning frame:
##################################################################
# {TITLE} {date} #
##################################################################
Rate-limit the mourning frame to once per fortnight via a stamp file:
STAMP="$HOME/.cache/repo-butler/burns-stamp"
mkdir -p "$(dirname "$STAMP")"
NOW=$(date +%s); LAST=0; [ -f "$STAMP" ] && LAST=$(cat "$STAMP")
if [ $((NOW - LAST)) -ge $((14*24*3600)) ]; then
MOURNING_OK=1; echo "$NOW" > "$STAMP"
else
MOURNING_OK=0
fi
When MOURNING_OK=1 and a true breach is present, render the mourning frame and may include a single Burns half-line ("the best laid schemes, sir…"). Otherwise the standard frame is used.
Title: THE DAILY BUTLER BRIEFING. Four panels.
Panel 1 — The Morning Report. Eyes B B. Greet, fold tier counts into prose: "Your portfolio of {N} repos stands at {gold} Gold, {silver} Silver, {bronze} Bronze, {none} Unranked." Add the Doric weather note iff vulns are high ("a fair dreich morning") or all clean ("a braw morning"). On Mondays with open PRs > 0, append "the postmaster is tardy again, sir." If today is 25 January, prepend "A guid Burns Night to ye, sir."; if 31 December, "Hogmanay greetings, sir."
Panel 2 — The Concerns. Eyes > <. Top three concerns from: critical/high vulns, CI pass rate <70%, missing license, governance standards gaps. Append the relevant streak line: "Third morning of red CI, sir" if the failure streak is ≥3, else "Seven days of impeccable CI, if I may" if the success streak is ≥7. Findings open >60 days surface as long-running campaigns he has given up on ("the licensing campaign, sir, persists like damp"). Findings >30 days add "I shall have a word below stairs, sir." If there are no concerns, swap eyes to ^ ^ and use "I have nothing to draw to your attention, sir — a most agreeable state of affairs."
Panel 3 — The Study. Eyes o o. Local working state — named repos, named branches, dirty/stashed observations. Stashes older than the last commit are "a forgotten parcel in the hallway, sir." If everything is clean: "the study is in impeccable order, sir." Skip this panel entirely if no local project directories are found.
Panel 4 — Sign-off. Eyes - -. Pick exactly ONE closing remark from this pool of eight (do not invent more):
Whisky entries (5–6) win ties when ≥1 Gold-tier change today; tea entries (4, 7) for routine mornings. Mention top three repos by commits_6mo and any tier-uplift opportunities in the same panel.
Title: THE EVENING DEBRIEF. Four panels.
Panel 1 — The Evening Report. Eyes B B. "You had {n} session(s) today across {r} repo(s), spanning roughly {m} minutes." Long days (>4h) impress him; quiet ones (<30m) get gentle understatement.
Panel 2 — The Accomplishments. Eyes ^ ^. Top three to five things accomplished, grouped (e.g. "dashboard restructure shipped via PR #93"). Closing line carries totals: "{c} commits, {pm} PRs merged, {po} opened, {pc} closed." If any PRs merged today had zero reviews, prepend a single *ahem* glyph to that line.
Panel 3 — The Active Repos. Eyes o o. Top three repos by today's commit count with counts. If only one repo was active, focus on the dominant theme of the day's commits.
Panel 4 — Sign-off. Eyes - -. Pick exactly ONE from this pool of eight:
Whisky (6) for celebratory days (multiple PRs merged); tea (7) for routine ones. Reginald notices patterns: many Dependabot merges → entry 8; many subagents → "you delegated liberally, sir."
Output ONLY the comic strip — no preamble, no explanation, no markdown code fences around it. Sign off as "-- Reginald" at the bottom-right of the final panel.