feat(python): implement python bindings for all components and solvers
This commit is contained in:
187
generate_status.py
Normal file
187
generate_status.py
Normal file
@@ -0,0 +1,187 @@
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
epics_file = '_bmad-output/planning-artifacts/epics.md'
|
||||
status_file = '_bmad-output/implementation-artifacts/sprint-status.yaml'
|
||||
story_location = '_bmad-output/implementation-artifacts'
|
||||
|
||||
def to_kebab_case(s):
|
||||
# keep alphanumeric, spaces and hyphens
|
||||
s = re.sub(r'[^a-zA-Z0-9\s-]', '', s)
|
||||
s = re.sub(r'[\s]+', '-', s.strip()).lower()
|
||||
return re.sub(r'-+', '-', s)
|
||||
|
||||
epics = []
|
||||
current_epic = None
|
||||
|
||||
with open(epics_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
epic_match = re.match(r'^## Epic (\d+): (.*)', line)
|
||||
if epic_match:
|
||||
epics.append({
|
||||
'num': epic_match.group(1),
|
||||
'title': epic_match.group(2).strip(),
|
||||
'stories': []
|
||||
})
|
||||
current_epic = epics[-1]
|
||||
continue
|
||||
|
||||
story_match = re.match(r'^### Story (\d+)\.(\d+):\s*(.*)', line)
|
||||
if story_match and current_epic:
|
||||
title = story_match.group(3).strip().replace('*', '')
|
||||
kebab_title = to_kebab_case(title)
|
||||
story_key = f"{story_match.group(1)}-{story_match.group(2)}-{kebab_title}"
|
||||
current_epic['stories'].append(story_key)
|
||||
|
||||
existing_status = {}
|
||||
if os.path.exists(status_file):
|
||||
with open(status_file, 'r', encoding='utf-8') as f:
|
||||
in_dev_status = False
|
||||
for line in f:
|
||||
line = line.strip('\n')
|
||||
if line.startswith('development_status:'):
|
||||
in_dev_status = True
|
||||
continue
|
||||
if in_dev_status and ':' in line and not line.strip().startswith('#'):
|
||||
parts = line.split(':', 1)
|
||||
existing_status[parts[0].strip()] = parts[1].strip()
|
||||
|
||||
def upgrade_status(current, new_status):
|
||||
order = ['backlog', 'ready-for-dev', 'in-progress', 'review', 'done', 'completed']
|
||||
try:
|
||||
curr_idx = order.index(current)
|
||||
except ValueError:
|
||||
curr_idx = -1
|
||||
try:
|
||||
new_idx = order.index(new_status)
|
||||
except ValueError:
|
||||
new_idx = -1
|
||||
return order[max(curr_idx, new_idx)] if max(curr_idx, new_idx) >= 0 else new_status
|
||||
|
||||
# Compute statuses
|
||||
computed_statuses = {}
|
||||
epic_count = 0
|
||||
story_count = 0
|
||||
done_count = 0
|
||||
epics_in_progress_count = 0
|
||||
|
||||
for epic in epics:
|
||||
epic_num = epic['num']
|
||||
epic_key = f"epic-{epic_num}"
|
||||
epic_count += 1
|
||||
|
||||
epic_stat = existing_status.get(epic_key, 'backlog')
|
||||
if epic_stat == 'completed': epic_stat = 'done'
|
||||
|
||||
story_stats = {}
|
||||
any_story_started = False
|
||||
all_stories_done = True
|
||||
|
||||
if len(epic['stories']) == 0:
|
||||
all_stories_done = False
|
||||
|
||||
for story_key in epic['stories']:
|
||||
story_count += 1
|
||||
stat = existing_status.get(story_key, 'backlog')
|
||||
if stat == 'completed': stat = 'done'
|
||||
|
||||
story_md_path = os.path.join(story_location, f"{story_key}.md")
|
||||
if os.path.exists(story_md_path):
|
||||
stat = upgrade_status(stat, 'ready-for-dev')
|
||||
any_story_started = True
|
||||
|
||||
if stat in ['in-progress', 'review', 'done']:
|
||||
any_story_started = True
|
||||
|
||||
if stat != 'done':
|
||||
all_stories_done = False
|
||||
else:
|
||||
done_count += 1
|
||||
|
||||
story_stats[story_key] = stat
|
||||
|
||||
if any_story_started and epic_stat == 'backlog':
|
||||
epic_stat = 'in-progress'
|
||||
|
||||
if all_stories_done and epic_stat in ['backlog', 'in-progress']:
|
||||
epic_stat = 'done'
|
||||
|
||||
if epic_stat == 'in-progress':
|
||||
epics_in_progress_count += 1
|
||||
|
||||
computed_statuses[epic_key] = epic_stat
|
||||
for k, v in story_stats.items():
|
||||
computed_statuses[k] = v
|
||||
|
||||
retro_key = f"epic-{epic_num}-retrospective"
|
||||
computed_statuses[retro_key] = existing_status.get(retro_key, 'optional')
|
||||
|
||||
lines = [
|
||||
"# Sprint Status - Entropyk",
|
||||
f"# Last Updated: {datetime.now().strftime('%Y-%m-%d')}",
|
||||
"# Project: Entropyk",
|
||||
"# Project Key: NOKEY",
|
||||
"# Tracking System: file-system",
|
||||
f"# Story Location: {story_location}",
|
||||
"",
|
||||
"# STATUS DEFINITIONS:",
|
||||
"# ==================",
|
||||
"# Epic Status:",
|
||||
"# - backlog: Epic not yet started",
|
||||
"# - in-progress: Epic actively being worked on",
|
||||
"# - done: All stories in epic completed",
|
||||
"#",
|
||||
"# Epic Status Transitions:",
|
||||
"# - backlog → in-progress: Automatically when first story is created (via create-story)",
|
||||
"# - in-progress → done: Manually when all stories reach 'done' status",
|
||||
"#",
|
||||
"# Story Status:",
|
||||
"# - backlog: Story only exists in epic file",
|
||||
"# - ready-for-dev: Story file created in stories folder",
|
||||
"# - in-progress: Developer actively working on implementation",
|
||||
"# - review: Ready for code review (via Dev's code-review workflow)",
|
||||
"# - done: Story completed",
|
||||
"#",
|
||||
"# Retrospective Status:",
|
||||
"# - optional: Can be completed but not required",
|
||||
"# - done: Retrospective has been completed",
|
||||
"#",
|
||||
"# WORKFLOW NOTES:",
|
||||
"# ===============",
|
||||
"# - Epic transitions to 'in-progress' automatically when first story is created",
|
||||
"# - Stories can be worked in parallel if team capacity allows",
|
||||
"# - SM typically creates next story after previous one is 'done' to incorporate learnings",
|
||||
"# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)",
|
||||
"",
|
||||
f"generated: {datetime.now().strftime('%Y-%m-%d')}",
|
||||
"project: Entropyk",
|
||||
"project_key: NOKEY",
|
||||
"tracking_system: file-system",
|
||||
f"story_location: {story_location}",
|
||||
"",
|
||||
"development_status:"
|
||||
]
|
||||
|
||||
for epic in epics:
|
||||
epic_num = epic['num']
|
||||
epic_key = f"epic-{epic_num}"
|
||||
lines.append(f" # Epic {epic_num}: {epic['title']}")
|
||||
lines.append(f" {epic_key}: {computed_statuses[epic_key]}")
|
||||
for story_key in epic['stories']:
|
||||
lines.append(f" {story_key}: {computed_statuses[story_key]}")
|
||||
retro_key = f"epic-{epic_num}-retrospective"
|
||||
lines.append(f" {retro_key}: {computed_statuses[retro_key]}")
|
||||
lines.append("")
|
||||
|
||||
with open(status_file, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
|
||||
print(json.dumps({
|
||||
"file": status_file,
|
||||
"epic_count": epic_count,
|
||||
"story_count": story_count,
|
||||
"epics_in_progress": epics_in_progress_count,
|
||||
"done_count": done_count
|
||||
}, indent=2))
|
||||
Reference in New Issue
Block a user