Automated GitHub activity tracking with AI-generated weekly team reports delivered to Slack.
Small dev teams have a visibility problem. Every Friday, engineering leads are left asking — what did we actually ship this week? GitHub Insights shows graphs but no narrative. Jira is too heavyweight for most teams. Nobody writes changelogs manually.
DevPulse solves this with zero behaviour change from the team. It watches your GitHub activity silently, computes meaningful weekly metrics, and delivers a human-readable AI-generated summary to your Slack channel every Monday morning. Nobody logs into a dashboard. Nobody fills out a form.
Developer pushes code
│
▼
POST /api/webhooks/github
│ HMAC-SHA256 signature verification
│ Idempotency check via delivery ID
▼
raw_events table (PostgreSQL)
│
│ Every night at 2am
▼
MetricsComputationService
│ 10 derived metrics computed from raw payloads
│ Commit counts, PR lifecycle, contributor breakdown,
│ bug/feature ratio, most changed file, velocity
▼
weekly_metrics table (PostgreSQL)
│
│ Every Monday at 9am
▼
Gemini API ──► AI-generated professional summary
│
▼
Slack channel ◄── Team receives digest
│
▼
delivery_logs table (full audit trail)
Two decoupled worlds running in the same process:
- Reactive — webhook ingestion, responds in milliseconds, always available
- Scheduled — nightly analysis and Monday reporting, completely independent
Week of April 13th recorded 6 total commits, all from Bhavya-Sonigra. The most frequently modified file was WebhookController.java, with changes also touching GeminiService.java and MetricsComputationService.java. Commit messages indicate 2 new features shipped and 1 bug fix resolved.
No pull requests were opened, merged, or remain in review this week. Average PR review time is not applicable for this period.
Every night, MetricsComputationService derives these from raw GitHub payloads:
| Metric | Source |
|---|---|
| Total commits | Push event commit arrays |
| Commits per contributor | Push event pusher field |
| Top contributor | Max of commits per user map |
| Most changed file | Union of added + modified across all commits |
| Bug fix count | Commit messages matching: fix, bug, patch, hotfix, resolve, issue |
| Feature count | Commit messages matching: feat, feature, add, new, implement |
| PRs opened | pull_request events with action: opened |
| PRs merged | pull_request events with action: closed + merged: true |
| PRs still open | pull_request events with state: open |
| Average PR open hours | Time delta from created_at to merged_at |
Why store raw webhook payloads before processing? Event sourcing — raw GitHub JSON is stored verbatim before any analysis. If computation logic changes or has a bug, metrics can be recomputed from the original data. The source of truth is never lost.
Why three separate tables?
raw_events is write-heavy, append-only, the source of truth. weekly_metrics is read-heavy, derived, recomputable. delivery_logs is audit-only, append-only. Different access patterns, different lifecycles, clean separation.
Why constant-time signature comparison?
Standard string comparison exits on the first mismatched character. An attacker measuring response latency can reconstruct the valid HMAC one character at a time. constantTimeEquals always runs to completion — timing reveals nothing.
What does @Transactional protect in metrics computation?
Saving WeeklyMetrics and marking raw events as processed = true must be atomic. If the app crashes between these two operations, events would be permanently missed. The transaction rolls both back on failure — next run starts clean.
Why RestTemplate over WebClient? Gemini and Slack are called once per week from a scheduled job. The complexity of reactive HTTP is not warranted. Synchronous RestTemplate with explicit 5s connect / 30s read timeouts is the correct fit for this access pattern.
| Layer | Technology | Why |
|---|---|---|
| Language | Java 21 | LTS, modern text blocks and records |
| Framework | Spring Boot 4 | Industry standard Java backend |
| ORM | Spring Data JPA + Hibernate 7 | Zero SQL for standard operations |
| Database | PostgreSQL 15 | Relational structure for time-series queries |
| AI | Gemini 2.5 Flash | Natural language report generation |
| Delivery | Slack Incoming Webhooks | Zero-friction team channel delivery |
| Container | Docker + docker-compose | Identical environment across dev and CI |
| CI | GitHub Actions | Build, test, Docker image on every push |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/webhooks/github |
Receive GitHub push and pull_request events |
Required headers:
X-Hub-Signature-256: sha256={hmac}
X-GitHub-Event: push | pull_request
X-GitHub-Delivery: {uuid}
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/metrics/latest |
Latest week's computed metrics |
GET |
/api/metrics/history |
Past 90 days of metrics |
GET |
/api/events/recent |
Unprocessed events from last 24 hours |
GET |
/api/deliveries |
All Slack delivery records with status |
POST |
/api/report/trigger |
Manually trigger AI report generation |
POST |
/api/analysis/trigger |
Manually trigger nightly analysis |
{
"weekStart": "2026-04-13",
"totalCommits": 6,
"prsOpened": 0,
"prsMerged": 0,
"prsStillOpen": 0,
"avgPrOpenHours": null,
"topContributor": "Bhavya-Sonigra",
"bugFixCount": 1,
"featureCount": 2,
"mostChangedFile": "WebhookController.java",
"commitsByUser": "{\"Bhavya-Sonigra\": 6}",
"computedAt": "2026-04-18T02:00:03"
}- Java 21+
- Maven 3.9+
- Docker Desktop running
git clone https://github.com/Bhavya-Sonigra/Devpulse.git
cd DevpulseCreate .env at the project root:
GITHUB_WEBHOOK_SECRET=your-webhook-secret
GEMINI_API_KEY=your-gemini-api-key
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz
docker-compose up postgres -dmvn spring-boot:runcurl http://localhost:8080/actuator/health
# {"status":"UP"}docker-compose up --buildngrok http 8080
# Copy the https://xxx.ngrok-free.app URL- Your repo → Settings → Webhooks → Add webhook
- Payload URL:
https://your-domain/api/webhooks/github - Content type:
application/json - Secret: matches your
GITHUB_WEBHOOK_SECRET - Events: select Pushes and Pull requests
GitHub sends a ping immediately — check your logs to confirm.
# 1. Push a real commit (triggers live webhook)
git commit -m "feat: test full pipeline" --allow-empty && git push
# 2. Confirm event was stored
curl http://localhost:8080/api/events/recent
# 3. Run nightly analysis manually
curl -X POST http://localhost:8080/api/analysis/trigger
# 4. Check computed metrics
curl http://localhost:8080/api/metrics/latest
# 5. Generate and deliver AI report
curl -X POST http://localhost:8080/api/report/trigger
# 6. Check audit trail
curl http://localhost:8080/api/deliveriessrc/main/java/com/devpulse/
├── config/
│ ├── AppConfig.java # Environment variable binding
│ └── RestTemplateConfig.java # HTTP client + ObjectMapper beans
├── controller/
│ ├── WebhookController.java # POST /api/webhooks/github
│ └── MetricsController.java # Query and trigger endpoints
├── exception/
│ ├── InvalidSignatureException.java
│ └── GlobalExceptionHandler.java # Centralised error handling, no stack traces leaked
├── model/
│ ├── entity/
│ │ ├── RawEvent.java # raw_events table
│ │ ├── WeeklyMetrics.java # weekly_metrics table
│ │ └── DeliveryLog.java # delivery_logs table
│ └── payload/
│ ├── PushEventPayload.java # GitHub push event shape
│ ├── PullRequestEventPayload.java
│ └── GeminiResponse.java # Gemini API response + extractText()
├── repository/
│ ├── RawEventRepository.java # Method-name queries, custom @Query batch update
│ ├── WeeklyMetricsRepository.java
│ └── DeliveryLogRepository.java
├── scheduler/
│ ├── AnalysisScheduler.java # @Scheduled 2am daily
│ └── ReportScheduler.java # @Scheduled 9am every Monday
├── service/
│ ├── WebhookAuthService.java # HMAC-SHA256 + constant-time comparison
│ ├── WebhookProcessorService.java # Parse, deduplicate, persist
│ ├── MetricsComputationService.java # 10-metric computation engine
│ ├── GeminiService.java # Prompt construction + AI call
│ └── SlackDeliveryService.java # HTTP delivery + audit logging
└── DevpulseApplication.java # @EnableScheduling entry point
| Variable | Description | Required |
|---|---|---|
GITHUB_WEBHOOK_SECRET |
Shared secret for HMAC signature verification | Yes |
GEMINI_API_KEY |
Google AI Studio API key | Yes |
SLACK_WEBHOOK_URL |
Slack Incoming Webhook URL | Yes |
SPRING_DATASOURCE_URL |
PostgreSQL JDBC URL (docker-compose sets automatically) | Production |
SPRING_DATASOURCE_USERNAME |
Database username | Production |
SPRING_DATASOURCE_PASSWORD |
Database password | Production |
| Limitation | Production Solution |
|---|---|
@Scheduled jobs don't persist across restarts |
Quartz Scheduler with DB-backed job store |
| Single team, single repo — no user isolation | Multi-tenancy: team_id on every table, JWT auth |
| No authentication on control endpoints | Spring Security + JWT |
| No retry on external API failure | Resilience4j retry with exponential backoff |
| Two instances both run scheduled jobs | ShedLock distributed lock |
| Raw events accumulate indefinitely | Retention policy: archive after 90 days |
V2 introduces self-service onboarding so any team can connect in under 5 minutes:
- GitHub OAuth — login with GitHub, no account creation
- Automated webhook registration — GitHub API call registers webhook programmatically, user never touches GitHub settings
- Slack OAuth — bot token via OAuth, user picks channel from dropdown
- Multi-tenancy — complete data isolation between teams via
team_idscoping - Next.js dashboard — connect page, metrics visualisation, report history
- Trend detection — week-over-week velocity comparison, contributor concentration alerts