mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-07-29 03:37:44 +00:00
chore: move workflow/repo/documents/config under assets
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# workflow_engine
|
||||
|
||||
全自动开发闭环的轻量编排引擎,基于 **文件事件 Hook + 状态机** 实现。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
workflow_engine/
|
||||
├── runner.py # 状态机调度器
|
||||
├── hook_runner.sh # 文件监听 Hook (inotify)
|
||||
├── state/
|
||||
│ └── current_step.json # 当前状态
|
||||
└── artifacts/
|
||||
└── <run_id>/ # 每次运行的产物
|
||||
├── step1.json
|
||||
├── step2.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 手动模式(无 Hook)
|
||||
|
||||
```bash
|
||||
# 启动新工作流
|
||||
python runner.py start
|
||||
|
||||
# 查看状态
|
||||
python runner.py status
|
||||
```
|
||||
|
||||
### 2. 自动模式(Hook 监听)
|
||||
|
||||
```bash
|
||||
# 终端 1: 启动 Hook 监听
|
||||
./hook_runner.sh
|
||||
|
||||
# 终端 2: 启动工作流(状态变更会自动触发后续步骤)
|
||||
python runner.py start
|
||||
```
|
||||
|
||||
## 状态文件 Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "20251225T053800",
|
||||
"step": "step3",
|
||||
"status": "running|success|failed|completed|fatal_error",
|
||||
"payload_path": "artifacts/20251225T053800/step2.json",
|
||||
"verify": {"status": "success|failed", "details": "..."},
|
||||
"target_step": "step2|step5|done",
|
||||
"retry_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
## 流程控制
|
||||
|
||||
```
|
||||
step1 → step2 → step3 → step4 → step5
|
||||
│
|
||||
┌─────────────┴─────────────┐
|
||||
│ │
|
||||
verify=failed verify=success
|
||||
│ │
|
||||
▼ ▼
|
||||
target_step=step2 target_step=done
|
||||
(回跳重规划) (流程结束)
|
||||
```
|
||||
|
||||
## 熔断机制
|
||||
|
||||
- 同一任务最多重试 3 次
|
||||
- 超过后状态变为 `fatal_error`,需人工介入
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] 集成实际 LLM 调用(替换 runner.py 中的 MOCK)
|
||||
- [ ] 添加 CI 集成示例
|
||||
- [ ] 支持并行任务处理
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"step": "step1",
|
||||
"status": "success",
|
||||
"output": "[MOCK] step1 completed",
|
||||
"timestamp": "2025-12-25T05:45:01.810163"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"step": "step2",
|
||||
"status": "success",
|
||||
"output": "[MOCK] step2 completed",
|
||||
"timestamp": "2025-12-25T05:45:01.812465"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"step": "step3",
|
||||
"status": "success",
|
||||
"output": "[MOCK] step3 completed",
|
||||
"timestamp": "2025-12-25T05:45:01.816471"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"step": "step4",
|
||||
"status": "success",
|
||||
"output": "[MOCK] step4 completed",
|
||||
"timestamp": "2025-12-25T05:45:01.823537"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"step": "step5",
|
||||
"status": "success",
|
||||
"output": "[MOCK] step5 completed",
|
||||
"timestamp": "2025-12-25T05:45:01.824088"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# workflow_engine/hook_runner.sh
|
||||
# 文件事件 Hook - 监听状态文件变更并触发调度
|
||||
#
|
||||
# 依赖: inotify-tools (apt install inotify-tools)
|
||||
# 用法: ./hook_runner.sh
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
STATE_FILE="$SCRIPT_DIR/state/current_step.json"
|
||||
RUNNER="$SCRIPT_DIR/runner.py"
|
||||
|
||||
echo "[HOOK] 启动监听: $STATE_FILE"
|
||||
echo "[HOOK] 按 Ctrl+C 停止"
|
||||
|
||||
# 检查依赖
|
||||
if ! command -v inotifywait &> /dev/null; then
|
||||
echo "[ERROR] 需要安装 inotify-tools: sudo apt install inotify-tools"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 确保状态文件存在
|
||||
mkdir -p "$(dirname "$STATE_FILE")"
|
||||
[ -f "$STATE_FILE" ] || echo '{"status":"idle"}' > "$STATE_FILE"
|
||||
|
||||
# 监听文件修改事件
|
||||
inotifywait -m -e modify "$STATE_FILE" 2>/dev/null | while read -r directory event filename; do
|
||||
echo "[HOOK] $(date '+%H:%M:%S') 检测到状态变更"
|
||||
|
||||
# 读取 target_step
|
||||
target=$(python3 -c "import json; print(json.load(open('$STATE_FILE')).get('target_step',''))" 2>/dev/null)
|
||||
|
||||
if [ "$target" = "done" ]; then
|
||||
echo "[HOOK] 工作流已完成"
|
||||
elif [ -n "$target" ] && [ "$target" != "null" ]; then
|
||||
echo "[HOOK] 触发调度 -> $target"
|
||||
python3 "$RUNNER" dispatch
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
workflow_engine/runner.py - 轻量状态机调度器
|
||||
用于编排 step1~step5 的全自动开发闭环
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).parent.parent
|
||||
STATE_FILE = BASE_DIR / "workflow_engine/state/current_step.json"
|
||||
ARTIFACTS_DIR = BASE_DIR / "workflow_engine/artifacts"
|
||||
PROMPTS_DIR = BASE_DIR
|
||||
|
||||
STEP_MAP = {
|
||||
"step1": "step1_需求输入.jsonl",
|
||||
"step2": "step2_执行计划.jsonl",
|
||||
"step3": "step3_实施变更.jsonl",
|
||||
"step4": "step4_验证发布.jsonl",
|
||||
"step5": "step5_总控与循环.jsonl",
|
||||
}
|
||||
|
||||
STEP_FLOW = ["step1", "step2", "step3", "step4", "step5"]
|
||||
|
||||
|
||||
def load_state() -> dict:
|
||||
if STATE_FILE.exists():
|
||||
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
|
||||
return {"run_id": None, "step": None, "status": "idle"}
|
||||
|
||||
|
||||
def save_state(state: dict):
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def get_run_id() -> str:
|
||||
return datetime.now().strftime("%Y%m%dT%H%M%S")
|
||||
|
||||
|
||||
def get_artifact_path(run_id: str, step: str, ext: str = "json") -> Path:
|
||||
path = ARTIFACTS_DIR / run_id
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path / f"{step}.{ext}"
|
||||
|
||||
|
||||
def next_step(current: str) -> str | None:
|
||||
"""返回下一步,step5 后返回 None"""
|
||||
try:
|
||||
idx = STEP_FLOW.index(current)
|
||||
return STEP_FLOW[idx + 1] if idx + 1 < len(STEP_FLOW) else None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def run_step(step: str, state: dict, input_data: dict = None):
|
||||
"""执行单个步骤(实际调用模型的占位)"""
|
||||
prompt_file = PROMPTS_DIR / STEP_MAP.get(step, "")
|
||||
if not prompt_file.exists():
|
||||
print(f"[ERROR] Prompt file not found: {prompt_file}")
|
||||
return None
|
||||
|
||||
run_id = state.get("run_id") or get_run_id()
|
||||
|
||||
# 更新状态为 running
|
||||
state.update({"run_id": run_id, "step": step, "status": "running"})
|
||||
save_state(state)
|
||||
|
||||
print(f"[RUN] {step} | run_id={run_id}")
|
||||
print(f" prompt: {prompt_file.name}")
|
||||
|
||||
# === 这里是模型调用占位 ===
|
||||
# 实际实现时替换为:
|
||||
# result = call_llm(prompt_file.read_text(), input_data)
|
||||
result = {
|
||||
"step": step,
|
||||
"status": "success", # 模拟成功
|
||||
"output": f"[MOCK] {step} completed",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
# ========================
|
||||
|
||||
# 保存产物
|
||||
artifact_path = get_artifact_path(run_id, step)
|
||||
artifact_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f" artifact: {artifact_path}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def dispatch():
|
||||
"""根据当前状态分发到下一步"""
|
||||
state = load_state()
|
||||
target = state.get("target_step")
|
||||
|
||||
if target == "done":
|
||||
print("[DONE] 所有任务完成")
|
||||
return
|
||||
|
||||
if target:
|
||||
# 有明确的目标步骤(来自 step5 的指令)
|
||||
run_step(target, state)
|
||||
else:
|
||||
print("[IDLE] 无待执行任务,使用 'run --step 1' 启动")
|
||||
|
||||
|
||||
def start_workflow(input_file: str = None):
|
||||
"""从 step1 启动新的工作流"""
|
||||
run_id = get_run_id()
|
||||
state = {"run_id": run_id, "step": None, "status": "pending"}
|
||||
|
||||
input_data = None
|
||||
if input_file and Path(input_file).exists():
|
||||
input_data = json.loads(Path(input_file).read_text(encoding="utf-8"))
|
||||
|
||||
print(f"[START] 新工作流 run_id={run_id}")
|
||||
|
||||
# 依次执行 step1 -> step5
|
||||
for step in STEP_FLOW:
|
||||
result = run_step(step, state, input_data)
|
||||
|
||||
if not result:
|
||||
state["status"] = "error"
|
||||
save_state(state)
|
||||
return
|
||||
|
||||
# step4 后检查验证结果
|
||||
if step == "step4":
|
||||
verify_status = result.get("verify", {}).get("status", "success")
|
||||
state["verify"] = {"status": verify_status}
|
||||
|
||||
# step5 决定下一步
|
||||
if step == "step5":
|
||||
# 模拟 step5 的决策逻辑
|
||||
if state.get("verify", {}).get("status") == "failed":
|
||||
state["target_step"] = "step2" # 失败回跳
|
||||
state["status"] = "retry"
|
||||
print(f"[RETRY] 验证失败,返回 step2 重规划")
|
||||
else:
|
||||
state["target_step"] = "done"
|
||||
state["status"] = "completed"
|
||||
print(f"[COMPLETE] 工作流完成")
|
||||
|
||||
save_state(state)
|
||||
|
||||
# 如果需要回跳,递归处理(带熔断)
|
||||
if state.get("target_step") == "step2":
|
||||
retry_count = state.get("retry_count", 0) + 1
|
||||
if retry_count > 3:
|
||||
print(f"[FATAL] 超过最大重试次数")
|
||||
state["status"] = "fatal_error"
|
||||
save_state(state)
|
||||
return
|
||||
state["retry_count"] = retry_count
|
||||
print(f"[RETRY {retry_count}/3] 从 step2 重新执行")
|
||||
|
||||
# 递归调用自身,复用完整的验证逻辑
|
||||
state["target_step"] = None # 清除回跳标记
|
||||
save_state(state)
|
||||
|
||||
for retry_step in STEP_FLOW[1:]: # step2 onwards
|
||||
result = run_step(retry_step, state, input_data)
|
||||
if not result:
|
||||
state["status"] = "error"
|
||||
save_state(state)
|
||||
return
|
||||
|
||||
# step4 后检查验证结果
|
||||
if retry_step == "step4":
|
||||
verify_status = result.get("verify", {}).get("status", "success")
|
||||
state["verify"] = {"status": verify_status}
|
||||
|
||||
# step5 决定下一步
|
||||
if retry_step == "step5":
|
||||
if state.get("verify", {}).get("status") == "failed":
|
||||
state["target_step"] = "step2"
|
||||
state["status"] = "retry"
|
||||
print(f"[RETRY] 验证仍失败,继续重试")
|
||||
break # 跳出内层循环,触发外层重试检查
|
||||
else:
|
||||
state["target_step"] = "done"
|
||||
state["status"] = "completed"
|
||||
print(f"[COMPLETE] 重试成功,工作流完成")
|
||||
|
||||
save_state(state)
|
||||
|
||||
# 如果 step5 设置了新的回跳目标,继续递归
|
||||
if state.get("target_step") == "step2":
|
||||
continue # 继续外层循环的下一次迭代(实际会被 break 跳出)
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:")
|
||||
print(" python runner.py start [input.json] - 启动新工作流")
|
||||
print(" python runner.py dispatch - 根据状态分发")
|
||||
print(" python runner.py status - 查看当前状态")
|
||||
return
|
||||
|
||||
cmd = sys.argv[1]
|
||||
|
||||
if cmd == "start":
|
||||
input_file = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
start_workflow(input_file)
|
||||
elif cmd == "dispatch":
|
||||
dispatch()
|
||||
elif cmd == "status":
|
||||
state = load_state()
|
||||
print(json.dumps(state, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(f"Unknown command: {cmd}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"run_id": "20251225T054501",
|
||||
"step": "step5",
|
||||
"status": "completed",
|
||||
"verify": {
|
||||
"status": "success"
|
||||
},
|
||||
"target_step": "done"
|
||||
}
|
||||
Reference in New Issue
Block a user