feat: T-1.21 + T-1.22 + P1-23 — 打通 dispatch→Worker→merge 全链路
T-1.21 (P1-22): Dispatch → Worker 桥接 - eng_mode: spawn_workers() 为每个 ready 任务生成标准 Skill 调用参数 - commands/eng.md: dispatch 段从意图描述改为可执行5步伪代码 Agent 不再需要猜测工具名、参数格式、task-text 来源 T-1.22 (P1-24): Merge → TaskGraph 状态同步 - merge_worker_result Phase 6.5: 写回 task-graph.json 节点 status - merge 后节点状态 DONE,ready_tasks() 过滤已完成任务 _select_ready_tasks 修复: - DAG ready 为空时不再 fallback 到 todo.md(todo.md 状态陈旧时导致重复派发) P1-23: commands/eng.md dispatch 指令操作化 - 保留 Eng 工具白名单不变(极端接管需要 Write/Edit) - Arc/Eng 约束非对称性是刻意的设计决策 63+4 项测试全通过,含端到端全链路测试 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -556,6 +556,16 @@ def merge_worker_result(project_root: Path, result_path: Path) -> dict:
|
||||
state["lastMergeAt"] = now_iso()
|
||||
atomic_json_write(paths["state"], state)
|
||||
|
||||
# Phase 6.5: 同步 task-graph.json 节点状态(P1-24)
|
||||
tg_json = airplan_root(project_root) / "state" / "airarc" / "reviews" / "task-graph.json"
|
||||
if tg_json.exists():
|
||||
from air_runtime.modes.arc_mode import _export_task_graph_json
|
||||
graph = TaskGraph.load(tg_json)
|
||||
if task_id in graph.nodes:
|
||||
new_status = "DONE" if status == "done" else status.upper()
|
||||
graph.nodes[task_id].status = new_status
|
||||
_export_task_graph_json(graph, tg_json)
|
||||
|
||||
log.emit(MERGE_COMPLETED, {
|
||||
"taskId": task_id,
|
||||
"status": status,
|
||||
@@ -586,17 +596,17 @@ def merge_worker_result(project_root: Path, result_path: Path) -> dict:
|
||||
|
||||
|
||||
def _select_ready_tasks(project_root: Path, max_count: int) -> list[str]:
|
||||
"""优先从 task-graph.json 的 DAG 计算 in-degree 为 0 的 TODO task,fallback parse_tasks。"""
|
||||
"""优先从 task-graph.json 的 DAG 计算 in-degree 为 0 的 TODO task。
|
||||
DAG 中 ready 为空意味着无任务可派发(全部完成或全部被依赖阻塞),不应 fallback 到 todo.md。"""
|
||||
task_graph_json = airplan_root(project_root) / "state" / "airarc" / "reviews" / "task-graph.json"
|
||||
if task_graph_json.exists():
|
||||
try:
|
||||
graph = TaskGraph.load(task_graph_json)
|
||||
ready = graph.ready_tasks()
|
||||
if ready:
|
||||
return ready[:max_count]
|
||||
return ready[:max_count] # 空列表也是正确答案,不 fallback
|
||||
except Exception:
|
||||
pass
|
||||
# fallback
|
||||
# fallback:仅在 task-graph.json 不存在时使用 todo.md
|
||||
todo = get_todo_path(project_root)
|
||||
if not todo.exists():
|
||||
return []
|
||||
@@ -604,6 +614,25 @@ def _select_ready_tasks(project_root: Path, max_count: int) -> list[str]:
|
||||
return [t.task_id for t in tasks if t.status == "TODO"][:max_count]
|
||||
|
||||
|
||||
def spawn_workers(project_root: Path, task_ids: list[str]) -> list[dict]:
|
||||
"""T-1.21: 为每个 ready 任务准备 spawn 指令,返回 Agent 可直接消费的 Skill 调用参数列表。
|
||||
不实际启动子进程——启动由 Agent 框架的 Skill 工具完成。
|
||||
"""
|
||||
tg_json = airplan_root(project_root) / "state" / "airarc" / "reviews" / "task-graph.json"
|
||||
graph = TaskGraph.load(tg_json) if tg_json.exists() else TaskGraph()
|
||||
instructions = []
|
||||
for tid in task_ids:
|
||||
node = graph.nodes.get(tid)
|
||||
task_text = node.task if node else ""
|
||||
instructions.append({
|
||||
"skill": "airplan",
|
||||
"args": f"do --sub enter --task-id {tid} --task-text '{task_text}' --project .",
|
||||
"taskId": tid,
|
||||
"taskText": task_text,
|
||||
})
|
||||
return instructions
|
||||
|
||||
|
||||
def handle_adr_invalidation(project_root: Path, adr_id: str) -> dict:
|
||||
"""P1-21: ADR 变更级联失效处理。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user