220 lines
6.7 KiB
Python
220 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate task files from plan.md
|
|
This script parses the plan.md file and creates detailed task files for each task.
|
|
"""
|
|
|
|
import re
|
|
import os
|
|
from pathlib import Path
|
|
|
|
def parse_plan(plan_path):
|
|
"""Parse plan.md and extract tasks"""
|
|
with open(plan_path, 'r') as f:
|
|
content = f.read()
|
|
|
|
tasks = []
|
|
current_phase = None
|
|
current_section = None
|
|
subtask_num = 1
|
|
|
|
lines = content.split('\n')
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i].rstrip()
|
|
|
|
# Phase header
|
|
phase_match = re.match(r'^## Phase (\d+):', line)
|
|
if phase_match:
|
|
current_phase = int(phase_match.group(1))
|
|
subtask_num = 1 # Reset subtask counter for new phase
|
|
i += 1
|
|
continue
|
|
|
|
# Section header (e.g., "#### 0.1 Repository Bootstrap")
|
|
section_match = re.match(r'^#### (\d+\.\d+)', line)
|
|
if section_match:
|
|
current_section = section_match.group(1)
|
|
subtask_num = 1 # Reset subtask counter for new section
|
|
i += 1
|
|
continue
|
|
|
|
# Task item (checkbox) - must match exactly
|
|
task_match = re.match(r'^- \[ \] (.+)', line)
|
|
if task_match and current_phase is not None and current_section is not None:
|
|
task_desc = task_match.group(1).strip()
|
|
|
|
# Handle tasks that end with colon (might have code block or list following)
|
|
code_block = ""
|
|
# Skip empty lines and code blocks
|
|
if i + 1 < len(lines):
|
|
next_line = lines[i + 1].strip()
|
|
if next_line.startswith('```'):
|
|
# Extract code block
|
|
j = i + 2
|
|
while j < len(lines) and not lines[j].strip().startswith('```'):
|
|
code_block += lines[j] + '\n'
|
|
j += 1
|
|
i = j + 1
|
|
elif next_line.startswith('- [ ]') or next_line.startswith('```'):
|
|
# Next task or code block, don't skip
|
|
i += 1
|
|
else:
|
|
i += 1
|
|
else:
|
|
i += 1
|
|
|
|
# Only add if we have valid phase and section
|
|
if current_phase is not None and current_section is not None:
|
|
tasks.append({
|
|
'phase': current_phase,
|
|
'section': current_section,
|
|
'subtask': subtask_num,
|
|
'description': task_desc,
|
|
'code': code_block.strip()
|
|
})
|
|
subtask_num += 1
|
|
continue
|
|
|
|
i += 1
|
|
|
|
return tasks
|
|
|
|
def create_task_file(task, output_dir):
|
|
"""Create a task markdown file"""
|
|
phase_dir = output_dir / f"phase{task['phase']}"
|
|
phase_dir.mkdir(exist_ok=True)
|
|
|
|
task_id = f"{task['section']}.{task['subtask']}"
|
|
# Create safe filename
|
|
safe_desc = re.sub(r'[^\w\s-]', '', task['description'])[:50].strip().replace(' ', '-').lower()
|
|
filename = f"{task_id}-{safe_desc}.md"
|
|
filepath = phase_dir / filename
|
|
|
|
# Generate content
|
|
content = f"""# Task {task_id}: {task['description']}
|
|
|
|
## Metadata
|
|
- **Task ID**: {task_id}
|
|
- **Title**: {task['description']}
|
|
- **Phase**: {task['phase']} - {get_phase_name(task['phase'])}
|
|
- **Section**: {task['section']}
|
|
- **Status**: Pending
|
|
- **Priority**: High
|
|
- **Estimated Time**: TBD
|
|
- **Dependencies**: TBD
|
|
|
|
## Description
|
|
{task['description']}
|
|
|
|
## Requirements
|
|
- {task['description']}
|
|
|
|
## Implementation Steps
|
|
1. TODO: Add implementation steps
|
|
2. TODO: Add implementation steps
|
|
3. TODO: Add implementation steps
|
|
|
|
## Acceptance Criteria
|
|
- [ ] Task {task_id} is completed
|
|
- [ ] All requirements are met
|
|
- [ ] Code compiles and tests pass
|
|
|
|
## Related ADRs
|
|
- See relevant ADRs in `docs/adr/`
|
|
|
|
## Implementation Notes
|
|
- TODO: Add implementation notes
|
|
|
|
## Testing
|
|
```bash
|
|
# TODO: Add test commands
|
|
go test ./...
|
|
```
|
|
|
|
"""
|
|
|
|
if task['code']:
|
|
content += f"\n## Code Reference\n\n```go\n{task['code']}\n```\n"
|
|
|
|
with open(filepath, 'w') as f:
|
|
f.write(content)
|
|
|
|
return filepath
|
|
|
|
def get_phase_name(phase_num):
|
|
"""Get phase name from number"""
|
|
phases = {
|
|
0: "Project Setup & Foundation",
|
|
1: "Core Kernel & Infrastructure",
|
|
2: "Authentication & Authorization",
|
|
3: "Module Framework",
|
|
4: "Sample Feature Module (Blog)",
|
|
5: "Infrastructure Adapters",
|
|
6: "Observability & Production Readiness",
|
|
7: "Testing, Documentation & CI/CD",
|
|
8: "Advanced Features & Polish"
|
|
}
|
|
return phases.get(phase_num, "Unknown")
|
|
|
|
def main():
|
|
script_dir = Path(__file__).parent
|
|
plan_path = script_dir.parent / "plan.md"
|
|
output_dir = script_dir
|
|
|
|
if not plan_path.exists():
|
|
print(f"Error: {plan_path} not found")
|
|
return
|
|
|
|
print(f"Parsing {plan_path}...")
|
|
try:
|
|
tasks = parse_plan(plan_path)
|
|
print(f"Found {len(tasks)} tasks")
|
|
|
|
if len(tasks) == 0:
|
|
print("Warning: No tasks found. Check the plan.md format.")
|
|
return
|
|
|
|
created = 0
|
|
skipped = 0
|
|
for task in tasks:
|
|
try:
|
|
task_id = f"{task['section']}.{task['subtask']}"
|
|
|
|
# Determine filepath before creating
|
|
phase_dir = output_dir / f"phase{task['phase']}"
|
|
phase_dir.mkdir(exist_ok=True)
|
|
|
|
# Create safe filename
|
|
safe_desc = re.sub(r'[^\w\s-]', '', task['description'])[:50].strip().replace(' ', '-').lower()
|
|
filename = f"{task_id}-{safe_desc}.md"
|
|
filepath = phase_dir / filename
|
|
|
|
# Check if file already exists (skip if so)
|
|
if filepath.exists() and filepath.stat().st_size > 100:
|
|
skipped += 1
|
|
continue
|
|
|
|
# Create the file
|
|
create_task_file(task, output_dir)
|
|
created += 1
|
|
if created % 10 == 0:
|
|
print(f"Created {created} task files...")
|
|
except Exception as e:
|
|
print(f"Error creating task {task.get('section', '?')}.{task.get('subtask', '?')}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
print(f"\nCreated {created} new task files")
|
|
if skipped > 0:
|
|
print(f"Skipped {skipped} existing task files")
|
|
print(f"Total tasks processed: {len(tasks)}")
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|