infra/scripts/parse-postmortem-todos.sh

89 lines
3 KiB
Bash
Executable file

#!/bin/sh
# parse-postmortem-todos.sh — Extract auto-implementable TODOs from a post-mortem markdown file
# Usage: bash scripts/parse-postmortem-todos.sh docs/post-mortems/2026-04-14-foo.md
# Output: JSON with file path and list of TODOs
#
# Supports two table formats:
# New: | Priority | Action | Type | Details | Status |
# Old: | Action | Status | Details | (infers type from action text)
set -eu
PM_FILE="${1:?Usage: $0 <post-mortem.md>}"
if [ ! -f "$PM_FILE" ]; then
echo '{"file": "", "todos": [], "error": "File not found"}' >&2
exit 1
fi
python3 -c "
import re, json, sys
pm_file = sys.argv[1]
with open(pm_file) as f:
content = f.read()
safe_types = {'Alert', 'Config', 'Monitor'}
todos = []
# Format 1 (new template): | Priority | Action | Type | Details | Status |
pattern_new = r'\|\s*(P[0-3])\s*\|\s*(.+?)\s*\|\s*(\w+)\s*\|\s*(.+?)\s*\|\s*TODO\s*\|'
for priority, action, todo_type, details in re.findall(pattern_new, content):
todos.append({
'priority': priority.strip(),
'action': action.strip(),
'type': todo_type.strip(),
'details': details.strip(),
'safe': todo_type.strip() in safe_types
})
# Format 2 (old): | Action | TODO/Done | Details | or | Action | Owner | Status |
# Look for rows with TODO in any column
if not todos:
pattern_old = r'\|\s*(.+?)\s*\|\s*TODO\s*\|\s*(.+?)\s*\|'
for action, details in re.findall(pattern_old, content):
action = action.strip()
details = details.strip()
# Skip header rows and clean up leading pipes
if action.startswith('--') or action.lower() == 'action':
continue
action = action.lstrip('| ').strip()
# Infer type from action text
action_lower = action.lower()
if any(kw in action_lower for kw in ['prometheusrule', 'alert', 'alerting']):
todo_type = 'Alert'
elif any(kw in action_lower for kw in ['uptime kuma', 'monitor', 'ping', 'tcp check']):
todo_type = 'Monitor'
elif any(kw in action_lower for kw in ['config', 'manage', 'add.*option', 'document', 'nfs.conf']):
todo_type = 'Config'
elif any(kw in action_lower for kw in ['migrate', 'move']):
todo_type = 'Migration'
elif any(kw in action_lower for kw in ['review', 'investigate', 'verify']):
todo_type = 'Investigation'
else:
todo_type = 'Config' # default to Config for ambiguous items
# Infer priority from section header context
priority = 'P2' # default
todos.append({
'priority': priority,
'action': action,
'type': todo_type,
'details': details,
'safe': todo_type in safe_types
})
safe_todos = [t for t in todos if t['safe']]
unsafe_todos = [t for t in todos if not t['safe']]
result = {
'file': pm_file,
'todos': safe_todos,
'skipped': unsafe_todos,
'total_todos_in_doc': len(todos),
'safe_todos': len(safe_todos),
'skipped_todos': len(unsafe_todos)
}
print(json.dumps(result, indent=2))
" "$PM_FILE"