diff --git a/skills/auto-tmux/SKILL.md b/skills/auto-tmux/SKILL.md index 3d751d7..7d85d6e 100644 --- a/skills/auto-tmux/SKILL.md +++ b/skills/auto-tmux/SKILL.md @@ -152,6 +152,7 @@ skills/auto-tmux/scripts/swarm-state.sh lock-acquire --name README.md --owner :. --reason "等待输入" skills/auto-tmux/scripts/swarm-state.sh task-fail --id task-001 --owner :. --reason "测试失败" +skills/auto-tmux/scripts/swarm-state.sh validate --dir /tmp/ai_swarm skills/auto-tmux/scripts/swarm-state.sh report --dir /tmp/ai_swarm ``` diff --git a/skills/auto-tmux/references/automation.md b/skills/auto-tmux/references/automation.md index 5ca4ece..7b6bff6 100644 --- a/skills/auto-tmux/references/automation.md +++ b/skills/auto-tmux/references/automation.md @@ -216,6 +216,7 @@ skills/auto-tmux/scripts/swarm-state.sh lock-prune --older-than 3600 --dry-run ```bash skills/auto-tmux/scripts/swarm-state.sh task-done --id task-001 --owner "ai-hub:2.1" --result "make test passed" +skills/auto-tmux/scripts/swarm-state.sh validate skills/auto-tmux/scripts/swarm-state.sh report ``` diff --git a/skills/auto-tmux/references/swarm-state.md b/skills/auto-tmux/references/swarm-state.md index 94f1c8e..bb2b9ec 100644 --- a/skills/auto-tmux/references/swarm-state.md +++ b/skills/auto-tmux/references/swarm-state.md @@ -115,6 +115,14 @@ skills/auto-tmux/scripts/swarm-state.sh task-fail \ skills/auto-tmux/scripts/swarm-state.sh task-list ``` +校验状态一致性: + +```bash +skills/auto-tmux/scripts/swarm-state.sh validate +``` + +`validate` 会检查 `tasks.tsv` 表头、字段数量、重复任务 ID、非法状态、终态任务结果文件,以及锁目录中的 `owner` / `created_at`。 + ## 锁管理 锁用于避免多个 worker 同时修改同一文件、目录或服务。 @@ -165,6 +173,8 @@ skills/auto-tmux/scripts/swarm-state.sh report - 当前锁。 - 最近状态日志。 +报告前建议先运行 `validate`,避免把坏状态继续传给 commander 或 reviewer。 + ## 推荐协作流 1. commander 初始化状态目录。 diff --git a/skills/auto-tmux/scripts/README.md b/skills/auto-tmux/scripts/README.md index 7f36a71..da3c27c 100644 --- a/skills/auto-tmux/scripts/README.md +++ b/skills/auto-tmux/scripts/README.md @@ -55,6 +55,7 @@ skills/auto-tmux/scripts/swarm-state.sh task-next --owner "$target" --dir /tmp/a skills/auto-tmux/scripts/swarm-state.sh lock-prune --older-than 3600 --dry-run --dir /tmp/ai_swarm skills/auto-tmux/scripts/swarm-state.sh task-block --id task-001 --owner "$target" --reason "等待输入" --dir /tmp/ai_swarm skills/auto-tmux/scripts/swarm-state.sh task-fail --id task-001 --owner "$target" --reason "测试失败" --dir /tmp/ai_swarm +skills/auto-tmux/scripts/swarm-state.sh validate --dir /tmp/ai_swarm skills/auto-tmux/scripts/swarm-state.sh report --dir /tmp/ai_swarm # 端到端自测 diff --git a/skills/auto-tmux/scripts/auto-tmux-smoke-test.sh b/skills/auto-tmux/scripts/auto-tmux-smoke-test.sh index ac56ff4..489b498 100755 --- a/skills/auto-tmux/scripts/auto-tmux-smoke-test.sh +++ b/skills/auto-tmux/scripts/auto-tmux-smoke-test.sh @@ -69,6 +69,7 @@ grep -q 'smoke-next' /tmp/auto-tmux-smoke-task-next.txt "$SWARM_STATE" task-add --dir "$SWARM_DIR" --id smoke-fail --text "fail task" >/tmp/auto-tmux-smoke-task-fail-add.txt "$SWARM_STATE" task-fail --dir "$SWARM_DIR" --id smoke-fail --owner "$worker_target" --reason "expected failure" "$SWARM_STATE" report --dir "$SWARM_DIR" >/tmp/auto-tmux-smoke-report.txt +"$SWARM_STATE" validate --dir "$SWARM_DIR" >/tmp/auto-tmux-smoke-state-validate.txt grep -q 'smoke-task' /tmp/auto-tmux-smoke-report.txt grep -q 'BLOCKED' /tmp/auto-tmux-smoke-report.txt grep -q 'FAIL' /tmp/auto-tmux-smoke-report.txt diff --git a/skills/auto-tmux/scripts/swarm-state.sh b/skills/auto-tmux/scripts/swarm-state.sh index bdd72a1..4a1d418 100755 --- a/skills/auto-tmux/scripts/swarm-state.sh +++ b/skills/auto-tmux/scripts/swarm-state.sh @@ -29,6 +29,7 @@ Usage: swarm-state.sh lock-list [--dir DIR] swarm-state.sh lock-prune --older-than SEC [--dir DIR] [--dry-run] swarm-state.sh report [--dir DIR] + swarm-state.sh validate [--dir DIR] Environment: AUTO_TMUX_SWARM_DIR overrides the default /tmp/ai_swarm. @@ -396,6 +397,77 @@ cmd_report() { cmd_status -n 20 } +cmd_validate() { + while [[ $# -gt 0 ]]; do + case "$1" in + --dir) set_dir "${2:-}"; shift 2 ;; + *) die "unknown validate option: $1" ;; + esac + done + ensure_init + + local failures=0 + local expected_header=$'id\tstatus\towner\ttext\tresult' + local actual_header + actual_header="$(head -n 1 "$TASKS_TSV")" + if [[ "$actual_header" == "$expected_header" ]]; then + printf '[OK] tasks header\n' + else + printf '[FAIL] invalid tasks header: %s\n' "$actual_header" >&2 + failures=$((failures + 1)) + fi + + local awk_output + if awk_output="$(awk -F '\t' ' + NR == 1 {next} + NF != 5 {print "[FAIL] invalid field count at line " NR ": " NF; bad = 1; next} + $1 == "" {print "[FAIL] empty task id at line " NR; bad = 1} + seen[$1]++ {print "[FAIL] duplicate task id: " $1; bad = 1} + $2 !~ /^(TODO|DOING|DONE|BLOCKED|FAIL)$/ {print "[FAIL] invalid status for " $1 ": " $2; bad = 1} + END {exit bad ? 1 : 0} + ' "$TASKS_TSV" 2>&1)"; then + printf '[OK] tasks rows\n' + else + printf '%s\n' "$awk_output" >&2 + failures=$((failures + 1)) + fi + + local id status owner text result + while IFS=$'\t' read -r id status owner text result; do + [[ -n "$id" ]] || continue + case "$status" in + DONE|BLOCKED|FAIL) + if [[ -s "$RESULTS_DIR/$id.txt" ]]; then + printf '[OK] result exists: %s\n' "$id" + else + printf '[FAIL] missing result file: %s\n' "$id" >&2 + failures=$((failures + 1)) + fi + ;; + esac + done < <(tail -n +2 "$TASKS_TSV") + + local path name + while IFS= read -r path; do + [[ -n "$path" ]] || continue + name="$(basename "$path" .lock.d)" + if [[ ! -s "$path/owner" ]]; then + printf '[FAIL] lock missing owner: %s\n' "$name" >&2 + failures=$((failures + 1)) + fi + if [[ ! -s "$path/created_at" ]]; then + printf '[FAIL] lock missing created_at: %s\n' "$name" >&2 + failures=$((failures + 1)) + fi + done < <(find "$LOCKS_DIR" -mindepth 1 -maxdepth 1 -type d -name '*.lock.d' -print | sort) + + if (( failures > 0 )); then + printf 'swarm state invalid: %s issue(s)\n' "$failures" >&2 + return 1 + fi + printf 'swarm state valid: %s\n' "$SWARM_DIR" +} + main() { local cmd="${1:-help}" shift || true @@ -416,6 +488,7 @@ main() { lock-list) cmd_lock_list "$@" ;; lock-prune) cmd_lock_prune "$@" ;; report) cmd_report "$@" ;; + validate) cmd_validate "$@" ;; *) die "unknown command: $cmd" ;; esac }