A continuous roadmap planner agent that runs on a schedule, analyses the state of GitHub repositories, generates HTML health dashboards, and proposes improvements as issues.
Live dashboards: ismaelmartinez.github.io/repo-butler
Add Repo Butler to any repository with a simple workflow file:
name: Repo Butler
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
phase:
description: 'Phase to run (observe, assess, update, governance, ideate, propose, report, or all)'
default: 'report'
permissions:
contents: write
issues: write
pull-requests: write
pages: write
id-token: write
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: IsmaelMartinez/repo-butler@v1
with:
github-token: ${{ github.token }}
phase: ${{ github.event.inputs.phase || 'report' }}
gemini-api-key: ${{ secrets.GEMINI_API_KEY }}The only required input is github-token. The gemini-api-key is needed for LLM-powered phases (assess, ideate, update) but not for observe or report.
| Input | Required | Default | Description |
|---|---|---|---|
github-token |
yes | ${{ github.token }} |
GitHub token with contents, issues, pull-requests, and pages write access |
phase |
no | all |
Which phase to run: observe, assess, update, governance, ideate, propose, report, or all |
config-path |
no | .github/roadmap.yml |
Path to the roadmap config file |
gemini-api-key |
no | — | Gemini API key (free tier: 10 RPM, 250 RPD) |
claude-api-key |
no | — | Claude API key (for deep reasoning in ideate phase) |
dry-run |
no | false |
If true, log what would happen but do not create issues or PRs |
Create a .github/roadmap.yml in your repository to customise Repo Butler's behaviour:
roadmap:
path: ROADMAP.md
compact_after_days: 60
schedule:
assess: daily
ideate: weekly
providers:
default: gemini
context: |
Describe your project, its goals, and what kind of ideas would be useful.
limits:
max_issues_per_run: 3
require_approval: trueThe context field tells the LLM about your project so it can generate relevant improvement ideas. The providers.default field selects the LLM provider (gemini for Gemini Flash free tier, claude for Claude Sonnet). Setting require_approval: true means proposed issues are created with a needs-approval label for human review before any action is taken.
The schedule section controls how often each phase runs. Setting assess: daily and ideate: weekly means the butler checks project health every day but only generates new ideas once a week, keeping noise low.
The UPDATE phase only ever appends to your roadmap — it can add entries but never delete or rewrite them — so without compaction the document would grow forever and eventually fail the 60,000-character safety check. roadmap.compact_after_days sets how much full detail to keep: completed ~~SHIPPED~~ subsections older than that are trimmed to a one-line pointer, and older dated entries in the free-prose ## Implemented section are rolled up to one line per month. The prose always stays in git history. Undated paragraphs are never touched, so evergreen descriptions survive — if you want a paragraph kept verbatim, leave a full YYYY-MM-DD date out of it.
Repo Butler follows a seven-phase loop: OBSERVE → ASSESS → UPDATE → GOVERNANCE → IDEATE → PROPOSE → REPORT
- OBSERVE gathers project state via the GitHub API (issues, PRs, releases, labels, workflows, roadmap content) and classifies all portfolio repos by activity level. No LLM needed.
- ASSESS diffs the current snapshot against the previous run, computes weekly trends (growing/shrinking/stable), and optionally summarises changes with Gemini Flash.
- UPDATE generates an updated roadmap document, validates it through a safety layer, and opens a PR.
- GOVERNANCE runs deterministic detectors over the portfolio — standards gaps, policy drift, tier-uplift opportunities, tier regressions, open vulnerabilities, stalled security alerts, stale Dependabot PRs, and the butler's own unmerged PRs — and persists findings to the data branch. No LLM cost, so the daily pipeline runs it 4×/day.
- IDEATE generates improvement ideas using an LLM (Claude for deeper reasoning, Gemini Flash as default), feeding off the fresh governance findings.
- PROPOSE safety-filters ideas (URL allowlist, @mention blocking, secret detection), then creates GitHub issues capped at
max_issues_per_run, sorted by priority, labelled for human review. - REPORT generates HTML dashboards for every active repo in the portfolio, deployed to GitHub Pages.
For a visual map of how the four scheduled workflows + on-demand apply and onboard interleave, see docs/architecture.md.
The portfolio page (index.html) is the landing page with a stacked weekly commit heatmap, a health matrix table (commits, CI, license, status), and distribution charts for language, status, and commit totals. Repo names link to individual per-repo reports.
Per-repo pages ({repo-name}.html) are generated for every active, non-fork, non-test repo. Repos with 10 or more commits in the last 6 months get full charts covering PR merge velocity (12 months), issues opened vs closed (12 months), release cadence, PR author distribution, open issues by label, and weekly trend lines when history is available. Repos with less activity get a lightweight summary card.
Reports regenerate four times a day during UK waking hours (07:00, 11:00, 16:00, 20:00 UTC) and are deployed to GitHub Pages automatically. Caching skips regeneration when the snapshot hash hasn't changed, reducing quiet-day runs from ~15 minutes to seconds.
If your repo has a github-issue-triage-bot deployed, Repo Butler auto-discovers it and integrates. The bot is found from .github/butler.json in the target repo (the same config file the triage bot reads) or from the TRIAGE_BOT_URL environment variable.
When the bot is available, the OBSERVE phase POSTs snapshot metrics to the bot's /ingest endpoint (requires TRIAGE_BOT_INGEST_SECRET), the ASSESS phase fetches synthesis findings from /report/trends, and per-repo report footers link to the live triage dashboard.
When the bot is not available, nothing changes — no errors, no warnings, no degraded behaviour.
- Add a
.github/roadmap.ymlto your repo (see Configuration above). - Add the workflow from the Usage section.
- Trigger manually:
gh workflow run "Repo Butler" --ref main
The butler will observe your repo, generate a health dashboard, and (if LLM keys are configured) propose improvements as GitHub issues.
# Copy .env.example to .env.local, fill in your values, then:
npm run report # Generate reports
npm run observe # Observe only
npm run all # Full pipeline (needs GEMINI_API_KEY)Zero external dependencies. Runs on the GitHub Actions node24 runtime and uses Node's built-in fetch for all API calls. The GitHub API client handles rate limiting with automatic retry and backoff. Search API calls are throttled to stay under secondary rate limits. A safety layer validates all LLM output before publishing.
src/
├── index.js # Entry point, phase router
├── observe.js # OBSERVE: GitHub API data gathering + portfolio classification
├── assess.js # ASSESS: snapshot diffing, trend computation, LLM summarisation
├── update.js # UPDATE: roadmap PR generation with safety validation
├── governance.js # GOVERNANCE: standards-gap, policy-drift, tier-uplift, tier-regression, open-vulnerability detection (deterministic)
├── dependabot-audit.js # Stale Dependabot PR detector (called by governance)
├── butler-pr-audit.js # Stale-butler-pr detector: the butler's own PRs nobody landed (called by governance)
├── stalled-alert.js # Stalled-alert detector: open Dependabot alerts with no PR driving them (called by governance)
├── trimmer.js # Parent-scoped npm override decider for transitive vulns (ADR-013; no caller in the write path yet)
├── private-watch.js # Private-repo security watch — standalone pass, never enters the governance pipeline
├── private-notify.js # Tracking-issue delivery for private-watch findings
├── ideate.js # IDEATE: LLM idea generation with structured parsing
├── propose.js # PROPOSE: GitHub issue creation with safety filtering + approval gate
├── report.js # REPORT: entry point, orchestrates report generation
├── report-shared.js # Shared constants, computeHealthTier(), helpers
├── report-portfolio.js # Portfolio reports, campaigns, dependency inventory
├── report-repo.js # Per-repo charts, health sections, data fetchers
├── report-styles.js # CSS template
├── apply.js # Governance Apply: opens remediation PRs on target repos (manual dispatch)
├── council.js # Agent-council deliberation on proposals and events
├── monitor.js # Continuous event monitoring between daily runs
├── onboard.js # Auto-onboarding PRs (CLAUDE.md marker) for new repos
├── mcp.js # MCP server: JSON-RPC 2.0 over stdio for AI agents
├── agent-card.js # A2A AgentCard generator (served at .well-known/agent-card.json)
├── safety.js # Output validators: URLs, @mentions, secrets, XSS, lengths
├── triage-bot.js # Optional triage bot integration (auto-discovered)
├── store.js # Snapshot + weekly history + hash persistence via Git Data API
├── config.js # YAML config loader with defaults
├── github.js # GitHub REST API client with rate limit handling
├── libyear.js # Dependency freshness (libyear metric via npm/PyPI/crates.io)
└── providers/
├── base.js # LLM provider interface
├── gemini.js # Gemini Flash (free tier, API key via header)
└── claude.js # Claude (Anthropic Messages API)
schemas/v1/ # JSON Schema definitions for all data structures
docs/
├── architecture.md # Visual pipeline diagram + data flow
├── consumer-guide.md # Repo-owner guide for the per-repo dashboards
├── skill.md # Claude Code skill for AI agent consumption
├── decisions/ # Architecture Decision Records (ADR-001 through ADR-006)
├── research/ # Research notes for open roadmap items
└── superpowers/ # Active implementation plans (in flight only)
The portfolio observer prefers the /installation/repositories endpoint (GitHub App tokens), falling back to /user/repos (PATs), then to the public-only /users/{owner}/repos endpoint. Private repos only appear when the token can see them — a default GITHUB_TOKEN cannot list repos across an owner's portfolio, so the workflow should use a GitHub App token (actions/create-github-app-token) installed on every repo that should be included.
Two skills ship from skills/ for use inside Claude Code: repo-butler (read-side, briefing/debrief modes) and repo-butler-apply (write-side, confirm-gated governance dispatch). Install them into your local skill registry with:
./scripts/install-skills.shThe script symlinks both skills into $HOME/.claude/skills/, cleans up dead symlinks from earlier butler-briefing/butler-debrief/butler-apply layouts, and is idempotent. Pass --uninstall to remove the symlinks, or --skills-dir DIR to target a custom location. Restart your Claude Code session afterwards so the new skills appear in the registry.
Because these are symlinks, the skill that runs is whatever is in that checkout's working tree — not whatever is on main. Merging a PR does not change what runs until you pull, an experiment on a feature branch becomes the live skill while you have it checked out, and editing the file through the registry path edits the repository itself.
scripts/check-skills.js reports that state instead of leaving you to guess (#350):
node scripts/check-skills.js # human-readable report
node scripts/check-skills.js --headline # one line, what the skills render
node scripts/check-skills.js --json # full readingIt names how far the checkout is behind origin/main, which branch it is on, how many uncommitted changes under skills/ are live, and where each registry entry actually points — a copy or a link into a different checkout both mean a merge can never reach the running skill. It exits 0 when there is nothing to report and 1 when there is. Like the MCP staleness envelope it reports rather than fetches, and it distinguishes "could not check" from "checked, it is fine": a checkout that has not fetched since origin/main moved is reported as exactly that, never as zero commits behind. Both skills run it themselves and surface the reading — the briefing as an almanac line in the frame, the apply skill as a caveat on every confirmation prompt.
Both skills source their portfolio data via the repo-butler MCP server below — no local clone of the data branch is required. Install the MCP server first (next section) and the skills will work from any working directory. Optional config at ~/.config/repo-butler/config.sh recognises REPO_BUTLER_PROJECTS_DIRS (newline-separated parent dirs to scan for local working state) — defaults to $HOME/projects/github and $HOME/projects/gitlab.
Repo Butler includes an MCP (Model Context Protocol) server that lets AI agents query portfolio health data directly. Any MCP-compatible client (Claude Code, Claude Desktop, Cursor, VS Code) can connect.
# Add to Claude Code
claude mcp add repo-butler node src/mcp.js
# Or add to Claude Desktop (~/.claude/claude_desktop_config.json)
{
"mcpServers": {
"repo-butler": {
"command": "node",
"args": ["/path/to/repo-butler/src/mcp.js"]
}
}
}Once connected, the AI gets twelve tools: get_health_tier (tier + checklist for any repo), get_campaign_status (portfolio compliance), query_portfolio (filter by tier/language), get_snapshot_diff (what changed since last run), get_weekly_trend (up to 12 weeks of per-repo or portfolio-wide history), get_governance_findings (every finding type, with autofix-not-driven and tier-regression counts), get_open_governance_prs (outstanding repo-butler/apply-* PRs across the portfolio), list_stale_dependabot_prs (stale dependency PRs by minimum age), trigger_refresh (dispatch the workflow via gh CLI), get_monitor_events (events captured between daily runs), get_watchlist (council-watchlisted proposals), and get_council_personas (the five reviewer personas). It also exposes three resources: the latest snapshot, portfolio health summary, and campaign status.
For A2A-protocol-aware agents, the butler publishes an AgentCard at ismaelmartinez.github.io/repo-butler/.well-known/agent-card.json. It declares the butler's skills (portfolio-health, governance-findings, campaign-status, snapshot-diff, monitor-events, council-triage) for capability discovery. The card is discovery-only — the live programmatic interface is the MCP server above.
- Zero dependencies. No
npm installneeded. - Generic. Any repo can use it by adding a config file and a workflow.
- Conservative. Max 3 issues per run,
require_approvalgate enforced, dry-run by default. - Safe. All LLM output validated before publishing — URL allowlist, @mention blocking, secret detection, XSS prevention.
- Free to run. GitHub Actions is unlimited for public repos, Gemini Flash free tier for LLM calls.
- Self-dogfooding. This repo uses itself as its own planner.
To report a vulnerability, see SECURITY.md. It also documents the trust model — GitHub App token scope, untrusted-data boundaries, the repo-butler-data branch treatment, and cross-repo write gates.
MIT