- todo_parser: 支持无方括号的短 ID 提取(G-001/H-000 格式)
原来只认 [T-xxx] 方括号格式,非方括号行退化为全文当 ID
- arc_mode: parallel_review_mode 保留 arcPhase 字段
原来写 state 时覆盖为 {enabled, updatedAt, projectRoot},丢失 phase
- airplan.py: 新增 --task-text CLI 参数
原来 argparse 缺此参数,do 模式无法接收任务描述
- do_mode.main: 透传 task_text 给 enter_worker
原来调 enter_worker(project_root, tid) 缺第三个参数
P1-20 is_ui_task() 永远收到空字符串
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
"""
|
||
TODO 解析器 — V2 修复 P1-8:列索引从表头推导,不再硬编码 cells[1]/cells[6]/cells[7]。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
|
||
|
||
@dataclass
|
||
class TodoTask:
|
||
task_id: str
|
||
task: str
|
||
files_dirs: str = ""
|
||
status: str = "TODO"
|
||
done_when: str = ""
|
||
validations: str = ""
|
||
adr: str = ""
|
||
|
||
|
||
def parse_tasks(todo_path: Path) -> list[TodoTask]:
|
||
"""从 todo.md 解析任务列表,动态检测列索引。"""
|
||
if not todo_path.exists():
|
||
return []
|
||
|
||
content = todo_path.read_text(encoding="utf-8")
|
||
lines = [l.strip() for l in content.splitlines() if l.strip()]
|
||
|
||
# 找到 Markdown 表格头
|
||
header_idx = -1
|
||
for i, line in enumerate(lines):
|
||
if line.startswith("|") and "Task" in line and "Status" in line:
|
||
header_idx = i
|
||
break
|
||
|
||
if header_idx < 0:
|
||
return []
|
||
|
||
# 解析列名
|
||
header_line = lines[header_idx]
|
||
header_cols = [c.strip() for c in header_line.split("|") if c.strip()]
|
||
|
||
# 建立列名 → 索引映射
|
||
col_map = {}
|
||
for idx, col_name in enumerate(header_cols):
|
||
col_name_lower = col_name.lower()
|
||
if "task" in col_name_lower:
|
||
col_map["task"] = idx
|
||
elif "status" in col_name_lower:
|
||
col_map["status"] = idx
|
||
elif "files" in col_name_lower or "dir" in col_name_lower:
|
||
col_map["files_dirs"] = idx
|
||
elif "done" in col_name_lower or "when" in col_name_lower:
|
||
col_map["done_when"] = idx
|
||
elif "valid" in col_name_lower:
|
||
col_map["validations"] = idx
|
||
elif "adr" in col_name_lower:
|
||
col_map["adr"] = idx
|
||
|
||
# 跳过表头和分隔符
|
||
tasks = []
|
||
for line in lines[header_idx + 2:]:
|
||
if not line.startswith("|"):
|
||
continue
|
||
cells = [c.strip() for c in line.split("|") if len(c.strip()) > 0]
|
||
if not cells:
|
||
continue
|
||
|
||
task_cell = cells[col_map.get("task", 0)] if col_map.get("task", 0) < len(cells) else ""
|
||
# 优先提取 [T-xxx] 方括号格式的 ID;若没有则尝试从开头提取 G-001/H-000 类短 ID
|
||
tid_match = re.match(r"\[([A-Za-z0-9_\-\.]+)\]", task_cell)
|
||
if tid_match:
|
||
task_id = tid_match.group(1)
|
||
else:
|
||
short_match = re.match(r"^([A-Z]+-\d+[a-z]*)", task_cell)
|
||
task_id = short_match.group(1) if short_match else task_cell
|
||
task = task_cell
|
||
status = cells[col_map.get("status", 1)] if col_map.get("status", 1) < len(cells) else "TODO"
|
||
files_dirs = cells[col_map.get("files_dirs", 2)] if col_map.get("files_dirs", 2) < len(cells) else ""
|
||
done_when = cells[col_map.get("done_when", 3)] if col_map.get("done_when", 3) < len(cells) else ""
|
||
validations = cells[col_map.get("validations", 4)] if col_map.get("validations", 4) < len(cells) else ""
|
||
adr = cells[col_map.get("adr", 5)] if col_map.get("adr", 5) < len(cells) else ""
|
||
|
||
# 清理标记
|
||
task_id = re.sub(r"^\[|\]$", "", task_id).strip()
|
||
|
||
if task_id and task_id != "---":
|
||
tasks.append(TodoTask(
|
||
task_id=task_id,
|
||
task=task,
|
||
files_dirs=files_dirs,
|
||
status=status.upper() if status else "TODO",
|
||
done_when=done_when,
|
||
validations=validations,
|
||
adr=adr,
|
||
))
|
||
|
||
return tasks |