feat: add granular phase-level cards to Nextcloud Deck sync
- Updated parser to extract phases from detailed roadmap files - Cleaner card titles: 'Phase X: Description' instead of file paths - Improved status detection: CURRENT/DEPLOYED → In Progress, NEXT → Planning - Code block removal to prevent API 400/500 errors - Shorter descriptions (400 chars max) for better readability - All 21 cards created: 3 initiatives + 18 phases Card distribution: - Backlog: 6 cards (future work) - Planning: 1 card (next phase) - In Progress: 10 cards (active work) - Complete: 4 cards (done) Changes: - scripts/sync-roadmap-to-deck.py: Complete parser rewrite for phase-level granularity - Handles both ## Phase and ### Phase patterns - Removes markdown/emojis from titles for clean display
This commit is contained in:
@@ -131,9 +131,11 @@ class RoadmapParser:
|
||||
return []
|
||||
|
||||
content = filepath.read_text()
|
||||
filename = filepath.name
|
||||
tasks = []
|
||||
|
||||
# Parse initiatives (main sections) - handle different emoji prefixes
|
||||
# Master roadmap: Parse initiatives (high-level overview only)
|
||||
if filename == 'OPTIMIZATION_MASTER_ROADMAP.md':
|
||||
initiative_pattern = r'## (?:🎯|📐|📊) Initiative (\d+): (.+?)(?=\n##|\Z)'
|
||||
initiatives = re.finditer(initiative_pattern, content, re.DOTALL)
|
||||
|
||||
@@ -154,57 +156,92 @@ class RoadmapParser:
|
||||
status_match = re.search(r'### Current Status\n(.+?)(?=\n###|\Z)', initiative_content, re.DOTALL)
|
||||
status = status_match.group(1).strip() if status_match else ''
|
||||
|
||||
# Extract timeline
|
||||
timeline_match = re.search(r'### Timeline\n(.+?)(?=\n###|\Z)', initiative_content, re.DOTALL)
|
||||
timeline = timeline_match.group(1).strip() if timeline_match else ''
|
||||
|
||||
# Extract success metrics
|
||||
metrics_match = re.search(r'### Success Metrics\n(.+?)(?=\n###|\Z)', initiative_content, re.DOTALL)
|
||||
metrics = metrics_match.group(1).strip() if metrics_match else ''
|
||||
|
||||
# Determine phase status
|
||||
phase_status = 'FUTURE'
|
||||
if '✅ COMPLETE' in status or '✅ **COMPLETE' in status:
|
||||
if '✅ COMPLETE' in status or 'COMPLETE' in status:
|
||||
phase_status = 'COMPLETE'
|
||||
elif '🔄 IN PROGRESS' in status or '🔄 **IN PROGRESS' in status:
|
||||
elif '🔄 IN PROGRESS' in status or 'IN PROGRESS' in status:
|
||||
phase_status = 'IN PROGRESS'
|
||||
elif '🔜 NEXT' in status or 'Phase 2' in status:
|
||||
elif '🔜 NEXT' in status:
|
||||
phase_status = 'PENDING'
|
||||
|
||||
# Extract progress
|
||||
progress_match = re.search(r'Progress:.*?(\d+)/(\d+)', status)
|
||||
progress = f"{progress_match.group(1)}/{progress_match.group(2)}" if progress_match else ''
|
||||
|
||||
# Calculate due date from timeline
|
||||
due_date = None
|
||||
if 'weeks' in timeline.lower():
|
||||
weeks = re.search(r'(\d+)-?(\d+)?\s*weeks', timeline.lower())
|
||||
if weeks:
|
||||
max_weeks = int(weeks.group(2) if weeks.group(2) else weeks.group(1))
|
||||
due_date = (datetime.now() + timedelta(weeks=max_weeks)).strftime('%Y-%m-%d')
|
||||
|
||||
task = {
|
||||
'title': f"Initiative {initiative_num}: {initiative_title}",
|
||||
'description': f"""**Initiative {initiative_num}: {initiative_title}**
|
||||
'description': f"""**File:** [{file_ref}](./{file_ref})
|
||||
|
||||
{purpose}
|
||||
|
||||
**Current Status:**
|
||||
{status}
|
||||
|
||||
**Timeline:**
|
||||
{timeline}
|
||||
|
||||
**Success Metrics:**
|
||||
{metrics}
|
||||
|
||||
**File:** {file_ref}
|
||||
**Status:** {status}
|
||||
""",
|
||||
'status': phase_status,
|
||||
'progress': '',
|
||||
'due_date': None,
|
||||
'labels': [f'initiative-{initiative_num}'],
|
||||
}
|
||||
|
||||
tasks.append(task)
|
||||
|
||||
# Detailed roadmaps: Parse phases (use ## or ### pattern depending on file)
|
||||
else:
|
||||
# Try ## Phase pattern first (SIGNAL_QUALITY, POSITION_SCALING)
|
||||
phase_pattern = r'## Phase (\d+): (.+?)(?=\n(?:##|\Z))'
|
||||
phases = list(re.finditer(phase_pattern, content, re.DOTALL))
|
||||
|
||||
# If no ## Phase found, try ### Phase pattern (ATR_BASED_TP)
|
||||
if not phases:
|
||||
phase_pattern = r'### Phase (\d+): (.+?)(?=\n(?:###|##|\Z))'
|
||||
phases = list(re.finditer(phase_pattern, content, re.DOTALL))
|
||||
|
||||
for phase in phases:
|
||||
phase_num = phase.group(1)
|
||||
phase_title_raw = phase.group(2).strip()
|
||||
|
||||
# Clean up phase title (remove status emojis and extra text in parens)
|
||||
phase_title = re.sub(r'\s*\(.*?\)\s*', ' ', phase_title_raw)
|
||||
phase_title = re.sub(r'[🔄✅🔜🎯⏳🤖📏📊🔮]+', '', phase_title).strip()
|
||||
|
||||
# Extract full phase content for description
|
||||
phase_start = phase.end()
|
||||
next_phase_match = re.search(r'\n(?:##|###) Phase \d+:', content[phase_start:])
|
||||
phase_end = phase_start + next_phase_match.start() if next_phase_match else len(content)
|
||||
phase_content = content[phase_start:phase_end].strip()
|
||||
|
||||
# Remove code blocks FIRST (they cause 400/500 errors)
|
||||
phase_content_clean = re.sub(r'```[\s\S]*?```', '[Code block omitted - see roadmap file]', phase_content)
|
||||
|
||||
# Then limit description to first 400 chars
|
||||
description_preview = phase_content_clean[:400]
|
||||
if len(phase_content_clean) > 400:
|
||||
description_preview += '...'
|
||||
|
||||
# Determine status from title
|
||||
phase_status = 'FUTURE'
|
||||
title_upper = phase_title_raw.upper()
|
||||
|
||||
# Check for status indicators in the raw title
|
||||
if ('CURRENT' in title_upper or '✅' in phase_title_raw or 'DEPLOYED' in title_upper) and 'FUTURE' not in title_upper:
|
||||
phase_status = 'IN PROGRESS'
|
||||
elif '🔜' in phase_title_raw or ('NEXT' in title_upper and 'FUTURE' not in title_upper):
|
||||
phase_status = 'PENDING'
|
||||
elif 'COMPLETE' in title_upper:
|
||||
phase_status = 'COMPLETE'
|
||||
# Otherwise stays FUTURE
|
||||
|
||||
# Extract progress if present
|
||||
progress_match = re.search(r'(\d+)/(\d+)', phase_content[:200])
|
||||
progress = f"{progress_match.group(1)}/{progress_match.group(2)}" if progress_match else ''
|
||||
|
||||
task = {
|
||||
'title': f"Phase {phase_num}: {phase_title}",
|
||||
'description': f"""**Initiative:** {filepath.stem.replace('_', ' ').title().replace('Roadmap', '')}
|
||||
**File:** [{filename}](./{filename})
|
||||
|
||||
{description_preview}
|
||||
""",
|
||||
'status': phase_status,
|
||||
'initiative_num': initiative_num,
|
||||
'progress': progress,
|
||||
'due_date': due_date,
|
||||
'labels': [f'initiative-{initiative_num}', 'optimization'],
|
||||
'due_date': None,
|
||||
'labels': [filename.replace('.md', '').lower(), f'phase-{phase_num}'],
|
||||
}
|
||||
|
||||
tasks.append(task)
|
||||
|
||||
Reference in New Issue
Block a user