
Summarize Meeting
- 101 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
summarize-meeting is a Claude skill that turns meeting notes or transcripts into a structured summary of decisions, action items, and open questions.
About
Summarize-meeting turns meeting notes, transcripts, or recordings into a consistent structured summary. It captures metadata, key discussion points, single-owner action items with due dates, decisions, and open questions. A developer or PM uses it after any meeting where decisions were made or actions assigned.
- Turns meeting notes or transcripts into consistent structured summaries
- Captures decisions, single-owner action items with due dates, and open questions
- Ships a fixed output template plus an action-item quality checklist
Summarize Meeting by the numbers
- 101 all-time installs (skills.sh)
- Ranked #1,363 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
summarize-meeting capabilities & compatibility
- Capabilities
- meeting summary · action tracking · decision logging
- Works with
- confluence · notion · jira
- Use cases
- documentation · project management · transcription
- Pricing
- Free
What summarize-meeting says it does
Every action item must answer three questions:
Transform meeting notes, transcripts, or recordings into clear, actionable summaries.
npx skills add https://github.com/borghei/claude-skills --skill summarize-meetingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 101 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Transform meeting notes or a transcript into a structured summary of decisions, action items, and open questions.
Who is it for?
Anyone summarizing a meeting where decisions were made or actions were assigned.
Skip if: Detailed technical discussions better captured in design docs, or status updates already available in Jira or dashboards.
When should I use this skill?
After any meeting, sprint ceremony, or stakeholder call that produced decisions or commitments.
What you get
A consistent markdown summary with metadata, decisions, single-owner action items, and open questions.
- meeting summary
- action-item table
- decision log
By the numbers
- 6-step methodology
- 3-question action-item test
Files
Meeting Summary Expert
Overview
Transform meeting notes, transcripts, or recordings into clear, actionable summaries. Every summary follows a consistent structure that makes it easy for attendees and non-attendees alike to understand what was discussed, what was decided, and who is doing what by when.
When to Use
- After any meeting where decisions were made or actions were assigned.
- Sprint ceremonies -- planning, retro, backlog refinement, sprint review.
- Stakeholder meetings -- steering committees, executive reviews, client calls.
- Ad-hoc discussions -- when an impromptu conversation produces commitments that need tracking.
Methodology
Step 1: Capture Meeting Metadata
Record the essential context:
| Field | Description |
|---|---|
| Date | Meeting date (YYYY-MM-DD) |
| Time | Start and end time with timezone |
| Participants | Names and roles (e.g., "Sarah Chen, Product Lead") |
| Topic | One-line meeting purpose |
| Location | Room name, video link, or "async" |
Step 2: Extract Key Discussion Points
From the raw notes or transcript, identify the substantive topics discussed. Guidelines:
- Summarize, do not transcribe. Capture the essence of each topic in 1-3 bullet points.
- Use plain language. Avoid jargon. Anyone reading the summary should understand the points without having attended.
- Focus on what matters. Skip small talk, repeated points, and tangential discussions.
- Note disagreements. If there was significant debate, capture the key positions and how they were resolved (or not).
Step 3: Extract Action Items
Every action item must answer three questions:
1. Who is responsible? (Single owner, not a team) 2. What specifically must they do? (Concrete, observable deliverable) 3. By when? (Specific date, not "soon" or "next sprint")
Format as a table:
| Due Date | Owner | Action |
|---|---|---|
| 2026-03-10 | Sarah Chen | Share revised wireframes with the design team |
| 2026-03-07 | James Park | Schedule load test for the staging environment |
Action item quality checks:
- Each action has exactly one owner (not "Sarah and James")
- The deliverable is specific enough to verify completion
- The due date is a calendar date, not a relative timeframe
- Actions use active verbs: "share," "schedule," "draft," "review," "decide"
Step 4: Record Decisions
List each decision made during the meeting as a numbered item. Include enough context that someone who was not present understands the decision and its rationale.
Format:
1. [Decision] -- [Brief rationale or context]. Decided by [who]. 2. [Decision] -- [Brief rationale or context]. Decided by [who].
Examples:
1. Launch date set for April 15 -- Allows two full sprints for QA after feature freeze on March 28. Decided by steering committee. 2. Use PostgreSQL instead of MongoDB for the analytics service -- Team consensus based on query pattern analysis showing 80% relational queries. Decided by engineering leads.
Step 5: Capture Open Questions
List unresolved questions that need follow-up. For each question, note who is expected to provide an answer and by when, if known.
1. Do we need a separate staging environment for the new analytics service? (James to investigate by March 10) 2. What is the budget ceiling for the Q2 marketing campaign? (Pending finance review)
Step 6: Save and Distribute
File naming convention: Meeting-Summary-[YYYY-MM-DD]-[topic-slug].md
Examples:
Meeting-Summary-2026-03-04-sprint-planning.mdMeeting-Summary-2026-03-04-q2-roadmap-review.md
Distribution:
- Share the summary within 24 hours of the meeting.
- Send to all participants and relevant stakeholders who were not present.
- Store in the team's shared documentation space (Confluence, Notion, shared drive).
Output Template
# Meeting Summary
## Metadata
| Field | Value |
|-------|-------|
| **Date** | [YYYY-MM-DD] |
| **Time** | [HH:MM] - [HH:MM] [TZ] |
| **Participants** | [Name, Role]; [Name, Role]; ... |
| **Topic** | [One-line meeting purpose] |
## Summary
- [Key discussion point 1]
- [Key discussion point 2]
- [Key discussion point 3]
## Action Items
| Due Date | Owner | Action |
|----------|-------|--------|
| [YYYY-MM-DD] | [Name] | [Specific, verifiable action] |
## Decisions Made
1. **[Decision]** -- [Rationale]. Decided by [who].
## Open Questions
1. [Question]? ([Who is expected to answer, by when])What to Focus On
When summarizing, prioritize:
- Decisions that affect roadmap or strategy -- These have the broadest impact and are most likely to be referenced later.
- Who does what by when -- Accountability is the primary value of a meeting summary.
- Blockers and risks surfaced -- These need visibility beyond the meeting room.
- Changes to previously agreed plans -- These create confusion if not documented.
When summarizing, deprioritize:
- Status updates that are available elsewhere (Jira, dashboards)
- Repetition of information already documented
- Social conversation and small talk
- Detailed technical discussions better captured in design docs
Integration with Other Skills
- Feed decisions into
wwas/to create backlog items with strategic context. - Use action items to create tickets via
../jira-expert/. - Document recurring meeting outcomes in
../confluence-expert/templates.
References
- See
references/meeting-facilitation-guide.mdfor meeting types, note-taking strategies, and anti-patterns. - See
assets/meeting_summary_template.mdfor ready-to-use templates.
Troubleshooting
| Problem | Likely Cause | Resolution |
|---|---|---|
| Action items are assigned to teams instead of individuals | Culture avoids individual accountability; facilitator does not press for a single owner | Enforce the "one owner" rule during the meeting; if a team is named, ask "Who on that team is the single point of contact?" |
| Summaries are too long and nobody reads them | Summarizer includes too much detail; tries to capture everything | Apply the "would someone who missed the meeting need this?" filter to every bullet point; target 1 page maximum for 1-hour meetings |
| Decisions are not documented, leading to re-litigation in future meetings | Meeting moved quickly; facilitator focused on discussion, not decisions | Pause after each decision and state it aloud: "Let me confirm: we decided X because Y"; add decision capture as a facilitator checklist item |
| Action items have vague due dates ("soon", "next sprint") | Facilitator does not push for specificity; team uncomfortable committing to dates | Require a calendar date for every action; if the team cannot commit, set a date to decide the date |
| Summaries are distributed days after the meeting | Summarizer is overburdened or perfectionist | Set a 24-hour distribution rule; use a structured template to reduce writing effort; assign summary responsibility before the meeting |
| Open questions from previous meetings are never resolved | No follow-up mechanism; questions captured but not tracked | Add "Previous Open Questions" as a standing agenda item; assign each question an owner and a resolution date |
| Attendees disagree with the summary after distribution | Summary reflects summarizer's interpretation, not group consensus | Share key decisions and action items verbally at the meeting close; invite corrections within 24 hours of distribution |
Success Criteria
- 100% of meetings with decisions or action items produce a written summary within 24 hours
- Every action item has a single named owner and a specific calendar due date
- Action item completion rate exceeds 80% by the stated due date
- Summaries are 1 page or less for meetings under 1 hour
- Decisions are documented with enough context that a non-attendee can understand the rationale
- Open questions from previous meetings are tracked and resolved within 2 meeting cycles
- Meeting summary satisfaction (from periodic team survey) averages 4+/5
Scope & Limitations
In Scope: Capturing meeting metadata, extracting key discussion points, documenting decisions with rationale, recording action items with owners and due dates, capturing open questions, distributing summaries, maintaining consistent naming conventions and storage.
Out of Scope: Meeting facilitation and agenda design, real-time transcription (use a transcription tool as input), project status tracking (hand off to ../jira-expert/), strategic decision frameworks (hand off to ../senior-pm/), recording or video management.
Limitations: Summary quality is bounded by the quality of input notes or transcript. Automated transcription tools may introduce errors that the summarizer must catch. The skill does not replace the need for a skilled facilitator -- poorly run meetings produce poor summaries regardless of template quality. Sensitive or confidential meetings may require restricted distribution that the standard workflow does not address.
Integration Points
| Integration | Direction | What Flows |
|---|---|---|
wwas/ | Meetings -> WWAS | Decisions and commitments from meetings become WWAS backlog items |
job-stories/ | Meetings -> Stories | Discovery discussions surface situations and motivations for job stories |
../jira-expert/ | Meetings -> Jira | Action items create Jira tickets; decisions update issue comments |
../confluence-expert/ | Meetings -> Confluence | Summaries stored in Confluence using meeting notes template |
../senior-pm/ | Meetings -> PM | Steering committee and stakeholder meeting summaries feed portfolio reporting |
../delivery-manager/ | Meetings -> DM | Release planning and incident review meeting outcomes feed delivery tracking |
Meeting Summary
Metadata
| Field | Value |
|---|---|
| Date | [YYYY-MM-DD] |
| Time | [HH:MM] - [HH:MM] [TZ] |
| Participants | [Name, Role]; [Name, Role]; [Name, Role] |
| Topic | [One-line meeting purpose] |
| Location | [Room / Video link / Async] |
Summary
- [Key discussion point 1 -- what was discussed and why it matters]
- [Key discussion point 2]
- [Key discussion point 3]
- [Key discussion point 4, if applicable]
Action Items
| Due Date | Owner | Action |
|---|---|---|
| [YYYY-MM-DD] | [Single owner name] | [Specific, verifiable action with active verb] |
| [YYYY-MM-DD] | [Single owner name] | [Specific, verifiable action with active verb] |
| [YYYY-MM-DD] | [Single owner name] | [Specific, verifiable action with active verb] |
Decisions Made
1. [Decision statement] -- [Brief rationale or context]. Decided by [who]. 2. [Decision statement] -- [Brief rationale or context]. Decided by [who].
Open Questions
1. [Unresolved question]? ([Who is expected to answer, by when]) 2. [Unresolved question]? ([Who is expected to answer, by when])
---
File name: Meeting-Summary-[YYYY-MM-DD]-[topic-slug].md Distributed to: [List of recipients] Next meeting: [Date and time, if recurring]
---
Follow-Up Email Template
Subject: Meeting Summary: [Topic] -- [Date]
Hi team,
Attached is the summary from today's [topic] meeting. Key highlights:
- Decisions: [1-2 sentence summary of the most important decisions]
- Actions: [Number] action items assigned -- see the summary for details and due dates.
- Open: [Number] questions remain open -- owners noted in the summary.
Please review and flag any corrections by [deadline].
Thanks, [Your name]
---
Decision Log Template
Use this format to maintain a running log of decisions across meetings.
| Date | Meeting | Decision | Rationale | Decided By | Status |
|---|---|---|---|---|---|
| [YYYY-MM-DD] | [Meeting name] | [Decision] | [Why] | [Who] | Active / Superseded / Reversed |
Meeting Facilitation Guide
Meeting Types and Best Practices
Decision Meetings
Purpose: Make a specific decision with the right stakeholders in the room.
Best practices:
- State the decision to be made in the meeting invite.
- Share pre-read materials at least 24 hours in advance.
- Timebox discussion. If no decision after the allotted time, explicitly decide to defer and state what additional information is needed.
- End with a clear statement of the decision and who will communicate it.
Summary focus: The decision, the rationale, and the communication plan.
Status / Update Meetings
Purpose: Align the team on progress, blockers, and next steps.
Best practices:
- Keep to 30 minutes or less.
- Use a standing agenda (same structure each time).
- Focus on exceptions -- what is off track, what needs help.
- Do not use the meeting to share information that could be read asynchronously.
Summary focus: Blockers, changes to plan, and escalations.
Brainstorming / Working Sessions
Purpose: Generate ideas or solve a problem collaboratively.
Best practices:
- Define the problem statement before the meeting.
- Separate idea generation from idea evaluation.
- Use structured techniques (brainwriting, round-robin, dot voting).
- Capture all ideas, even ones not selected.
Summary focus: Ideas generated, criteria used to evaluate, and which ideas move forward.
Planning Meetings
Purpose: Agree on scope, timeline, and assignments for upcoming work.
Best practices:
- Come with a draft plan, not a blank slate.
- Walk through dependencies and risks.
- Assign owners to every deliverable.
- Confirm the next checkpoint.
Summary focus: Commitments (who, what, when), dependencies, and risks.
Retrospectives
Purpose: Reflect on what worked, what did not, and what to change.
Best practices:
- Create psychological safety -- no blame, focus on systems.
- Use a structured format (Start-Stop-Continue, 4Ls, Mad-Sad-Glad).
- Limit to 3 actionable improvements per retro.
- Assign owners to improvement actions.
Summary focus: Top improvement actions with owners and due dates.
Note-Taking Strategies
The Cornell Method (Adapted for Meetings)
Divide your notes into three sections:
1. Main notes (during meeting) -- Capture discussion points as they happen. 2. Key takeaways (margin) -- Flag decisions, actions, and questions in real-time. 3. Summary (bottom) -- Write a 2-3 sentence summary immediately after the meeting.
Real-Time Action Tagging
As you take notes, prefix actionable items with tags:
[ACTION]-- Something someone needs to do[DECISION]-- Something that was decided[QUESTION]-- Something unresolved[RISK]-- A risk or concern raised[PARKING]-- Off-topic item to revisit later
This makes post-meeting processing much faster.
Collaborative Note-Taking
For important meetings, use a shared document where multiple participants contribute:
- One person captures discussion points.
- Another captures actions and decisions.
- Participants correct or clarify in real-time.
- Reduces single-point-of-failure and improves accuracy.
Action Item Best Practices (SMART Format)
Every action item should be SMART:
| Criterion | Question | Bad Example | Good Example |
|---|---|---|---|
| Specific | What exactly needs to be done? | "Look into the performance issue" | "Profile the dashboard API endpoint and identify the top 3 bottlenecks" |
| Measurable | How will we know it is done? | "Improve onboarding" | "Reduce onboarding flow from 8 steps to 5 steps" |
| Assignable | Who is the single owner? | "The team will handle it" | "Maria will handle it" |
| Realistic | Can it be done in the timeframe? | "Rewrite the entire backend by Friday" | "Draft the migration plan by Friday" |
| Time-bound | What is the deadline? | "Do it soon" | "Complete by March 15" |
Follow-Up Processes
Within 24 Hours
1. Finalize and distribute the meeting summary. 2. Create tickets for action items in your tracking system (Jira, Linear, Asana). 3. Update any affected project plans or roadmaps.
At the Next Meeting
1. Review open actions from the previous meeting. 2. Mark completed items and escalate overdue items. 3. Carry forward unresolved questions.
Weekly
1. Review all open action items across meetings. 2. Follow up with owners on items approaching their due date. 3. Escalate blocked items.
Meeting Anti-Patterns
The Status Meeting That Should Be an Email
Symptom: Participants take turns reading updates that could be shared asynchronously. Fix: Use async updates (Slack, email, dashboard) and reserve meeting time for discussion of exceptions and decisions.
The Meeting Without a Decision
Symptom: Lots of discussion, no clear outcome. Same topic appears in the next meeting. Fix: State the decision to be made upfront. If the group cannot decide, explicitly state what information is needed and who will gather it.
The Meeting With Too Many People
Symptom: Most attendees are silent. Side conversations happen. Decisions are deferred because "we need to check with so-and-so." Fix: Invite only decision-makers and direct contributors. Share the summary with everyone else.
The Meeting Without an Agenda
Symptom: Discussion meanders. Important topics get squeezed at the end or skipped entirely. Fix: Every meeting invite must include an agenda with time allocations. No agenda, no meeting.
The Meeting That Runs Over
Symptom: Meetings consistently exceed their scheduled time, causing cascading delays. Fix: Appoint a timekeeper. When time is up, decide to either extend (with consent) or schedule a follow-up. Do not silently run over.
Missing Follow-Through
Symptom: Action items are captured but never tracked. The same issues resurface weeks later. Fix: Review previous action items at the start of every recurring meeting. Use a tracking system, not just meeting notes.
#!/usr/bin/env python3
"""Action Item Tracker - Track meeting action items across multiple meetings.
Aggregates action items from meeting summaries and tracks completion status,
overdue items, and per-person accountability.
Usage:
python action_item_tracker.py --meetings meetings.json
python action_item_tracker.py --meetings meetings.json --json
python action_item_tracker.py --example
"""
import argparse
import json
import sys
from datetime import datetime
from collections import defaultdict
def load_data(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def parse_date(s: str) -> datetime:
for fmt in ("%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"):
try:
return datetime.strptime(s, fmt)
except ValueError:
continue
return None
def analyze_actions(data: dict) -> dict:
meetings = data.get("meetings", [])
today = datetime.now()
all_actions = []
for meeting in meetings:
meeting_date = meeting.get("date", "Unknown")
meeting_topic = meeting.get("topic", "Unknown")
for action in meeting.get("action_items", []):
action["meeting_date"] = meeting_date
action["meeting_topic"] = meeting_topic
all_actions.append(action)
# Classify actions
completed = []
overdue = []
upcoming = []
no_date = []
person_stats = defaultdict(lambda: {"total": 0, "completed": 0, "overdue": 0})
for action in all_actions:
owner = action.get("owner", "Unassigned")
status = action.get("status", "open").lower()
due_str = action.get("due_date", "")
due_dt = parse_date(due_str) if due_str else None
person_stats[owner]["total"] += 1
if status in ("done", "complete", "completed"):
completed.append(action)
person_stats[owner]["completed"] += 1
elif due_dt and due_dt < today:
overdue.append(action)
person_stats[owner]["overdue"] += 1
elif due_dt:
upcoming.append(action)
else:
no_date.append(action)
# Sort overdue by most overdue first
for a in overdue:
due_dt = parse_date(a.get("due_date", ""))
a["days_overdue"] = (today - due_dt).days if due_dt else 0
overdue.sort(key=lambda x: x.get("days_overdue", 0), reverse=True)
# Person accountability
person_results = []
for person, stats in sorted(person_stats.items()):
completion_rate = round(stats["completed"] / stats["total"] * 100) if stats["total"] > 0 else 0
person_results.append({
"person": person,
"total": stats["total"],
"completed": stats["completed"],
"overdue": stats["overdue"],
"completion_rate_pct": completion_rate,
})
person_results.sort(key=lambda x: x["completion_rate_pct"])
total = len(all_actions)
completion_rate = round(len(completed) / total * 100, 1) if total > 0 else 0
recs = []
if overdue:
top_overdue_owners = list(set(a.get("owner", "Unknown") for a in overdue[:5]))
recs.append(f"{len(overdue)} overdue action item(s). Top owners: {', '.join(top_overdue_owners)}")
if no_date:
recs.append(f"{len(no_date)} action item(s) without due dates. Add specific dates for accountability.")
if completion_rate < 70:
recs.append(f"Completion rate is {completion_rate:.0f}% (target: 80%+). Review blockers in next meeting.")
return {
"analysis_date": today.strftime("%Y-%m-%d"),
"total_meetings": len(meetings),
"total_actions": total,
"completed": len(completed),
"overdue": len(overdue),
"upcoming": len(upcoming),
"no_due_date": len(no_date),
"completion_rate_pct": completion_rate,
"overdue_items": [
{"action": a.get("action"), "owner": a.get("owner"), "due_date": a.get("due_date"), "days_overdue": a.get("days_overdue", 0), "meeting": a.get("meeting_topic")}
for a in overdue[:10]
],
"person_accountability": person_results,
"recommendations": recs,
}
def print_report(result: dict) -> None:
print(f"\nAction Item Tracker")
print(f"Date: {result['analysis_date']} | Meetings: {result['total_meetings']}")
print("=" * 65)
print(f"Total: {result['total_actions']} | Done: {result['completed']} | Overdue: {result['overdue']} | Upcoming: {result['upcoming']}")
print(f"Completion Rate: {result['completion_rate_pct']:.0f}%")
if result["overdue_items"]:
print(f"\nOverdue Items:")
for item in result["overdue_items"]:
print(f" [{item['days_overdue']}d overdue] {item['owner']}: {item['action'][:50]} (due: {item['due_date']})")
print(f"\nPerson Accountability:")
print(f" {'Person':<20} {'Total':>6} {'Done':>6} {'Overdue':>8} {'Rate':>6}")
print(f" {'-'*20} {'-'*6} {'-'*6} {'-'*8} {'-'*6}")
for p in result["person_accountability"]:
print(f" {p['person']:<20} {p['total']:>6} {p['completed']:>6} {p['overdue']:>8} {p['completion_rate_pct']:>5}%")
if result["recommendations"]:
print(f"\nRecommendations:")
for i, r in enumerate(result["recommendations"], 1):
print(f" {i}. {r}")
print()
def print_example() -> None:
example = {
"meetings": [
{
"date": "2026-03-14",
"topic": "Sprint Planning",
"action_items": [
{"action": "Draft API spec for new endpoint", "owner": "Alice", "due_date": "2026-03-18", "status": "complete"},
{"action": "Set up staging environment", "owner": "Bob", "due_date": "2026-03-16", "status": "open"},
],
},
{
"date": "2026-03-07",
"topic": "Stakeholder Review",
"action_items": [
{"action": "Prepare Q1 metrics report", "owner": "Carol", "due_date": "2026-03-10", "status": "open"},
{"action": "Schedule design review", "owner": "Alice", "due_date": "2026-03-12", "status": "complete"},
],
},
],
}
print(json.dumps(example, indent=2))
def main():
parser = argparse.ArgumentParser(description="Track meeting action items.")
parser.add_argument("--meetings", type=str, help="Path to meetings JSON file")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--example", action="store_true", help="Print example and exit")
args = parser.parse_args()
if args.example:
print_example()
return
if not args.meetings:
parser.error("--meetings is required")
data = load_data(args.meetings)
result = analyze_actions(data)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Decision Log Builder - Build and query a decision log from meeting data.
Aggregates decisions across meetings and provides search, timeline,
and decision audit capabilities.
Usage:
python decision_log_builder.py --meetings meetings.json
python decision_log_builder.py --meetings meetings.json --search "launch"
python decision_log_builder.py --meetings meetings.json --json
python decision_log_builder.py --example
"""
import argparse
import json
import re
import sys
from datetime import datetime
def load_data(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def build_log(data: dict, search: str = None) -> dict:
meetings = data.get("meetings", [])
decisions = []
for meeting in meetings:
date = meeting.get("date", "Unknown")
topic = meeting.get("topic", "Unknown")
participants = meeting.get("participants", [])
for dec in meeting.get("decisions", []):
decision = {
"id": f"DEC-{date.replace('-', '')}-{len(decisions)+1:03d}",
"date": date,
"meeting_topic": topic,
"decision": dec.get("decision", ""),
"rationale": dec.get("rationale", ""),
"decided_by": dec.get("decided_by", "Unknown"),
"participants": participants,
"category": dec.get("category", "General"),
"impact": dec.get("impact", "Medium"),
"reversible": dec.get("reversible", True),
}
decisions.append(decision)
# Sort by date (most recent first)
decisions.sort(key=lambda x: x["date"], reverse=True)
# Apply search filter
if search:
pattern = re.compile(search, re.IGNORECASE)
decisions = [d for d in decisions if pattern.search(d["decision"]) or pattern.search(d.get("rationale", ""))]
# Category distribution
categories = {}
for d in decisions:
cat = d["category"]
categories[cat] = categories.get(cat, 0) + 1
# Impact distribution
impact_counts = {"High": 0, "Medium": 0, "Low": 0}
for d in decisions:
impact_counts[d["impact"]] = impact_counts.get(d["impact"], 0) + 1
# Quality checks
missing_rationale = [d for d in decisions if not d.get("rationale")]
high_impact_reversible = [d for d in decisions if d["impact"] == "High" and d.get("reversible", True)]
recs = []
if missing_rationale:
recs.append(f"{len(missing_rationale)} decision(s) without documented rationale. Future teams need to understand 'why'.")
if high_impact_reversible:
recs.append(f"{len(high_impact_reversible)} high-impact decision(s) marked as reversible. Verify this is correct and document rollback plans.")
return {
"total_decisions": len(decisions),
"search_query": search,
"categories": categories,
"impact_distribution": impact_counts,
"decisions": decisions,
"quality": {
"missing_rationale": len(missing_rationale),
"decisions_with_rationale_pct": round((len(decisions) - len(missing_rationale)) / len(decisions) * 100, 1) if decisions else 0,
},
"recommendations": recs,
}
def print_report(result: dict) -> None:
print(f"\nDecision Log")
if result["search_query"]:
print(f"Search: '{result['search_query']}'")
print(f"Total Decisions: {result['total_decisions']}")
print("=" * 65)
imp = result["impact_distribution"]
print(f"Impact: High={imp.get('High',0)} | Medium={imp.get('Medium',0)} | Low={imp.get('Low',0)}")
print(f"Rationale Coverage: {result['quality']['decisions_with_rationale_pct']:.0f}%")
print(f"\nDecisions:")
for d in result["decisions"]:
rev = " [reversible]" if d.get("reversible") else " [irreversible]"
print(f"\n {d['id']} ({d['date']})")
print(f" Decision: {d['decision']}")
if d["rationale"]:
print(f" Rationale: {d['rationale'][:80]}")
print(f" Decided by: {d['decided_by']} | Impact: {d['impact']}{rev}")
if result["recommendations"]:
print(f"\nRecommendations:")
for i, r in enumerate(result["recommendations"], 1):
print(f" {i}. {r}")
print()
def print_example() -> None:
example = {
"meetings": [
{
"date": "2026-03-14",
"topic": "Architecture Review",
"participants": ["Alice", "Bob", "Carol"],
"decisions": [
{"decision": "Use PostgreSQL for analytics service", "rationale": "80% relational queries based on pattern analysis", "decided_by": "Engineering leads", "category": "Technical", "impact": "High", "reversible": False},
{"decision": "Adopt trunk-based development", "rationale": "Reduce merge conflicts and improve CI speed", "decided_by": "Alice", "category": "Process", "impact": "Medium", "reversible": True},
],
},
{
"date": "2026-03-07",
"topic": "Sprint Planning",
"participants": ["Alice", "Dave"],
"decisions": [
{"decision": "Launch date set for April 15", "rationale": "Allows 2 full sprints for QA after feature freeze", "decided_by": "Steering committee", "category": "Schedule", "impact": "High", "reversible": True},
],
},
],
}
print(json.dumps(example, indent=2))
def main():
parser = argparse.ArgumentParser(description="Build and query a decision log.")
parser.add_argument("--meetings", type=str, help="Path to meetings JSON file")
parser.add_argument("--search", type=str, help="Search decisions by keyword")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--example", action="store_true", help="Print example and exit")
args = parser.parse_args()
if args.example:
print_example()
return
if not args.meetings:
parser.error("--meetings is required")
data = load_data(args.meetings)
result = build_log(data, args.search)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Meeting Effectiveness Scorer - Score meeting effectiveness from summary data.
Analyzes meeting summaries to score effectiveness based on decisions made,
action items generated, time efficiency, and follow-through.
Usage:
python meeting_effectiveness_scorer.py --meetings meetings.json
python meeting_effectiveness_scorer.py --meetings meetings.json --json
python meeting_effectiveness_scorer.py --example
"""
import argparse
import json
import sys
from datetime import datetime
def load_data(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def score_meeting(meeting: dict) -> dict:
topic = meeting.get("topic", "Unknown")
duration_min = meeting.get("duration_minutes", 60)
participants = meeting.get("participants", [])
decisions = meeting.get("decisions", [])
action_items = meeting.get("action_items", [])
agenda_items = meeting.get("agenda_items", 0)
agenda_covered = meeting.get("agenda_items_covered", 0)
score = 0
max_score = 100
checks = {}
# Decisions made (25 points)
if decisions:
decision_score = min(25, len(decisions) * 8)
checks["decisions"] = {"score": decision_score, "max": 25, "detail": f"{len(decisions)} decision(s) documented"}
else:
decision_score = 0
checks["decisions"] = {"score": 0, "max": 25, "detail": "No decisions documented"}
score += decision_score
# Action items with owners and dates (25 points)
owned_actions = sum(1 for a in action_items if a.get("owner"))
dated_actions = sum(1 for a in action_items if a.get("due_date"))
if action_items:
ownership_pct = owned_actions / len(action_items)
dated_pct = dated_actions / len(action_items)
ai_score = int(min(25, len(action_items) * 5 * ((ownership_pct + dated_pct) / 2)))
checks["action_items"] = {"score": ai_score, "max": 25, "detail": f"{len(action_items)} items ({owned_actions} owned, {dated_actions} dated)"}
else:
ai_score = 0
checks["action_items"] = {"score": 0, "max": 25, "detail": "No action items"}
score += ai_score
# Time efficiency (20 points)
if duration_min <= 30:
time_score = 20
elif duration_min <= 60:
time_score = 15
elif duration_min <= 90:
time_score = 10
else:
time_score = 5
# Bonus for output density
outputs = len(decisions) + len(action_items)
density = outputs / (duration_min / 30) if duration_min > 0 else 0
if density >= 3:
time_score = min(20, time_score + 5)
checks["time_efficiency"] = {"score": time_score, "max": 20, "detail": f"{duration_min}min, {outputs} outputs, density: {density:.1f}/30min"}
score += time_score
# Agenda coverage (15 points)
if agenda_items > 0:
coverage = agenda_covered / agenda_items
agenda_score = int(15 * coverage)
checks["agenda_coverage"] = {"score": agenda_score, "max": 15, "detail": f"{agenda_covered}/{agenda_items} agenda items covered ({coverage*100:.0f}%)"}
else:
agenda_score = 8 # No agenda is a mild negative
checks["agenda_coverage"] = {"score": agenda_score, "max": 15, "detail": "No agenda defined"}
score += agenda_score
# Participant efficiency (15 points)
if len(participants) <= 5:
part_score = 15
elif len(participants) <= 8:
part_score = 10
elif len(participants) <= 12:
part_score = 5
else:
part_score = 0
checks["participants"] = {"score": part_score, "max": 15, "detail": f"{len(participants)} participants"}
score += part_score
if score >= 80:
rating = "Highly Effective"
elif score >= 60:
rating = "Effective"
elif score >= 40:
rating = "Needs Improvement"
else:
rating = "Ineffective"
return {
"topic": topic,
"date": meeting.get("date", "Unknown"),
"score": score,
"rating": rating,
"duration_minutes": duration_min,
"participant_count": len(participants),
"decision_count": len(decisions),
"action_item_count": len(action_items),
"checks": checks,
}
def analyze_meetings(data: dict) -> dict:
meetings = data.get("meetings", [])
results = [score_meeting(m) for m in meetings]
scores = [r["score"] for r in results]
avg = round(sum(scores) / len(scores), 1) if scores else 0
total_time = sum(r["duration_minutes"] for r in results)
total_decisions = sum(r["decision_count"] for r in results)
total_actions = sum(r["action_item_count"] for r in results)
recs = []
low_scoring = [r for r in results if r["score"] < 50]
if low_scoring:
recs.append(f"{len(low_scoring)} meeting(s) scored below 50. Review if these meetings are needed or need restructuring.")
no_decisions = [r for r in results if r["decision_count"] == 0]
if no_decisions:
recs.append(f"{len(no_decisions)} meeting(s) produced no decisions. Consider if the meeting purpose was clear.")
long_meetings = [r for r in results if r["duration_minutes"] > 60]
if long_meetings:
recs.append(f"{len(long_meetings)} meeting(s) exceeded 60 minutes. Review agenda focus and consider splitting.")
return {
"total_meetings": len(results),
"average_score": avg,
"total_time_minutes": total_time,
"total_decisions": total_decisions,
"total_action_items": total_actions,
"meetings": results,
"recommendations": recs,
}
def print_report(result: dict) -> None:
print(f"\nMeeting Effectiveness Report")
print(f"Meetings: {result['total_meetings']} | Avg Score: {result['average_score']:.0f}/100")
print("=" * 65)
print(f"Total Time: {result['total_time_minutes']}min | Decisions: {result['total_decisions']} | Action Items: {result['total_action_items']}")
print(f"\nPer-Meeting Scores:")
print(f" {'Date':<12} {'Topic':<25} {'Score':>6} {'Rating':<18} {'Dec':>4} {'AI':>4} {'Min':>4}")
print(f" {'-'*12} {'-'*25} {'-'*6} {'-'*18} {'-'*4} {'-'*4} {'-'*4}")
for m in sorted(result["meetings"], key=lambda x: x["score"]):
topic = m["topic"][:23] + ".." if len(m["topic"]) > 25 else m["topic"]
print(f" {m['date']:<12} {topic:<25} {m['score']:>5}% {m['rating']:<18} {m['decision_count']:>4} {m['action_item_count']:>4} {m['duration_minutes']:>4}")
if result["recommendations"]:
print(f"\nRecommendations:")
for i, r in enumerate(result["recommendations"], 1):
print(f" {i}. {r}")
print()
def print_example() -> None:
example = {
"meetings": [
{
"date": "2026-03-14",
"topic": "Sprint Planning",
"duration_minutes": 60,
"participants": ["Alice", "Bob", "Carol", "Dave"],
"agenda_items": 4,
"agenda_items_covered": 4,
"decisions": [
{"decision": "Focus on onboarding flow this sprint", "rationale": "Highest impact item"},
],
"action_items": [
{"action": "Create Jira tickets for onboarding epics", "owner": "Alice", "due_date": "2026-03-15"},
{"action": "Draft design spec", "owner": "Bob", "due_date": "2026-03-17"},
],
},
{
"date": "2026-03-12",
"topic": "Status Sync",
"duration_minutes": 45,
"participants": ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Henry"],
"agenda_items": 0,
"agenda_items_covered": 0,
"decisions": [],
"action_items": [{"action": "Look into the issue", "owner": "", "due_date": ""}],
},
],
}
print(json.dumps(example, indent=2))
def main():
parser = argparse.ArgumentParser(description="Score meeting effectiveness.")
parser.add_argument("--meetings", type=str, help="Path to meetings JSON file")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--example", action="store_true", help="Print example and exit")
args = parser.parse_args()
if args.example:
print_example()
return
if not args.meetings:
parser.error("--meetings is required")
data = load_data(args.meetings)
result = analyze_meetings(data)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
if __name__ == "__main__":
main()
Related skills
FAQ
What must every action item include?
A single owner, a concrete verifiable deliverable, and a specific calendar due date rather than 'soon' or 'next sprint.'
What output does it produce?
A markdown summary with metadata, key discussion points, an action-item table, numbered decisions, and open questions.