chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: task-execution-engine
|
||||
description: Execute implementation tasks from design documents using markdown checkboxes. Use when (1) implementing features from feature-design-assistant output, (2) resuming interrupted work, (3) batch executing tasks. Triggers on 'start implementation', 'run tasks', 'resume'.
|
||||
---
|
||||
|
||||
# Feature Pipeline
|
||||
|
||||
Execute implementation tasks directly from design documents. Tasks are managed as markdown checkboxes - no separate session files needed.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Get next task
|
||||
python3 scripts/task_manager.py next --file <design.md>
|
||||
|
||||
# Mark task completed
|
||||
python3 scripts/task_manager.py done --file <design.md> --task "Task Title"
|
||||
|
||||
# Mark task failed
|
||||
python3 scripts/task_manager.py fail --file <design.md> --task "Task Title" --reason "..."
|
||||
|
||||
# Show status
|
||||
python3 scripts/task_manager.py status --file <design.md>
|
||||
```
|
||||
|
||||
## Task Format
|
||||
|
||||
Tasks are written as markdown checkboxes in the design document:
|
||||
|
||||
```markdown
|
||||
## Implementation Tasks
|
||||
|
||||
- [ ] **Create User model** `priority:1` `phase:model`
|
||||
- files: src/models/user.py, tests/models/test_user.py
|
||||
- [ ] User model has email and password_hash fields
|
||||
- [ ] Email validation implemented
|
||||
- [ ] Password hashing uses bcrypt
|
||||
|
||||
- [ ] **Implement JWT utils** `priority:2` `phase:model`
|
||||
- files: src/utils/jwt.py
|
||||
- [ ] generate_token() creates valid JWT
|
||||
- [ ] verify_token() validates JWT
|
||||
|
||||
- [ ] **Create auth API** `priority:3` `phase:api` `deps:Create User model,Implement JWT utils`
|
||||
- files: src/api/auth.py
|
||||
- [ ] POST /register endpoint
|
||||
- [ ] POST /login endpoint
|
||||
```
|
||||
|
||||
See [references/task-format.md](references/task-format.md) for full format specification.
|
||||
|
||||
## Execution Loop
|
||||
|
||||
```
|
||||
LOOP until no tasks remain:
|
||||
1. GET next task (task_manager.py next)
|
||||
2. READ task details (files, criteria)
|
||||
3. IMPLEMENT the task
|
||||
4. VERIFY acceptance criteria
|
||||
5. UPDATE status (task_manager.py done/fail)
|
||||
6. CONTINUE
|
||||
```
|
||||
|
||||
### Unattended Mode Rules
|
||||
|
||||
- **NO stopping** for questions
|
||||
- **NO asking** for clarification
|
||||
- Make autonomous decisions based on codebase patterns
|
||||
- If blocked, mark as failed and continue
|
||||
|
||||
## Status Updates
|
||||
|
||||
Completed task:
|
||||
```markdown
|
||||
- [x] **Create User model** `priority:1` `phase:model` ✅
|
||||
- files: src/models/user.py
|
||||
- [x] User model has email field
|
||||
- [x] Password hashing implemented
|
||||
```
|
||||
|
||||
Failed task:
|
||||
```markdown
|
||||
- [x] **Create User model** `priority:1` `phase:model` ❌
|
||||
- files: src/models/user.py
|
||||
- [ ] User model has email field
|
||||
- reason: Missing database configuration
|
||||
```
|
||||
|
||||
## Resume / Recovery
|
||||
|
||||
To resume interrupted work, simply run again with the same design file:
|
||||
|
||||
```
|
||||
/feature-pipeline docs/designs/xxx.md
|
||||
```
|
||||
|
||||
The task manager will find the first uncompleted task and continue from there.
|
||||
|
||||
## Integration
|
||||
|
||||
This skill is typically triggered after `/feature-analyzer` completes:
|
||||
|
||||
```
|
||||
User: /feature-analyzer implement user auth
|
||||
|
||||
Claude: [designs feature, generates task list]
|
||||
Design saved to docs/designs/2026-01-02-user-auth.md
|
||||
Ready to start implementation?
|
||||
|
||||
User: Yes / 开始实现
|
||||
|
||||
Claude: [executes tasks via task-execution-engine]
|
||||
```
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Task Format Specification
|
||||
|
||||
## Basic Structure
|
||||
|
||||
```markdown
|
||||
## Implementation Tasks
|
||||
|
||||
- [ ] **Task Title** `priority:N` `phase:PHASE` `deps:Dep1,Dep2`
|
||||
- files: file1.py, file2.py
|
||||
- [ ] Acceptance criterion 1
|
||||
- [ ] Acceptance criterion 2
|
||||
```
|
||||
|
||||
## Task Line
|
||||
|
||||
```
|
||||
- [ ] **Task Title** `priority:1` `phase:model` `deps:Other Task`
|
||||
```
|
||||
|
||||
| Component | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `- [ ]` | Yes | Checkbox (unchecked) |
|
||||
| `**Title**` | Yes | Task title in bold |
|
||||
| `priority:N` | No | Priority 1-10 (default: 5, lower = higher) |
|
||||
| `phase:X` | No | Phase: model, api, ui, test, docs |
|
||||
| `deps:A,B` | No | Comma-separated dependency task titles |
|
||||
|
||||
## Task Details (Indented)
|
||||
|
||||
### Files Line
|
||||
|
||||
```markdown
|
||||
- files: src/models/user.py, tests/test_user.py
|
||||
```
|
||||
|
||||
Comma-separated list of files to create/modify.
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
```markdown
|
||||
- [ ] User model has email field
|
||||
- [ ] Password hashing uses bcrypt
|
||||
```
|
||||
|
||||
Checkboxes for each acceptance criterion. All must be checked for task to be complete.
|
||||
|
||||
### Failure Reason (Auto-added)
|
||||
|
||||
```markdown
|
||||
- reason: Database connection failed
|
||||
```
|
||||
|
||||
Added automatically when task is marked as failed.
|
||||
|
||||
## Status Markers
|
||||
|
||||
| Status | Checkbox | Marker |
|
||||
|--------|----------|--------|
|
||||
| Pending | `- [ ]` | (none) |
|
||||
| Completed | `- [x]` | ✅ |
|
||||
| Failed | `- [x]` | ❌ |
|
||||
|
||||
## Priority Order
|
||||
|
||||
1. Lower priority number = execute first
|
||||
2. Dependencies must be completed first
|
||||
3. Tasks with unsatisfied dependencies are "blocked"
|
||||
|
||||
## Examples
|
||||
|
||||
### Pending Task
|
||||
|
||||
```markdown
|
||||
- [ ] **Create User model** `priority:1` `phase:model`
|
||||
- files: src/models/user.py
|
||||
- [ ] User model has email and password_hash fields
|
||||
- [ ] Email validation implemented
|
||||
```
|
||||
|
||||
### Completed Task
|
||||
|
||||
```markdown
|
||||
- [x] **Create User model** `priority:1` `phase:model` ✅
|
||||
- files: src/models/user.py
|
||||
- [x] User model has email and password_hash fields
|
||||
- [x] Email validation implemented
|
||||
```
|
||||
|
||||
### Failed Task
|
||||
|
||||
```markdown
|
||||
- [x] **Create User model** `priority:1` `phase:model` ❌
|
||||
- files: src/models/user.py
|
||||
- [ ] User model has email and password_hash fields
|
||||
- reason: bcrypt package not installed
|
||||
```
|
||||
|
||||
### Task with Dependencies
|
||||
|
||||
```markdown
|
||||
- [ ] **Create auth API** `priority:3` `phase:api` `deps:Create User model,Implement JWT`
|
||||
- files: src/api/auth.py
|
||||
- [ ] POST /register endpoint
|
||||
- [ ] POST /login endpoint
|
||||
```
|
||||
|
||||
This task will not be selected by `next` until both "Create User model" and "Implement JWT" are completed.
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
# Workflow Guide
|
||||
|
||||
从需求分析到自动化实现的完整流程。
|
||||
|
||||
## 流程概览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ /feature-analyzer 实现用户登录功能 │
|
||||
│ → 对话设计 → 生成 docs/designs/xxx.md │
|
||||
│ → "设计已完成,要开始实现吗?" │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
↓
|
||||
用户: "开始实现" / "Yes"
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ /feature-pipeline docs/designs/xxx.md │
|
||||
│ → 读取任务列表 → 循环执行 → 更新 checkbox │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 完成!"要提交到 Git 吗?" │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 完整流程
|
||||
|
||||
```
|
||||
用户: /feature-analyzer 实现用户登录功能
|
||||
|
||||
Claude: [逐步对话理解需求...]
|
||||
[设计方案...]
|
||||
|
||||
设计已保存到 docs/designs/2026-01-02-user-auth.md
|
||||
|
||||
任务列表:
|
||||
1. Create User model (priority: 1)
|
||||
2. Implement JWT utils (priority: 2)
|
||||
3. Create auth API (priority: 3)
|
||||
|
||||
设计已完成,要开始实现吗?
|
||||
|
||||
用户: 开始实现
|
||||
|
||||
Claude: 开始执行任务...
|
||||
|
||||
---TASK RESULT---
|
||||
task: Create User model
|
||||
status: completed
|
||||
files: [src/models/user.py]
|
||||
---END TASK RESULT---
|
||||
|
||||
---TASK RESULT---
|
||||
task: Implement JWT utils
|
||||
status: completed
|
||||
files: [src/utils/jwt.py]
|
||||
---END TASK RESULT---
|
||||
|
||||
[继续执行...]
|
||||
|
||||
所有任务完成!
|
||||
✅ 3/3 tasks completed
|
||||
|
||||
要提交到 Git 吗?
|
||||
```
|
||||
|
||||
### 直接执行已有设计
|
||||
|
||||
```
|
||||
用户: /feature-pipeline docs/designs/2026-01-02-user-auth.md
|
||||
|
||||
Claude: [读取文件,开始执行任务...]
|
||||
```
|
||||
|
||||
### 恢复中断的工作
|
||||
|
||||
```
|
||||
用户: /feature-pipeline docs/designs/2026-01-02-user-auth.md
|
||||
|
||||
Claude: 正在恢复...
|
||||
已完成: 2/5 任务
|
||||
从 "Create auth API" 继续...
|
||||
|
||||
[继续执行剩余任务...]
|
||||
```
|
||||
|
||||
## 设计文档格式
|
||||
|
||||
设计文档中的任务列表使用 markdown checkbox:
|
||||
|
||||
```markdown
|
||||
# User Auth Design
|
||||
|
||||
## Overview
|
||||
...
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [ ] **Create User model** `priority:1` `phase:model`
|
||||
- files: src/models/user.py
|
||||
- [ ] User model has email field
|
||||
- [ ] Password hashing implemented
|
||||
|
||||
- [ ] **Implement JWT utils** `priority:2` `phase:model`
|
||||
- files: src/utils/jwt.py
|
||||
- [ ] generate_token() works
|
||||
- [ ] verify_token() works
|
||||
|
||||
- [ ] **Create auth API** `priority:3` `phase:api` `deps:Create User model,Implement JWT utils`
|
||||
- files: src/api/auth.py
|
||||
- [ ] POST /login endpoint
|
||||
- [ ] POST /register endpoint
|
||||
```
|
||||
|
||||
执行后:
|
||||
|
||||
```markdown
|
||||
- [x] **Create User model** `priority:1` `phase:model` ✅
|
||||
- files: src/models/user.py
|
||||
- [x] User model has email field
|
||||
- [x] Password hashing implemented
|
||||
|
||||
- [x] **Implement JWT utils** `priority:2` `phase:model` ✅
|
||||
...
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **可读性**: 设计文档直接可读
|
||||
2. **Git 友好**: markdown diff 清晰
|
||||
3. **简单**: 无需额外的 session 文件
|
||||
4. **中断恢复**: 重新读取文件即可继续
|
||||
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Markdown-based task manager for task-execution-engine.
|
||||
|
||||
Manages tasks directly in markdown files using checkbox syntax:
|
||||
- [ ] uncompleted task
|
||||
- [x] completed task
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
"""Represents a task parsed from markdown."""
|
||||
title: str
|
||||
status: str # pending, completed, failed
|
||||
priority: int = 5
|
||||
phase: str = "implementation"
|
||||
dependencies: list = field(default_factory=list)
|
||||
files: list = field(default_factory=list)
|
||||
criteria: list = field(default_factory=list)
|
||||
criteria_status: list = field(default_factory=list) # True/False for each criterion
|
||||
line_number: int = 0
|
||||
failure_reason: str = ""
|
||||
|
||||
|
||||
def parse_task_line(line: str) -> Optional[dict]:
|
||||
"""Parse a task line like: - [ ] **Task Title** `priority:1` `phase:model`"""
|
||||
|
||||
# Match checkbox task
|
||||
match = re.match(r'^- \[([ xX])\] \*\*(.+?)\*\*(.*)$', line.strip())
|
||||
if not match:
|
||||
return None
|
||||
|
||||
checkbox, title, rest = match.groups()
|
||||
status = "completed" if checkbox.lower() == 'x' else "pending"
|
||||
|
||||
# Check for failure marker
|
||||
if "❌" in rest or "FAILED" in rest:
|
||||
status = "failed"
|
||||
|
||||
# Parse inline attributes
|
||||
priority = 5
|
||||
phase = "implementation"
|
||||
dependencies = []
|
||||
|
||||
# Extract priority
|
||||
priority_match = re.search(r'`priority:(\d+)`', rest)
|
||||
if priority_match:
|
||||
priority = int(priority_match.group(1))
|
||||
|
||||
# Extract phase
|
||||
phase_match = re.search(r'`phase:(\w+)`', rest)
|
||||
if phase_match:
|
||||
phase = phase_match.group(1)
|
||||
|
||||
# Extract dependencies
|
||||
deps_match = re.search(r'`deps:([^`]+)`', rest)
|
||||
if deps_match:
|
||||
dependencies = [d.strip() for d in deps_match.group(1).split(',')]
|
||||
|
||||
return {
|
||||
"title": title.strip(),
|
||||
"status": status,
|
||||
"priority": priority,
|
||||
"phase": phase,
|
||||
"dependencies": dependencies
|
||||
}
|
||||
|
||||
|
||||
def parse_tasks_from_markdown(content: str) -> list[Task]:
|
||||
"""Parse all tasks from markdown content."""
|
||||
|
||||
lines = content.split('\n')
|
||||
tasks = []
|
||||
current_task = None
|
||||
in_task_section = False
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# Check if we're in the Implementation Tasks section
|
||||
if re.match(r'^##\s+Implementation\s+Tasks', line, re.IGNORECASE):
|
||||
in_task_section = True
|
||||
continue
|
||||
|
||||
# Exit task section on next ## header
|
||||
if in_task_section and re.match(r'^##\s+[^#]', line) and 'Implementation' not in line:
|
||||
in_task_section = False
|
||||
continue
|
||||
|
||||
if not in_task_section:
|
||||
continue
|
||||
|
||||
# Parse main task line
|
||||
task_data = parse_task_line(line)
|
||||
if task_data:
|
||||
if current_task:
|
||||
tasks.append(current_task)
|
||||
|
||||
current_task = Task(
|
||||
title=task_data["title"],
|
||||
status=task_data["status"],
|
||||
priority=task_data["priority"],
|
||||
phase=task_data["phase"],
|
||||
dependencies=task_data["dependencies"],
|
||||
line_number=i + 1
|
||||
)
|
||||
continue
|
||||
|
||||
# Parse task details (indented lines under a task)
|
||||
if current_task and line.strip().startswith('- '):
|
||||
stripped = line.strip()
|
||||
|
||||
# Files line
|
||||
if stripped.startswith('- files:'):
|
||||
files_str = stripped.replace('- files:', '').strip()
|
||||
current_task.files = [f.strip() for f in files_str.split(',') if f.strip()]
|
||||
|
||||
# Criterion line (checkbox)
|
||||
elif re.match(r'^- \[([ xX])\] ', stripped):
|
||||
checkbox_match = re.match(r'^- \[([ xX])\] (.+)$', stripped)
|
||||
if checkbox_match:
|
||||
is_done = checkbox_match.group(1).lower() == 'x'
|
||||
criterion = checkbox_match.group(2).strip()
|
||||
current_task.criteria.append(criterion)
|
||||
current_task.criteria_status.append(is_done)
|
||||
|
||||
# Failure reason
|
||||
elif stripped.startswith('- reason:') or stripped.startswith('- error:'):
|
||||
current_task.failure_reason = stripped.split(':', 1)[1].strip()
|
||||
|
||||
# Don't forget the last task
|
||||
if current_task:
|
||||
tasks.append(current_task)
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
def get_next_task(tasks: list[Task]) -> Optional[Task]:
|
||||
"""Get the next task to execute based on priority and dependencies."""
|
||||
|
||||
completed_titles = {t.title for t in tasks if t.status == "completed"}
|
||||
|
||||
# Find pending tasks with satisfied dependencies
|
||||
available = []
|
||||
for task in tasks:
|
||||
if task.status != "pending":
|
||||
continue
|
||||
|
||||
# Check dependencies
|
||||
deps_satisfied = all(dep in completed_titles for dep in task.dependencies)
|
||||
if deps_satisfied:
|
||||
available.append(task)
|
||||
|
||||
if not available:
|
||||
return None
|
||||
|
||||
# Sort by priority (lower number = higher priority)
|
||||
available.sort(key=lambda t: t.priority)
|
||||
return available[0]
|
||||
|
||||
|
||||
def update_task_status(content: str, task_title: str, new_status: str, reason: str = "") -> str:
|
||||
"""Update a task's status in the markdown content."""
|
||||
|
||||
lines = content.split('\n')
|
||||
result = []
|
||||
in_target_task = False
|
||||
task_indent = 0
|
||||
|
||||
for line in lines:
|
||||
# Check if this is the target task
|
||||
task_data = parse_task_line(line)
|
||||
if task_data and task_data["title"] == task_title:
|
||||
in_target_task = True
|
||||
task_indent = len(line) - len(line.lstrip())
|
||||
|
||||
# Update the checkbox
|
||||
if new_status == "completed":
|
||||
line = re.sub(r'^(\s*- )\[[ ]\]', r'\1[x]', line)
|
||||
# Add completion marker if not present
|
||||
if "✅" not in line:
|
||||
line = line.rstrip() + " ✅"
|
||||
elif new_status == "failed":
|
||||
line = re.sub(r'^(\s*- )\[[ ]\]', r'\1[x]', line)
|
||||
# Add failure marker
|
||||
if "❌" not in line:
|
||||
line = line.rstrip() + " ❌"
|
||||
elif new_status == "pending":
|
||||
line = re.sub(r'^(\s*- )\[[xX]\]', r'\1[ ]', line)
|
||||
# Remove markers
|
||||
line = line.replace(" ✅", "").replace(" ❌", "")
|
||||
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
# Check if we've moved to a different task
|
||||
if task_data and task_data["title"] != task_title:
|
||||
in_target_task = False
|
||||
|
||||
# Update criteria checkboxes within the task
|
||||
if in_target_task and re.match(r'^\s*- \[[ xX]\] ', line):
|
||||
current_indent = len(line) - len(line.lstrip())
|
||||
if current_indent > task_indent:
|
||||
if new_status == "completed":
|
||||
line = re.sub(r'^(\s*- )\[[ ]\]', r'\1[x]', line)
|
||||
elif new_status == "pending":
|
||||
line = re.sub(r'^(\s*- )\[[xX]\]', r'\1[ ]', line)
|
||||
|
||||
result.append(line)
|
||||
|
||||
# Add failure reason after the task line if failed
|
||||
if in_target_task and task_data and new_status == "failed" and reason:
|
||||
indent = " " * (task_indent // 2 + 1)
|
||||
# Check if next line already has a reason
|
||||
result.append(f"{indent}- reason: {reason}")
|
||||
in_target_task = False # Prevent adding reason multiple times
|
||||
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
def get_status_summary(tasks: list[Task]) -> dict:
|
||||
"""Get a summary of task statuses."""
|
||||
|
||||
summary = {
|
||||
"total": len(tasks),
|
||||
"completed": 0,
|
||||
"pending": 0,
|
||||
"failed": 0,
|
||||
"blocked": 0
|
||||
}
|
||||
|
||||
completed_titles = {t.title for t in tasks if t.status == "completed"}
|
||||
|
||||
for task in tasks:
|
||||
if task.status == "completed":
|
||||
summary["completed"] += 1
|
||||
elif task.status == "failed":
|
||||
summary["failed"] += 1
|
||||
elif task.status == "pending":
|
||||
# Check if blocked by dependencies
|
||||
deps_satisfied = all(dep in completed_titles for dep in task.dependencies)
|
||||
if deps_satisfied:
|
||||
summary["pending"] += 1
|
||||
else:
|
||||
summary["blocked"] += 1
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def cmd_next(args):
|
||||
"""Get the next task to execute."""
|
||||
content = Path(args.file).read_text()
|
||||
tasks = parse_tasks_from_markdown(content)
|
||||
|
||||
next_task = get_next_task(tasks)
|
||||
|
||||
if args.json:
|
||||
if next_task:
|
||||
print(json.dumps({
|
||||
"status": "found",
|
||||
"task": asdict(next_task)
|
||||
}, indent=2))
|
||||
else:
|
||||
summary = get_status_summary(tasks)
|
||||
print(json.dumps({
|
||||
"status": "no_tasks",
|
||||
"summary": summary,
|
||||
"message": "No pending tasks available"
|
||||
}, indent=2))
|
||||
else:
|
||||
if next_task:
|
||||
print(f"Next task: {next_task.title}")
|
||||
print(f"Priority: {next_task.priority} | Phase: {next_task.phase}")
|
||||
if next_task.files:
|
||||
print(f"Files: {', '.join(next_task.files)}")
|
||||
if next_task.criteria:
|
||||
print("Criteria:")
|
||||
for c in next_task.criteria:
|
||||
print(f" - {c}")
|
||||
else:
|
||||
print("No pending tasks available")
|
||||
|
||||
|
||||
def cmd_done(args):
|
||||
"""Mark a task as completed."""
|
||||
file_path = Path(args.file)
|
||||
content = file_path.read_text()
|
||||
|
||||
updated = update_task_status(content, args.task, "completed")
|
||||
file_path.write_text(updated)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps({"status": "success", "task": args.task, "new_status": "completed"}))
|
||||
else:
|
||||
print(f"✅ Marked '{args.task}' as completed")
|
||||
|
||||
|
||||
def cmd_fail(args):
|
||||
"""Mark a task as failed."""
|
||||
file_path = Path(args.file)
|
||||
content = file_path.read_text()
|
||||
|
||||
updated = update_task_status(content, args.task, "failed", args.reason or "")
|
||||
file_path.write_text(updated)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps({"status": "success", "task": args.task, "new_status": "failed", "reason": args.reason}))
|
||||
else:
|
||||
print(f"❌ Marked '{args.task}' as failed")
|
||||
if args.reason:
|
||||
print(f" Reason: {args.reason}")
|
||||
|
||||
|
||||
def cmd_status(args):
|
||||
"""Show status summary."""
|
||||
content = Path(args.file).read_text()
|
||||
tasks = parse_tasks_from_markdown(content)
|
||||
summary = get_status_summary(tasks)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps({
|
||||
"file": args.file,
|
||||
"summary": summary,
|
||||
"tasks": [asdict(t) for t in tasks]
|
||||
}, indent=2))
|
||||
else:
|
||||
total = summary["total"]
|
||||
completed = summary["completed"]
|
||||
pct = round(completed / total * 100, 1) if total > 0 else 0
|
||||
|
||||
print(f"File: {args.file}")
|
||||
print(f"Progress: {completed}/{total} ({pct}%)")
|
||||
print()
|
||||
print(f" Completed: {summary['completed']}")
|
||||
print(f" Pending: {summary['pending']}")
|
||||
print(f" Blocked: {summary['blocked']}")
|
||||
print(f" Failed: {summary['failed']}")
|
||||
|
||||
# Show next task
|
||||
next_task = get_next_task(tasks)
|
||||
if next_task:
|
||||
print(f"\nNext: {next_task.title}")
|
||||
|
||||
|
||||
def cmd_list(args):
|
||||
"""List all tasks."""
|
||||
content = Path(args.file).read_text()
|
||||
tasks = parse_tasks_from_markdown(content)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps([asdict(t) for t in tasks], indent=2))
|
||||
else:
|
||||
for task in tasks:
|
||||
status_icon = {"completed": "✅", "failed": "❌", "pending": "⬜"}.get(task.status, "?")
|
||||
print(f"{status_icon} [{task.priority}] {task.title}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Markdown task manager")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# next command
|
||||
next_parser = subparsers.add_parser("next", help="Get next task")
|
||||
next_parser.add_argument("--file", required=True, help="Markdown file path")
|
||||
next_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
next_parser.set_defaults(func=cmd_next)
|
||||
|
||||
# done command
|
||||
done_parser = subparsers.add_parser("done", help="Mark task as completed")
|
||||
done_parser.add_argument("--file", required=True, help="Markdown file path")
|
||||
done_parser.add_argument("--task", required=True, help="Task title")
|
||||
done_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
done_parser.set_defaults(func=cmd_done)
|
||||
|
||||
# fail command
|
||||
fail_parser = subparsers.add_parser("fail", help="Mark task as failed")
|
||||
fail_parser.add_argument("--file", required=True, help="Markdown file path")
|
||||
fail_parser.add_argument("--task", required=True, help="Task title")
|
||||
fail_parser.add_argument("--reason", default="", help="Failure reason")
|
||||
fail_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
fail_parser.set_defaults(func=cmd_fail)
|
||||
|
||||
# status command
|
||||
status_parser = subparsers.add_parser("status", help="Show status summary")
|
||||
status_parser.add_argument("--file", required=True, help="Markdown file path")
|
||||
status_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
status_parser.set_defaults(func=cmd_status)
|
||||
|
||||
# list command
|
||||
list_parser = subparsers.add_parser("list", help="List all tasks")
|
||||
list_parser.add_argument("--file", required=True, help="Markdown file path")
|
||||
list_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
list_parser.set_defaults(func=cmd_list)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
args.func(args)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {args.file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user