Community health analytics for GitHub organizations and repositories. devpulse imports contribution data from the GitHub API, enriches it with developer affiliations, and surfaces project health insights through a local dashboard.
Motivation and the drivers behind this project are covered in the blog post here
Project Health
- Bus factor / pony factor -- minimum developers or organizations producing 50% of contributions
- Repository Status -- stars, forks, open issues, language, license with 30-day sparkline
- Stars & forks trends -- daily star and fork counts over the last 30 days with historical backfill
- Community Profile -- badges for README, Contributing, Code of Conduct, and issue/PR templates
Activity & Code
- Activity trends -- monthly event volume (PRs, reviews, issues, comments, forks) with total and 3-month moving average
- PR size distribution -- pull requests bucketed by lines changed (S/M/L/XL) per month
- Forks & activity -- monthly fork count vs total event activity
- Issue Open/Close Ratio -- monthly opened vs closed issues
Velocity
- Lead time (PR to merge) -- average days from PR creation to merge
- Change failure rate -- percentage of deployments causing failures (bug issues near releases + revert PRs)
- Release cadence -- monthly release counts (total, stable, deployments) with merge-to-main fallback
- Release downloads -- monthly download trends and top releases by download count
- Time to First Response -- average hours to first review or comment on PRs
Quality
- PR review ratio -- PRs to reviews per month with ratio trend line
- Review latency -- average hours from PR creation to first review
- Time to close -- average days to close all issues vs bug issues near releases
- Contributor reputation -- two-tier scoring (shallow local, deep GitHub API) with known bot filtering
Community
- Contributor retention -- new vs returning contributors per month
- Contributor momentum -- rolling 3-month active contributor count with month-over-month delta
- First-time contributor funnel -- new contributor milestones per month (first comment, first PR, first merge)
- Entity affiliations -- top contributing companies/orgs with drill-down to individual developers (GitHub profile + CNCF gitdm)
Insights
- LLM-generated observations -- AI-powered analysis of repository health, trends, and action items per repo
Dashboard
- Global summary banner -- organizations, repositories, events, contributors, and last import timestamp (GMT) at a glance
- Tabbed layout -- Health, Activity, Velocity, Quality, Community, Insights, and Events tabs with lazy-loaded charts
- Event search filters -- filter by type, date range, username, or entity from the Events tab
- Adjustable time period -- dropdown adapts to available data range per search scope
- Unified search --
org:nameorrepo:nameprefix syntax; all panels respect scope
brew tap mchmarny/tap
brew install devpulseVisit latest releases page.
Requires Go 1.26+.
git clone https://github.com/mchmarny/devpulse.git
cd devpulse
make buildSee DEVELOPMENT.md for details.
devpulse uses GitHub's device flow for OAuth. By default the token requests the repo scope for private repository access. Use --public to skip the repo scope when you only work with public repos. The token is stored in your OS keychain.
devpulse auth # private + public repo access (default)
devpulse auth --public # public repo access onlyAlternatively, set GITHUB_TOKEN to skip the auth command entirely:
export GITHUB_TOKEN=ghp_...Import events, affiliations, metadata, releases, and reputation for an org:
devpulse import --org <org>Or target specific repos:
devpulse import --org <org> --repo <repo1> --repo <repo2>Use --fresh to clear pagination state and re-import from scratch:
devpulse import --org <org> --freshUpdate all previously imported data (no flags needed):
devpulse importControl how many repos import in parallel (default: 3):
devpulse import --concurrency 2See docs/IMPORT.md for all import options.
Import computes basic reputation scores automatically. For deeper scoring using GitHub API signals (profile age, org membership, PR history, etc.):
devpulse score --org <org> # deep-score 5 lowest in org (default)
devpulse score --org <org> --repo <repo> # scope to a specific repo
devpulse score --org <org> --count 20 # deep-score 20 lowestRun incrementally — each invocation scores the next batch of lowest-reputation contributors. See docs/SCORE.md for details.
For automated, scheduled imports, sync reads a config file and imports + scores one repo per run using hour-based round-robin rotation:
devpulse sync --config sync.yaml
devpulse sync --config https://raw.githubusercontent.com/org/repo/main/config/sync.yaml
# Override round-robin to sync a specific repo
devpulse sync --config sync.yaml --org NVIDIA --repo DCGM
# Sync a specific repo without a config file (uses hardcoded defaults)
devpulse sync --org NVIDIA --repo DCGMConfig format (all reputation and insight fields are optional with sensible defaults):
repos:
- name: repo1
org: myorg
reputation:
scoreCount: 50 # contributors to deep-score per run (default: 50)
staleAfter: "3d" # re-score after this duration (default: 3d)
insight:
periodMonths: 3 # months of data for insights (default: 3)
staleAfter: "3d" # regenerate insights after this (default: 3d)
- name: repo2
org: myorg
- name: devpulse
org: mchmarnyThe --config flag (or DEVPULSE_SYNC_CONFIG env var) accepts a local file path or HTTP(S) URL. Each run picks one repo from the list based on UTC hour % total repos, runs a full import, then deep-scores the lowest-reputation contributors. With 9 repos on an hourly schedule, each repo is imported 2-3 times per day while staying within GitHub's 5,000 requests/hour API rate limit. Reputation and insight thresholds are configured per-repo in the YAML file.
devpulse serverOpens your browser to http://127.0.0.1:8080. Use --port to change the port or --no-browser to suppress auto-open.
The dashboard shows a global summary banner (orgs, repos, events, contributors, last import timestamp in GMT) and organizes insights into seven tabs: Health, Activity, Velocity, Quality, Community, Insights, and Events. Charts load lazily per tab.
You can run devpulse import in a separate terminal or cron job while the server is running — the dashboard picks up new data immediately after each import transaction commits. See docs/SERVER.md for details.
Use the search bar with prefix syntax to scope the dashboard:
| Prefix | Example | Scope |
|---|---|---|
org: |
org:myorg |
All repos in an organization |
repo: |
repo:skyhook |
Single repository |
No prefix defaults to org search.
devpulse also exposes data as JSON for scripting:
devpulse query events --org knative --repo serving --type pr --since 2024-01-01
devpulse query developer list --like mark
devpulse query entity detail --name GOOGLESee docs/QUERY.md for all query options.
Remove imported data for a specific org or repo:
devpulse delete --org <org> # delete all data for org
devpulse delete --org <org> --repo <repo> # delete data for specific repo
devpulse delete --org <org> --repo <repo> --force # skip confirmation promptDelete all imported data and start fresh:
devpulse resetPrompts for confirmation before deleting the database.
| Source | Data |
|---|---|
| GitHub API | PRs, issues, comments, reviews, forks, repo metadata, releases |
| cncf/gitdm | Developer-to-company affiliations |
Entity names are normalized automatically. Use devpulse substitute to correct misattributions:
devpulse substitute --type entity --old "INTERNATIONAL BUSINESS MACHINES" --new "IBM"By default, data is stored locally in SQLite (~/.devpulse/data.db). No external services required.
To use PostgreSQL instead, pass a postgres:// connection URI via --db or DEVPULSE_DB:
devpulse import --db "postgres://user:pass@host:5432/dbname?sslmode=disable" --org <org> --repo <repo>
devpulse server --db "postgres://user:pass@host:5432/dbname?sslmode=disable"Or via environment variable:
export DEVPULSE_DB="postgres://user:pass@host:5432/dbname?sslmode=disable"
devpulse import --org <org> --repo <repo>Migrations run automatically on first connection. Special characters in the password must be URL-encoded (e.g., / → %2F, @ → %40).
For Google Cloud AlloyDB, connect through the AlloyDB Auth Proxy with --public-ip and use 127.0.0.1 as the host.
The Insights tab uses an LLM to generate observations and action items. Configure via environment variables:
export ANTHROPIC_API_KEY="sk-ant-..." # required for Insights tab
export ANTHROPIC_BASE_URL="https://..." # optional, override API endpoint
export ANTHROPIC_MODEL="claude-..." # optional, override model selectionSee docs/ARCHITECTURE.md for details.
Release binaries are signed and attested in CI. No private keys — everything uses keyless Sigstore OIDC via GitHub Actions.
cosign verify-blob \
--bundle checksums-sha256.txt.sigstore.json \
--certificate-identity-regexp 'github.com/mchmarny/devpulse' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums-sha256.txtgh attestation verify <binary> -R mchmarny/devpulseEach binary has a corresponding SBOM (SPDX JSON) attached to the release.
Contributions are welcome. Please open an issue before submitting large changes. See CONTRIBUTING.md for guidelines and DEVELOPMENT.md for setup.
- Fork and clone the repository
- Create a feature branch
- Run
make qualify(tests, lint, vulnerability scan) - Submit a pull request





