Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when you need to generate and post automated daily standup updates. Aggregates ticket tracker, GitHub activity, and prior standups into formatted updates with delta-only reporting.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 116% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 256% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 267% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 182% | 0% |
Automates daily standup reporting by aggregating ticket tracker activity, GitHub changes, and previous standups into concise, delta-only updates for team communication.
Create config.json in the skill directory:
json{ "team": { "name": "platform-team", "members": ["alice", "bob", "charlie"], "manager": "diana" }, "integrations": { "slack": { "channel": "#platform-standup", "webhook_url": "${SLACK_WEBHOOK_URL}", "post_as_user": true }, "github": { "org": "company", "repos": ["platform-api", "platform-ui", "platform-deploy"], "token": "${GITHUB_TOKEN}" }, "ticket_system": { "type": "jira", // or "linear", "asana" "project": "PLAT", "token": "${JIRA_TOKEN}" } }, "schedule": { "timezone": "America/Los_Angeles", "post_time": "09:00", "weekend_mode": "skip" }, "filters": { "exclude_labels": ["wontfix", "duplicate"], "include_states": ["In Progress", "Review", "Done"], "min_activity_threshold": 1 } }
standups.log for future referencebash# Generate and post standup python main.py post --config config.json # Generate only (don't post) python main.py generate --date yesterday --output standup.md # Custom date range python main.py generate --from-date 2024-01-15 --to-date 2024-01-16 # Specific team members python main.py post --members alice,bob --config config.json # Dry run with preview python main.py post --dry-run --preview
markdown# Platform Team Standup - January 16, 2024 ## 🎯 Highlights - **Feature**: User authentication service deployed to staging (alice) - **Bug Fix**: Fixed memory leak in API gateway (bob) - **Infrastructure**: Database migration completed (charlie) ## 📋 Activity Summary ### Alice (@alice) - ✅ PR #234: Add OAuth2 authentication (merged) - 🎫 PLAT-123: Implement user login (in review) - 📝 Updated API documentation ### Bob (@bob) - 🔧 PR #235: Fix memory leak in gateway (merged) - 🐛 PLAT-125: Resolve caching issue (done) - 📊 Performance testing completed ### Charlie (@charlie) - 🗄️ PLAT-127: Database migration (completed) - 📋 Infrastructure review checklist updated - 🔍 Security audit in progress ## 📊 Metrics - **PRs Merged**: 3 - **Tickets Completed**: 2 - **Commits**: 15 - **Team Members Active**: 3/3 ## 🔗 Links - [GitHub Activity](https://github.com/company/platform-api/pulls) - [Jira Board](https://company.atlassian.net/secure/RapidBoard.jspa?project=PLAT) - [Deployment Dashboard](https://deploy.company.com/platform) ## 📝 Notes - Code review meeting scheduled for 2 PM - Production deployment window tomorrow 10-11 AM
standups.log)json{ "date": "2024-01-16", "timestamp": "2024-01-16T09:00:00Z", "team": "platform-team", "summary": { "prs_merged": 3, "tickets_completed": 2, "commits": 15, "active_members": 3 }, "activities": [ { "member": "alice", "type": "pull_request", "action": "merged", "title": "Add OAuth2 authentication", "url": "https://github.com/company/platform-api/pull/234" } ], "posted_to_slack": true, "slack_message_id": "1234567890.123456" }
main.py - Main orchestration scriptpython#!/usr/bin/env python3 """ Standup Post Automation Aggregates team activity and posts daily standup updates """ # /// script # dependencies = [ # "requests>=2.28.0", # "PyYAML>=6.0", # "python-dateutil>=2.8.0", # "slack-sdk>=3.19.0", # "PyGithub>=1.58.0" # ] # /// import json import logging from datetime import datetime, timedelta from pathlib import Path from slack_sdk import WebClient from github import Github import requests class StandupGenerator: def __init__(self, config_path: str): self.config = self._load_config(config_path) self.slack_client = WebClient(token=self.config["integrations"]["slack"]["webhook_url"]) self.github_client = Github(self.config["integrations"]["github"]["token"]) self.standup_log = Path("standups.log") def _load_config(self, config_path: str) -> dict: """Load configuration from JSON file""" with open(config_path, 'r') as f: return json.load(f) def generate_standup(self, date: datetime = None) -> dict: """Generate standup report for specified date""" if date is None: date = datetime.now() - timedelta(days=1) # Collect activities from all sources github_activities = self._fetch_github_activity(date) ticket_activities = self._fetch_ticket_activity(date) # Combine and analyze activities all_activities = github_activities + ticket_activities activities_by_member = self._group_by_member(all_activities) # Generate report report = self._generate_report(date, activities_by_member) return report def post_to_slack(self, report: dict) -> str: """Post standup report to Slack channel""" message = self._format_slack_message(report) response = self.slack_client.chat_postMessage( channel=self.config["integrations"]["slack"]["channel"], text=message, username="Standup Bot" ) return response["ts"] def save_to_log(self, report: dict, slack_message_id: str = None): """Save standup to historical log""" log_entry = { "date": report["date"], "timestamp": datetime.now().isoformat(), "team": self.config["team"]["name"], "summary": report["summary"], "activities": report["activities"], "posted_to_slack": slack_message_id is not None, "slack_message_id": slack_message_id } with open(self.standup_log, 'a') as f: f.write(json.dumps(log_entry) + '\n') def _fetch_github_activity(self, date: datetime) -> list: """Fetch GitHub activity for the date""" activities = [] for repo_name in self.config["integrations"]["github"]["repos"]: repo = self.github_client.get_repo( f"{self.config['integrations']['github']['org']}/{repo_name}" ) # Fetch commits commits = repo.get_commits(since=date, until=date + timedelta(days=1)) for commit in commits: activities.append({ "member": commit.author.login if commit.author else "unknown", "type": "commit", "action": "pushed", "title": commit.commit.message.split('\n')[0], "url": commit.html_url, "timestamp": commit.commit.author.date }) # Fetch pull requests pulls = repo.get_pulls(state="all", sort="updated", direction="desc") for pr in pulls: if pr.updated_at.date() == date.date(): activities.append({ "member": pr.user.login, "type": "pull_request", "action": pr.state, "title": pr.title, "url": pr.html_url, "timestamp": pr.updated_at }) return activities def _fetch_ticket_activity(self, date: datetime) -> list: """Fetch ticket system activity for the date""" # Implementation varies by ticket system type if self.config["integrations"]["ticket_system"]["type"] == "jira": return self._fetch_jira_activity(date) # Add other ticket systems as needed return [] def _group_by_member(self, activities: list) -> dict: """Group activities by team member""" members = self.config["team"]["members"] grouped = {member: [] for member in members} grouped["other"] = [] for activity in activities: member = activity.get("member", "other") if member in grouped: grouped[member].append(activity) else: grouped["other"].append(activity) return grouped def _generate_report(self, date: datetime, activities_by_member: dict) -> dict: """Generate the standup report""" report = { "date": date.strftime("%Y-%m-%d"), "team": self.config["team"]["name"], "activities": [], "summary": self._calculate_summary(activities_by_member) } # Process activities for each member for member, activities in activities_by_member.items(): if activities and member != "other": member_activities = self._process_member_activities(member, activities) report["activities"].extend(member_activities) return report def _format_slack_message(self, report: dict) -> str: """Format report for Slack posting""" lines = [ f"📋 **{report['team'].title()} Standup - {report['date']}**", "" ] # Add highlights highlights = [a for a in report["activities"] if a.get("priority") == "high"] if highlights: lines.append("🎯 **Highlights**") for activity in highlights[:5]: # Limit to top 5 lines.append(f"• {activity['title']} ({activity['member']})") lines.append("") # Add member activities current_member = None for activity in report["activities"]: if activity["member"] != current_member: current_member = activity["member"] lines.append(f"**{current_member.title()}**") emoji = self._get_activity_emoji(activity["type"], activity["action"]) lines.append(f"{emoji} {activity['title']}") # Add metrics summary = report["summary"] lines.extend([ "", f"📊 **Metrics**: {summary['prs_merged']} PRs, {summary['tickets_completed']} tickets, {summary['commits']} commits", f"👥 **Active**: {summary['active_members']}/{len(self.config['team']['members'])} members" ]) return "\n".join(lines) def main(): import argparse parser = argparse.ArgumentParser(description="Generate and post standup updates") parser.add_argument("--config", required=True, help="Configuration file path") parser.add_argument("--date", help="Date for standup (YYYY-MM-DD)") parser.add_argument("--dry-run", action="store_true", help="Generate but don't post") parser.add_argument("--preview", action="store_true", help="Preview output") args = parser.parse_args() generator = StandupGenerator(args.config) # Parse date if provided if args.date: target_date = datetime.strptime(args.date, "%Y-%m-%d") else: target_date = datetime.now() - timedelta(days=1) # Generate standup report = generator.generate_standup(target_date) if args.preview or args.dry_run: print(json.dumps(report, indent=2)) if not args.dry_run: # Post to Slack message_id = generator.post_to_slack(report) generator.save_to_log(report, message_id) print(f"Standup posted to Slack (message ID: {message_id})") if __name__ == "__main__": main()
The skill maintains standups.log with:
Use historical data to:
Load these files when needed:
scripts/jira-client.py - Jira API integration helperscripts/github-client.py - GitHub API wrappertemplates/standup-template.md - Custom report templatesexamples/team-configs/ - Example team configurationsOther measured skills in the registry, with their headline benchmark lift.