113 lines
3.4 KiB
Python
Executable File
113 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Add Status field to story files that are missing it.
|
||
Uses sprint-status.yaml as source of truth.
|
||
"""
|
||
|
||
import re
|
||
from pathlib import Path
|
||
from typing import Dict
|
||
|
||
def load_sprint_status(path: str = "docs/sprint-artifacts/sprint-status.yaml") -> Dict[str, str]:
|
||
"""Load story statuses from sprint-status.yaml"""
|
||
with open(path) as f:
|
||
lines = f.readlines()
|
||
|
||
statuses = {}
|
||
in_dev_status = False
|
||
|
||
for line in lines:
|
||
if 'development_status:' in line:
|
||
in_dev_status = True
|
||
continue
|
||
|
||
if in_dev_status:
|
||
# Check if we've left development_status section
|
||
if line.strip() and not line.startswith(' ') and not line.startswith('#'):
|
||
break
|
||
|
||
# Parse story line: " story-id: status # comment"
|
||
match = re.match(r' ([a-z0-9-]+):\s*(\S+)', line)
|
||
if match:
|
||
story_id, status = match.groups()
|
||
statuses[story_id] = status
|
||
|
||
return statuses
|
||
|
||
def add_status_to_story(story_file: Path, status: str) -> bool:
|
||
"""Add Status field to story file if missing"""
|
||
content = story_file.read_text()
|
||
|
||
# Check if Status field already exists (handles both "Status:" and "**Status:**")
|
||
if re.search(r'^\*?\*?Status:', content, re.MULTILINE | re.IGNORECASE):
|
||
return False # Already has Status field
|
||
|
||
# Find the first section after the title (usually ## Story or ## Description)
|
||
# Insert Status field before that
|
||
lines = content.split('\n')
|
||
|
||
# Find insertion point (after title, before first ## section)
|
||
insert_idx = None
|
||
for idx, line in enumerate(lines):
|
||
if line.startswith('# ') and idx == 0:
|
||
# Title line - keep looking
|
||
continue
|
||
if line.startswith('##'):
|
||
# Found first section - insert before it
|
||
insert_idx = idx
|
||
break
|
||
|
||
if insert_idx is None:
|
||
# No ## sections found, insert after title
|
||
insert_idx = 1
|
||
|
||
# Insert blank line, Status field, blank line
|
||
lines.insert(insert_idx, '')
|
||
lines.insert(insert_idx + 1, f'**Status:** {status}')
|
||
lines.insert(insert_idx + 2, '')
|
||
|
||
# Write back
|
||
story_file.write_text('\n'.join(lines))
|
||
return True
|
||
|
||
def main():
|
||
story_dir = Path("docs/sprint-artifacts")
|
||
statuses = load_sprint_status()
|
||
|
||
added = 0
|
||
skipped = 0
|
||
missing = 0
|
||
|
||
for story_file in sorted(story_dir.glob("*.md")):
|
||
story_id = story_file.stem
|
||
|
||
# Skip special files
|
||
if (story_id.startswith('.') or
|
||
story_id.startswith('EPIC-') or
|
||
'COMPLETION' in story_id.upper() or
|
||
'SUMMARY' in story_id.upper() or
|
||
'REPORT' in story_id.upper() or
|
||
'README' in story_id.upper()):
|
||
continue
|
||
|
||
if story_id not in statuses:
|
||
print(f"⚠️ {story_id}: Not in sprint-status.yaml")
|
||
missing += 1
|
||
continue
|
||
|
||
status = statuses[story_id]
|
||
|
||
if add_status_to_story(story_file, status):
|
||
print(f"✓ {story_id}: Added Status: {status}")
|
||
added += 1
|
||
else:
|
||
skipped += 1
|
||
|
||
print()
|
||
print(f"✅ Added Status field to {added} stories")
|
||
print(f"ℹ️ Skipped {skipped} stories (already have Status)")
|
||
print(f"⚠️ {missing} stories not in sprint-status.yaml")
|
||
|
||
if __name__ == '__main__':
|
||
main()
|