
Decision Logger
Parse board-meeting decision logs and surface overdue owners, conflicts, and due-within reports for a solo founder running lightweight governance.
Overview
decision-logger is an agent skill most often used in Operate (also Grow support, Build pm) that parses board-meeting decisions.md and reports overdue, conflict, and owner-filtered action items.
Install
npx skills add https://github.com/alirezarezvani/claude-skills --skill decision-loggerWhat is this skill?
- Python decision_tracker.py CLI with stdlib only—no extra dependencies
- CLI modes: --summary, --overdue, --conflicts, --owner, --search, --due-within, --demo
- Parses memory/board-meetings/decisions.md into owners, dues, reviews, and completion state
- Overdue and due-within filters for operational standups
- Conflict detection across decision records for advisor-style reviews
- 7 CLI report modes: summary, overdue, conflicts, owner, search, due-within, demo
- Stdlib-only Python implementation with no third-party dependencies
Adoption & trust: 533 installs on skills.sh; 17.5k GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your advisor or board notes live in markdown but nobody can see what is overdue, who owns it, or which decisions contradict each other.
Who is it for?
Technical founders who already log decisions in repo markdown and want a zero-dependency Python tracker.
Skip if: Teams needing full GRC tooling, real-time multi-user PM suites, or decision logs outside the expected memory/board-meetings path without adaptation.
When should I use this skill?
When maintaining or reporting on memory/board-meetings/decisions.md after board or advisor meetings.
What do I get? / Deliverables
You get CLI reports—summary, overdue, conflicts, owner and search filters—so the next standup or board prep starts from an accurate action backlog.
- Terminal summary of open and completed action items
- Overdue and due-within reports
- Conflict listing across logged decisions
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Operate-iterate is where leadership decisions become tracked commitments that need follow-through between builds. Decision tracking supports iteration cadence—closing the loop on what the board or advisors decided last cycle.
Where it fits
After a pricing decision meeting, log resolutions and run --search pricing before changing checkout.
Filter --owner CMO to see open marketing commitments blocking a release checklist.
Weekly --overdue and --due-within 7 before leadership sync.
Use --conflicts when customer promises and board decisions on refunds or SLAs may disagree.
How it compares
Use as a repo-native decision ledger parser instead of bolting another SaaS PM tool for three advisor action items.
Common Questions / FAQ
Who is decision-logger for?
Solo builders and lean startups that run advisor or board rituals and store outcomes in markdown under version control.
When should I use decision-logger?
After operate iterate planning when reviewing decision hygiene, before grow support escalations, or during build pm when pricing or scope decisions need owner accountability.
Is decision-logger safe to install?
It runs local Python on your decisions file; review the Security Audits panel on this Prism page and inspect the script before running on sensitive board minutes.
SKILL.md
READMESKILL.md - Decision Logger
#!/usr/bin/env python3 """ decision_tracker.py — Board Meeting Decision Parser & Reporter Part of the C-Level Advisor / Decision Logger skill. Parses memory/board-meetings/decisions.md and produces actionable reports. Stdlib only. No dependencies. Usage: python decision_tracker.py --summary python decision_tracker.py --overdue python decision_tracker.py --conflicts python decision_tracker.py --owner "CMO" python decision_tracker.py --search "pricing" python decision_tracker.py --due-within 7 python decision_tracker.py --demo # Run with sample data """ import argparse import os import re import sys from datetime import date, datetime, timedelta from pathlib import Path from typing import Optional # ───────────────────────────────────────────── # Data structures # ───────────────────────────────────────────── class ActionItem: def __init__(self, text: str, owner: str, due: Optional[date], review: Optional[date], completed: bool, completed_date: Optional[date], result: str): self.text = text self.owner = owner self.due = due self.review = review self.completed = completed self.completed_date = completed_date self.result = result def is_overdue(self) -> bool: if self.completed: return False if self.due and self.due < date.today(): return True return False def is_due_within(self, days: int) -> bool: if self.completed: return False if self.due: return date.today() <= self.due <= date.today() + timedelta(days=days) return False class Decision: def __init__(self): self.date: Optional[date] = None self.title: str = "" self.decision: str = "" self.owner: str = "" self.deadline: Optional[date] = None self.review: Optional[date] = None self.rationale: str = "" self.user_override: str = "" self.rejected: list[str] = [] self.action_items: list[ActionItem] = [] self.supersedes: str = "" self.superseded_by: str = "" self.raw_transcript: str = "" def is_active(self) -> bool: return not bool(self.superseded_by.strip()) def has_override(self) -> bool: return bool(self.user_override.strip()) # ───────────────────────────────────────────── # Parser # ───────────────────────────────────────────── def parse_date(s: str) -> Optional[date]: """Parse YYYY-MM-DD or return None.""" if not s: return None s = s.strip() for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%d.%m.%Y"): try: return datetime.strptime(s, fmt).date() except ValueError: continue return None def parse_action_item(line: str) -> Optional[ActionItem]: """ Parse a line like: - [ ] Action text — Owner: CMO — Due: 2026-03-15 — Review: 2026-03-29 - [x] Action text — Owner: CEO — Completed: 2026-03-10 — Result: Done """ line = line.strip() if not line.startswith("- ["): return None completed = line.startswith("- [x]") or line.startswith("- [X]") text_start = line.find("]") + 1 raw = line[text_start:].strip() # Split on " — " (em dash with spaces) or " - " fallback parts_raw = re.split(r"\s+[—\-]{1,2}\s+", raw) text = parts_raw[0].strip() if parts_raw else raw def extract(label: str, parts: list[str]) -> str: for p in parts: if p.lower().startswith(label.lower() + ":"): return p[len(label) + 1:].strip() return "" owner = extract("Owner", parts_raw[1:]) due_str = extract("Due", parts_raw[1:]) review_str = extract("Review", parts_raw[1:]) completed_str = extract("Completed", parts_raw[1:]) result = extract("Result", parts_raw[1:]) return ActionItem( text=text, owner=owner, due=parse_date(due_str),