mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-08-22 23:38:05 +00:00
feat: skills - add auto-tmux swarm handoff
This commit is contained in:
@@ -10,6 +10,7 @@ scripts/
|
||||
├── README.md # 面向人类的脚本入口说明
|
||||
├── auto-tmux.sh # 统一 tmux 自动化命令入口
|
||||
├── swarm-state.sh # 蜂群状态、任务、锁和报告管理
|
||||
├── swarm-brief.sh # 只读生成蜂群交接报告
|
||||
├── render-swarm-prompt.sh # commander/worker/reviewer 提示词渲染
|
||||
└── auto-tmux-smoke-test.sh # 临时 tmux 会话端到端自测
|
||||
```
|
||||
@@ -22,6 +23,7 @@ scripts/
|
||||
- 批量救援必须通过 pattern 匹配触发,不允许无条件向所有 pane 发送输入。
|
||||
- 批量广播必须要求显式 session,并支持 `--dry-run`。
|
||||
- Prompt 渲染脚本只输出文本,不直接控制 tmux。
|
||||
- 交接报告脚本只读调用 doctor/topology/scan/report,不直接发送按键。
|
||||
- 状态脚本只能写入显式状态目录,不保存密钥、Token、密码或私密项目内容。
|
||||
- 自测脚本必须创建独立临时 tmux session,并在退出时清理。
|
||||
- 新增脚本后必须更新 `README.md`、`SKILL.md`、`references/index.md`,并运行 `bash -n` 与 skill strict 校验。
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- [`auto-tmux.sh`](./auto-tmux.sh) - 统一命令入口,支持拓扑、读取、发送、巡检、救援、录制、等待和 AI 工作台初始化。
|
||||
- [`swarm-state.sh`](./swarm-state.sh) - 蜂群状态、任务、锁和报告管理。
|
||||
- [`swarm-brief.sh`](./swarm-brief.sh) - 只读生成蜂群交接报告,汇总 doctor、topology、pane 输出和状态报告。
|
||||
- [`render-swarm-prompt.sh`](./render-swarm-prompt.sh) - commander、worker、reviewer 提示词渲染。
|
||||
- [`auto-tmux-smoke-test.sh`](./auto-tmux-smoke-test.sh) - 创建临时 tmux 会话,端到端验证脚本能力。
|
||||
|
||||
@@ -38,9 +39,15 @@ skills/auto-tmux/scripts/auto-tmux.sh broadcast --session ai-hub --text "pwd" --
|
||||
# 生成证据快照
|
||||
skills/auto-tmux/scripts/auto-tmux.sh snapshot --session ai-hub --dir /tmp/auto-tmux-snapshot
|
||||
|
||||
# 生成蜂群交接报告
|
||||
skills/auto-tmux/scripts/swarm-brief.sh --session ai-hub --swarm-dir /tmp/ai_swarm --out /tmp/auto-tmux-brief
|
||||
|
||||
# 初始化蜂群状态并添加任务
|
||||
skills/auto-tmux/scripts/swarm-state.sh init --dir /tmp/ai_swarm
|
||||
skills/auto-tmux/scripts/swarm-state.sh task-add --id task-001 --text "检查 README 链接"
|
||||
skills/auto-tmux/scripts/swarm-state.sh task-next --owner "$target" --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 report --dir /tmp/ai_swarm
|
||||
|
||||
# 端到端自测
|
||||
|
||||
@@ -6,14 +6,16 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
AUTO_TMUX="$SCRIPT_DIR/auto-tmux.sh"
|
||||
SWARM_STATE="$SCRIPT_DIR/swarm-state.sh"
|
||||
RENDER_PROMPT="$SCRIPT_DIR/render-swarm-prompt.sh"
|
||||
SWARM_BRIEF="$SCRIPT_DIR/swarm-brief.sh"
|
||||
SESSION="auto-tmux-smoke-$$"
|
||||
SWARM_DIR="/tmp/auto-tmux-smoke-swarm-$$"
|
||||
SNAPSHOT_DIR="/tmp/auto-tmux-smoke-snapshot-$$"
|
||||
RECORD_DIR="/tmp/auto-tmux-smoke-record-$$"
|
||||
BRIEF_DIR="/tmp/auto-tmux-smoke-brief-$$"
|
||||
|
||||
cleanup() {
|
||||
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||
rm -rf "$SWARM_DIR" "$SNAPSHOT_DIR" "$RECORD_DIR"
|
||||
rm -rf "$SWARM_DIR" "$SNAPSHOT_DIR" "$RECORD_DIR" "$BRIEF_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
@@ -25,6 +27,7 @@ command -v tmux >/dev/null 2>&1 || {
|
||||
bash -n "$AUTO_TMUX"
|
||||
bash -n "$SWARM_STATE"
|
||||
bash -n "$RENDER_PROMPT"
|
||||
bash -n "$SWARM_BRIEF"
|
||||
|
||||
"$AUTO_TMUX" hub --session "$SESSION" --workers 1 --cmd bash
|
||||
"$AUTO_TMUX" doctor --session "$SESSION" >/tmp/auto-tmux-smoke-doctor.txt
|
||||
@@ -49,8 +52,18 @@ test -s "$SNAPSHOT_DIR/topology.txt"
|
||||
"$SWARM_STATE" lock-acquire --dir "$SWARM_DIR" --name smoke-file --owner "$worker_target" >/tmp/auto-tmux-smoke-lock.txt
|
||||
"$SWARM_STATE" lock-release --dir "$SWARM_DIR" --name smoke-file --owner "$worker_target" >/tmp/auto-tmux-smoke-unlock.txt
|
||||
"$SWARM_STATE" task-done --dir "$SWARM_DIR" --id smoke-task --owner "$worker_target" --result "ok"
|
||||
"$SWARM_STATE" task-add --dir "$SWARM_DIR" --id smoke-next --text "claim next task" >/tmp/auto-tmux-smoke-task-next-add.txt
|
||||
"$SWARM_STATE" task-next --dir "$SWARM_DIR" --owner "$worker_target" >/tmp/auto-tmux-smoke-task-next.txt
|
||||
grep -q 'smoke-next' /tmp/auto-tmux-smoke-task-next.txt
|
||||
"$SWARM_STATE" task-block --dir "$SWARM_DIR" --id smoke-next --owner "$worker_target" --reason "waiting for input"
|
||||
"$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
|
||||
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
|
||||
"$SWARM_BRIEF" --session "$SESSION" --swarm-dir "$SWARM_DIR" --out "$BRIEF_DIR" -n 10 >/tmp/auto-tmux-smoke-brief.txt
|
||||
grep -q 'auto-tmux Swarm Brief' "$BRIEF_DIR/brief.md"
|
||||
"$RENDER_PROMPT" commander --session "$SESSION" --swarm-dir "$SWARM_DIR" --task "smoke" >/tmp/auto-tmux-smoke-commander-prompt.md
|
||||
"$RENDER_PROMPT" worker --session "$SESSION" --target "$worker_target" --swarm-dir "$SWARM_DIR" --task "smoke" >/tmp/auto-tmux-smoke-worker-prompt.md
|
||||
grep -q 'Commander Prompt' /tmp/auto-tmux-smoke-commander-prompt.md
|
||||
|
||||
@@ -147,7 +147,7 @@ cmd_doctor() {
|
||||
status_line "WARN" "tmux server has no reachable session yet"
|
||||
fi
|
||||
|
||||
for script in auto-tmux.sh swarm-state.sh auto-tmux-smoke-test.sh render-swarm-prompt.sh; do
|
||||
for script in auto-tmux.sh swarm-state.sh swarm-brief.sh auto-tmux-smoke-test.sh render-swarm-prompt.sh; do
|
||||
if [[ -x "$script_dir/$script" ]]; then
|
||||
status_line "OK" "script executable: $script"
|
||||
else
|
||||
|
||||
@@ -66,7 +66,8 @@ case "$role" in
|
||||
4. 给每个子任务创建 task,并在涉及同一文件、目录或服务时要求 worker 先加锁。
|
||||
5. 用 scan/capture/snapshot 收集 worker 状态与证据。
|
||||
6. 对卡住任务先 dry-run rescue,再做最小干预。
|
||||
7. 最终只用测试、diff、日志、报告和人工验收作为完成依据。
|
||||
7. 阶段结束时用 swarm-brief.sh 生成交接报告。
|
||||
8. 最终只用测试、diff、日志、报告和人工验收作为完成依据。
|
||||
|
||||
## 禁止
|
||||
|
||||
@@ -92,10 +93,10 @@ EOF
|
||||
## 你的职责
|
||||
|
||||
1. 只处理分配给你的单一任务。
|
||||
2. 开始前用 \`swarm-state.sh task-claim\` 认领任务。
|
||||
2. 开始前用 \`swarm-state.sh task-next\` 或 \`swarm-state.sh task-claim\` 认领任务。
|
||||
3. 修改文件、目录或服务前,用 \`swarm-state.sh lock-acquire\` 获取锁。
|
||||
4. 执行中保留关键命令、错误、测试结果和风险。
|
||||
5. 完成后用 \`swarm-state.sh task-done\` 写入结果。
|
||||
5. 完成后用 \`swarm-state.sh task-done\` 写入结果;无法继续时用 \`task-block\` 或 \`task-fail\` 写明原因。
|
||||
6. 汇报格式必须包含:状态、修改文件、验证结果、风险、下一步。
|
||||
|
||||
## 禁止
|
||||
@@ -120,9 +121,10 @@ EOF
|
||||
## 你的职责
|
||||
|
||||
1. 读取 commander 汇总、worker 结果、snapshot、record 日志和 git diff。
|
||||
2. 优先寻找 correctness、越界修改、缺失测试、风险遗漏和文档不同步。
|
||||
3. 对每个问题给出文件、证据、影响和最小修复建议。
|
||||
4. 如果没有阻塞问题,明确说明剩余风险和建议补充验证。
|
||||
2. 优先读取 \`swarm-brief.sh\` 生成的交接报告,恢复当前拓扑、状态和 pane 输出。
|
||||
3. 优先寻找 correctness、越界修改、缺失测试、风险遗漏和文档不同步。
|
||||
4. 对每个问题给出文件、证据、影响和最小修复建议。
|
||||
5. 如果没有阻塞问题,明确说明剩余风险和建议补充验证。
|
||||
|
||||
## 禁止
|
||||
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a read-only handoff brief for a tmux AI swarm session.
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
swarm-brief: export tmux AI swarm topology, pane output and state report into one brief
|
||||
|
||||
Usage:
|
||||
swarm-brief.sh [--session NAME] [--swarm-dir DIR] [--out DIR] [-n LINES]
|
||||
|
||||
Examples:
|
||||
swarm-brief.sh --session ai-hub --swarm-dir /tmp/ai_swarm
|
||||
swarm-brief.sh --session ai-hub --out /tmp/auto-tmux-brief -n 80
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
auto_tmux="$script_dir/auto-tmux.sh"
|
||||
swarm_state="$script_dir/swarm-state.sh"
|
||||
|
||||
session=""
|
||||
swarm_dir="${AUTO_TMUX_SWARM_DIR:-/tmp/ai_swarm}"
|
||||
out_dir="/tmp/auto-tmux-brief-$(date +%Y%m%d-%H%M%S)"
|
||||
lines=80
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--session) session="${2:-}"; shift 2 ;;
|
||||
--swarm-dir) swarm_dir="${2:-}"; shift 2 ;;
|
||||
--out) out_dir="${2:-}"; shift 2 ;;
|
||||
-n|--lines) lines="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$out_dir"
|
||||
brief="$out_dir/brief.md"
|
||||
|
||||
run_block() {
|
||||
local title="$1"
|
||||
shift
|
||||
{
|
||||
printf '## %s\n\n' "$title"
|
||||
printf '```text\n'
|
||||
"$@" 2>&1 || printf '[command failed: %s]\n' "$*"
|
||||
printf '```\n\n'
|
||||
} >> "$brief"
|
||||
}
|
||||
|
||||
{
|
||||
printf '# auto-tmux Swarm Brief\n\n'
|
||||
printf '%s\n' "- created_at: \`$(date -Is)\`"
|
||||
printf '%s\n' "- session: \`${session:-<all>}\`"
|
||||
printf '%s\n' "- swarm_dir: \`$swarm_dir\`"
|
||||
printf '%s\n\n' "- lines_per_pane: \`$lines\`"
|
||||
printf '本报告是只读交接快照,用于让 commander、reviewer 或新会话快速恢复上下文。\n\n'
|
||||
} > "$brief"
|
||||
|
||||
doctor_args=(doctor)
|
||||
topology_args=(topology)
|
||||
scan_args=(scan -n "$lines")
|
||||
if [[ -n "$session" ]]; then
|
||||
doctor_args+=(--session "$session")
|
||||
topology_args+=(--session "$session")
|
||||
scan_args+=(--session "$session")
|
||||
fi
|
||||
|
||||
run_block "Doctor" "$auto_tmux" "${doctor_args[@]}"
|
||||
run_block "Topology" "$auto_tmux" "${topology_args[@]}"
|
||||
run_block "Recent Pane Output" "$auto_tmux" "${scan_args[@]}"
|
||||
|
||||
if [[ -d "$swarm_dir" ]]; then
|
||||
run_block "Swarm State Report" "$swarm_state" report --dir "$swarm_dir"
|
||||
else
|
||||
{
|
||||
printf '## Swarm State Report\n\n'
|
||||
printf '```text\n'
|
||||
printf 'swarm state dir not found: %s\n' "$swarm_dir"
|
||||
printf 'initialize with: %s init --dir %s\n' "$swarm_state" "$swarm_dir"
|
||||
printf '```\n\n'
|
||||
} >> "$brief"
|
||||
fi
|
||||
|
||||
printf 'brief written: %s\n' "$brief"
|
||||
@@ -7,6 +7,7 @@ STATUS_LOG="$SWARM_DIR/status.log"
|
||||
TASKS_TSV="$SWARM_DIR/tasks.tsv"
|
||||
LOCKS_DIR="$SWARM_DIR/locks"
|
||||
RESULTS_DIR="$SWARM_DIR/results"
|
||||
STATE_LOCK_DIR="$SWARM_DIR/.state.lock.d"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
@@ -18,8 +19,11 @@ Usage:
|
||||
swarm-state.sh status [--dir DIR]
|
||||
swarm-state.sh task-add --text TEXT [--id ID] [--dir DIR]
|
||||
swarm-state.sh task-list [--dir DIR]
|
||||
swarm-state.sh task-next --owner TARGET [--dir DIR]
|
||||
swarm-state.sh task-claim --id ID --owner TARGET [--dir DIR]
|
||||
swarm-state.sh task-done --id ID --owner TARGET [--result TEXT] [--dir DIR]
|
||||
swarm-state.sh task-block --id ID --owner TARGET --reason TEXT [--dir DIR]
|
||||
swarm-state.sh task-fail --id ID --owner TARGET --reason TEXT [--dir DIR]
|
||||
swarm-state.sh lock-acquire --name NAME --owner TARGET [--dir DIR]
|
||||
swarm-state.sh lock-release --name NAME --owner TARGET [--dir DIR] [--force]
|
||||
swarm-state.sh lock-list [--dir DIR]
|
||||
@@ -46,6 +50,7 @@ set_dir() {
|
||||
TASKS_TSV="$SWARM_DIR/tasks.tsv"
|
||||
LOCKS_DIR="$SWARM_DIR/locks"
|
||||
RESULTS_DIR="$SWARM_DIR/results"
|
||||
STATE_LOCK_DIR="$SWARM_DIR/.state.lock.d"
|
||||
}
|
||||
|
||||
ensure_init() {
|
||||
@@ -56,6 +61,25 @@ ensure_init() {
|
||||
fi
|
||||
}
|
||||
|
||||
acquire_state_lock() {
|
||||
local timeout="${1:-10}"
|
||||
local start now
|
||||
start="$(date +%s)"
|
||||
while ! mkdir "$STATE_LOCK_DIR" 2>/dev/null; do
|
||||
now="$(date +%s)"
|
||||
if (( now - start >= timeout )); then
|
||||
die "timeout waiting for swarm state lock: $STATE_LOCK_DIR"
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
printf '%s\n' "$$" > "$STATE_LOCK_DIR/pid"
|
||||
printf '%s\n' "$(date -Is)" > "$STATE_LOCK_DIR/created_at"
|
||||
}
|
||||
|
||||
release_state_lock() {
|
||||
rm -rf "$STATE_LOCK_DIR"
|
||||
}
|
||||
|
||||
cmd_init() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -167,6 +191,40 @@ cmd_task_claim() {
|
||||
cmd_log --target "$owner" --status "START" --message "claimed $id"
|
||||
}
|
||||
|
||||
cmd_task_next() {
|
||||
local owner=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--owner) owner="${2:-}"; shift 2 ;;
|
||||
*) die "unknown task-next option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$owner" ]] || die "missing --owner"
|
||||
ensure_init
|
||||
|
||||
acquire_state_lock
|
||||
local id tmp
|
||||
id="$(awk -F '\t' 'NR > 1 && $2 == "TODO" {print $1; exit}' "$TASKS_TSV")"
|
||||
if [[ -z "$id" ]]; then
|
||||
release_state_lock
|
||||
printf 'no TODO task available\n'
|
||||
return 1
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
awk -F '\t' -v OFS='\t' -v id="$id" -v owner="$owner" '
|
||||
NR == 1 {print; next}
|
||||
$1 == id {$2 = "DOING"; $3 = owner}
|
||||
{print}
|
||||
' "$TASKS_TSV" > "$tmp"
|
||||
mv "$tmp" "$TASKS_TSV"
|
||||
release_state_lock
|
||||
|
||||
cmd_log --target "$owner" --status "START" --message "claimed next $id"
|
||||
printf 'task claimed: %s\n' "$id"
|
||||
}
|
||||
|
||||
cmd_task_done() {
|
||||
local id="" owner="" result="done"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -187,6 +245,29 @@ cmd_task_done() {
|
||||
cmd_log --target "$owner" --status "DONE" --message "$id $result"
|
||||
}
|
||||
|
||||
cmd_task_terminal() {
|
||||
local status="$1"
|
||||
shift
|
||||
local id="" owner="" reason=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--id) id="${2:-}"; shift 2 ;;
|
||||
--owner) owner="${2:-}"; shift 2 ;;
|
||||
--reason) reason="${2:-}"; shift 2 ;;
|
||||
*) die "unknown task-$status option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$id" ]] || die "missing --id"
|
||||
[[ -n "$owner" ]] || die "missing --owner"
|
||||
[[ -n "$reason" ]] || die "missing --reason"
|
||||
ensure_init
|
||||
reason="$(printf '%s' "$reason" | sanitize)"
|
||||
update_task "$id" "$status" "$owner" "$reason"
|
||||
printf '%s\n' "$reason" > "$RESULTS_DIR/$id.txt"
|
||||
cmd_log --target "$owner" --status "$status" --message "$id $reason"
|
||||
}
|
||||
|
||||
lock_path() {
|
||||
local name="$1"
|
||||
name="$(printf '%s' "$name" | sed -E 's#[^A-Za-z0-9_.-]+#_#g')"
|
||||
@@ -290,8 +371,11 @@ main() {
|
||||
status) cmd_status "$@" ;;
|
||||
task-add) cmd_task_add "$@" ;;
|
||||
task-list) cmd_task_list "$@" ;;
|
||||
task-next) cmd_task_next "$@" ;;
|
||||
task-claim) cmd_task_claim "$@" ;;
|
||||
task-done) cmd_task_done "$@" ;;
|
||||
task-block) cmd_task_terminal "BLOCKED" "$@" ;;
|
||||
task-fail) cmd_task_terminal "FAIL" "$@" ;;
|
||||
lock-acquire) cmd_lock_acquire "$@" ;;
|
||||
lock-release) cmd_lock_release "$@" ;;
|
||||
lock-list) cmd_lock_list "$@" ;;
|
||||
|
||||
Reference in New Issue
Block a user