speckit-runner
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRun the speckit pipeline for issue in repo .
$0$1You are an operator running in-session. You will spawn a worker devbox and drive it
through the speckit phases via the gateway worker API.
Gateway: (or ). Token: .
Mission: . Repo: (or ).
$PYLOT_API$PYLOT_GATEWAY_URL$PYLOT_DISPATCH_TOKEN$PYLOT_JOB_ID$1$PYLOT_REPODrive the worker in the foreground by polling the worker API — in short chunks you re-run yourself. After queueing each phase prompt, run the Poll-to-idle snippet (Step P) with the Bash tool, using the default Bash timeout (do NOT pass a long). Each call returns in under 2 minutes with atimeout; while it printsPOLL_RESULT, run Step P again immediately. Every ~30 min it printsPOLL_RESULT=runningwith a decision packet (heartbeat age, output-changed flag, log tail) — review it and, if the worker is healthy, run Step P again to grant another block. Repeat untilPOLL_RESULT=block_elapsed.POLL_RESULT=doneThe trap (read this): the harness auto-backgrounds any Bash command that runs past its tool(default ~120 s). A backgrounded poll is fatal. You may see a completion notification arrive for a background task — ignore that signal as a reason to wait. Those notifications only fire while your session is actively running tool calls; the instant you end your turn to "wait for it," the headlesstimeoutsession exits and the mission is finalized as failed — while the worker is still healthy. So: never set a long Bashclaude -pon Step P, never background it, never "wait for a notification," never end your turn while a worker turn is in flight. If Step P ever gets backgrounded, that is a bug — kill it and run it again. Each call is short synchronous shell you run, read, and re-run yourself.timeout
为仓库中的issue 运行speckit流水线。
$1$0你是一名在会话中运行的操作员。你将生成一个工作者开发环境(worker devbox),并通过网关工作者API驱动它完成speckit的各个阶段。
网关:(或)。令牌:。
任务ID:。仓库:(或)。
$PYLOT_API$PYLOT_GATEWAY_URL$PYLOT_DISPATCH_TOKEN$PYLOT_JOB_ID$1$PYLOT_REPO通过轮询工作者API在前台驱动工作者——以短周期重复运行自身。 在将每个阶段的提示加入队列后,使用Bash工具运行轮询至空闲的代码片段(步骤P),使用默认的Bash超时时间(不要设置长)。每次调用会在2分钟内返回timeout;当输出POLL_RESULT时,立即再次运行步骤P。大约每30分钟,它会输出POLL_RESULT=running并附带一个决策数据包(心跳时长、输出变更标记、日志尾部)——检查该数据包,如果工作者状态健康,再次运行步骤P以授予下一个周期。重复此操作直到POLL_RESULT=block_elapsed。POLL_RESULT=done注意事项(务必阅读): 如果Bash命令运行时间超过工具的(默认约120秒), harness会自动将其转入后台。转入后台的轮询操作是致命的。你可能会看到后台任务的完成通知——忽略该信号,不要以此为等待的理由。这些通知仅在你的会话正在主动运行工具调用时触发;一旦你结束当前轮次去“等待结果”,无头的timeout会话会退出,任务会被标记为失败——而此时工作者可能仍处于健康状态。因此:永远不要为步骤P设置长Bashclaude -p,永远不要将其转入后台,永远不要“等待通知”,永远不要在工作者正在运行时结束你的轮次。如果步骤P意外转入后台,这是一个bug——杀死它并重新运行。每次调用都是你同步运行、读取并重新执行的短shell命令。timeout
Step 0: Dedup Gate
步骤0:重复检查 Gate
Before spawning anything, check if the issue is already closed:
bash
ISSUE_STATE=$(gh issue view $0 --repo $1 --json state --jq '.state' 2>/dev/null || echo "OPEN")
if [ "$ISSUE_STATE" = "CLOSED" ]; then
echo "[pylot] outcome=\"already complete — issue $0 is CLOSED\" status=success"
exit 0
fioutcome="already complete"在生成任何资源之前,检查issue是否已关闭:
bash
ISSUE_STATE=$(gh issue view $0 --repo $1 --json state --jq '.state' 2>/dev/null || echo "OPEN")
if [ "$ISSUE_STATE" = "CLOSED" ]; then
echo "[pylot] outcome=\"already complete — issue $0 is CLOSED\" status=success"
exit 0
fioutcome="already complete"Step 1: Spawn Worker
步骤1:生成工作者
bash
REPO="${1:-$PYLOT_REPO}"
SPAWN_RESP=$(curl -s --max-time 90 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"repo\": \"$REPO\"}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers")
WID=$(echo "$SPAWN_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("worker_id",""))' 2>/dev/null)
if [ -z "$WID" ]; then
echo "[pylot] outcome=\"worker spawn failed: $(echo $SPAWN_RESP | head -c 200)\" status=failed"
exit 1
fi
echo "[speckit-runner] worker spawned: $WID"This skill ships a helper next to this file — the boot-sync copies the whole skill dir, so it lands at on the operator. It is the only way you poll a worker (see Step P) — never hand-roll a poll loop inline, never wait for a notification.
poll-worker.sh~/.claude/skills/speckit-runner/poll-worker.shbash
REPO="${1:-$PYLOT_REPO}"
SPAWN_RESP=$(curl -s --max-time 90 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"repo\": \"$REPO\"}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers")
WID=$(echo "$SPAWN_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("worker_id",""))' 2>/dev/null)
if [ -z "$WID" ]; then
echo "[pylot] outcome=\"worker spawn failed: $(echo $SPAWN_RESP | head -c 200)\" status=failed"
exit 1
fi
echo "[speckit-runner] worker spawned: $WID"此技能在当前文件旁附带了一个**辅助脚本——启动同步会复制整个技能目录,因此它会出现在操作员的路径下。这是你轮询工作者的唯一**方式(见步骤P)——绝不要手动编写轮询循环,绝不要等待通知。
poll-worker.sh~/.claude/skills/speckit-runner/poll-worker.shStep P: Poll-to-idle (run the helper, loop while RUNNING)
步骤P:轮询至空闲(运行辅助脚本,当状态为RUNNING时循环)
After queueing a phase prompt, poll only by running the bundled helper with the Bash tool (default timeout — do not pass a long one):
bash
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"Read its last line:
- (exit 0) → worker finished this turn; the printed output carries the phase marker. Proceed.
POLL_RESULT=done - (exit 10) → worker is healthy and still working — run the exact same command again (re-inline
POLL_RESULT=running/WID; the script resumes its cumulative timer via a state file). The implement phase needs many of these — keep going.TURN_SEQ - (exit 20) → a poll block (default 30 min) elapsed; the worker is NOT stopped and is very likely still working. The script printed a decision packet:
POLL_RESULT=block_elapsed,heartbeat_age, and a tail of the worker's output. Decide — heartbeat_age is the primary signal:output_changed- heartbeat fresh (< ~5 min) → run the exact same command again. That grants another block. This is the DEFAULT for a healthy worker — long implement turns legitimately take multiple blocks; never fail a healthy worker just because time passed. mid-turn is NORMAL (worker output only lands at turn end) and is not a stuck signal.
output_changed=empty - heartbeat stale (> ~10 min, W_STATE still running) → the worker is wedged. Stop it yourself () and emit a failed outcome quoting the packet.
POST ${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stopacross consecutive blocks is corroborating evidence only, never sufficient by itself.output_changed=no
- heartbeat fresh (< ~5 min) → run the exact same command again. That grants another block. This is the DEFAULT for a healthy worker — long implement turns legitimately take multiple blocks; never fail a healthy worker just because time passed.
- (exit 1) → the hard ceiling (default 4 h per turn) was hit; the worker has already been stopped by the script. Emit a failed outcome.
POLL_RESULT=ceiling_timeout
Each call returns in <2 min by design, so it never gets backgrounded. Never hand-roll a poll loop, set a long Bash , background the call, wait for a notification, or end your turn while a worker turn is in flight — any of those abandons a healthy worker and fails the mission.
timeout在将阶段提示加入队列后,仅通过Bash工具运行捆绑的辅助脚本进行轮询(使用默认超时时间——不要设置长超时):
bash
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"读取其最后一行输出:
- (退出码0)→ 工作者完成当前轮次;输出内容包含阶段标记。继续执行后续步骤。
POLL_RESULT=done - (退出码10)→ 工作者状态健康且仍在运行——再次运行完全相同的命令(重新传入
POLL_RESULT=running/WID;脚本会通过状态文件恢复累计计时器)。实现阶段需要多次这样的操作——持续执行。TURN_SEQ - (退出码20)→ 一个轮询周期(默认30分钟)已结束;工作者未被停止,很可能仍在运行。脚本会输出一个决策数据包:
POLL_RESULT=block_elapsed、heartbeat_age以及工作者输出的尾部信息。做出决策——heartbeat_age是主要信号:output_changed- 心跳新鲜(<约5分钟)→ 再次运行完全相同的命令。 这会授予下一个周期。这是健康工作者的默认操作——长时间的实现轮次确实需要多个周期;绝不要仅仅因为时间流逝就终止健康的工作者。轮次中途是正常现象(工作者仅在轮次结束时输出内容),并非停滞信号。
output_changed=empty - 心跳过期(>约10分钟,W_STATE仍为running)→ 工作者已卡住。 自行停止它()并输出包含数据包的失败结果。连续多个周期
POST ${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop仅作为佐证,绝不能单独作为判断依据。output_changed=no
- 心跳新鲜(<约5分钟)→ 再次运行完全相同的命令。 这会授予下一个周期。这是健康工作者的默认操作——长时间的实现轮次确实需要多个周期;绝不要仅仅因为时间流逝就终止健康的工作者。轮次中途
- (退出码1)→ 已达到硬上限(默认每轮次4小时);脚本已自动停止工作者。输出失败结果。
POLL_RESULT=ceiling_timeout
设计上每次调用会在<2分钟内返回,因此绝不会被转入后台。绝不要手动编写轮询循环、设置长Bash、将调用转入后台、等待通知或在工作者运行时结束你的轮次——这些操作都会放弃健康的工作者并导致任务失败。
timeoutStep 2: speckit.preflight — Pre-flight + Specify
步骤2:speckit.preflight — 预检 + 明确需求
Queue the prompt, then poll per Step P: run , re-running it while .
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningbash
PROMPT=$(python3 -c "import json,sys; print(json.dumps('You are a worker running inside repo $REPO. Issue: #$0.\n\nPre-Flight (MANDATORY — do this FIRST):\n1. Fetch issue: gh issue view $0 --repo $REPO --json title,body,labels,comments\n2. Check if closed: if CLOSED, emit [pylot] outcome=\"already complete\" status=success and exit.\n3. Verify required labels exist (create '\''in-progress'\'' if missing).\n4. Gather real data: read issue comments, fetch referenced URLs, read existing code patterns.\n\nSpeckit Specify:\n5. Ensure you'\''re on the default branch: git checkout \$(gh repo view $REPO --json defaultBranchRef -q .defaultBranchRef.name) && git pull\n6. Bootstrap speckit scaffolding if absent: if [ ! -f \".specify/scripts/bash/create-new-feature.sh\" ]; then /setup-speckit; fi\n7. Run: /speckit-specify $0\n8. Read specs/ output. If there are open questions, answer them from pre-flight data, then run /speckit-clarify.\n9. Detect the feature branch created by specify: BRANCH=\$(git branch --show-current)\n\nWhen done: emit [pylot] phase=preflight status=done branch=\$BRANCH'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] preflight prompt queued (turn_seq=$TURN_SEQ)"Poll per Step P now — run and re-run it while . When it prints , read the printed worker output for . If absent or , stop the worker and emit a failed outcome.
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningPOLL_RESULT=donephase=preflight status=donestatus=failed将提示加入队列,然后按照步骤P进行轮询:运行,当时重复运行。
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningbash
PROMPT=$(python3 -c "import json,sys; print(json.dumps('You are a worker running inside repo $REPO. Issue: #$0.\n\nPre-Flight (MANDATORY — do this FIRST):\n1. Fetch issue: gh issue view $0 --repo $REPO --json title,body,labels,comments\n2. Check if closed: if CLOSED, emit [pylot] outcome=\"already complete\" status=success and exit.\n3. Verify required labels exist (create '\''in-progress'\'' if missing).\n4. Gather real data: read issue comments, fetch referenced URLs, read existing code patterns.\n\nSpeckit Specify:\n5. Ensure you'\''re on the default branch: git checkout \$(gh repo view $REPO --json defaultBranchRef -q .defaultBranchRef.name) && git pull\n6. Bootstrap speckit scaffolding if absent: if [ ! -f \".specify/scripts/bash/create-new-feature.sh\" ]; then /setup-speckit; fi\n7. Run: /speckit-specify $0\n8. Read specs/ output. If there are open questions, answer them from pre-flight data, then run /speckit-clarify.\n9. Detect the feature branch created by specify: BRANCH=\$(git branch --show-current)\n\nWhen done: emit [pylot] phase=preflight status=done branch=\$BRANCH'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] preflight prompt queued (turn_seq=$TURN_SEQ)"现在按照步骤P进行轮询——运行,当时重复运行。当输出时,读取工作者输出中的。如果未找到或,停止工作者并输出失败结果。
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningPOLL_RESULT=donephase=preflight status=donestatus=failedStep 3: speckit.plan — Plan + Tasks
步骤3:speckit.plan — 规划 + 任务拆分
Queue the prompt, then poll per Step P: run , re-running it while .
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningbash
PROMPT=$(python3 -c "import json; print(json.dumps('Continue on the feature branch from the previous phase.\nRun: /speckit-plan $0\nRead specs/{issue-slug}/plan.md and verify the approach.\nRun: /speckit-tasks $0\nRead specs/{issue-slug}/tasks.md and verify tasks are concrete.\nWhen done: emit [pylot] phase=plan status=done'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] plan prompt queued (turn_seq=$TURN_SEQ)"Poll per Step P now — run and re-run it while . When it prints , check the printed worker output for . If blocked or failed, stop the worker and emit a blocked/failed outcome.
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningPOLL_RESULT=donephase=plan status=done将提示加入队列,然后按照步骤P进行轮询:运行,当时重复运行。
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningbash
PROMPT=$(python3 -c "import json; print(json.dumps('Continue on the feature branch from the previous phase.\nRun: /speckit-plan $0\nRead specs/{issue-slug}/plan.md and verify the approach.\nRun: /speckit-tasks $0\nRead specs/{issue-slug}/tasks.md and verify tasks are concrete.\nWhen done: emit [pylot] phase=plan status=done'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] plan prompt queued (turn_seq=$TURN_SEQ)"现在按照步骤P进行轮询——运行,当时重复运行。当输出时,检查工作者输出中的。如果被阻塞或失败,停止工作者并输出阻塞/失败结果。
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningPOLL_RESULT=donephase=plan status=doneStep 4: speckit.implement — Implement + PR
步骤4:speckit.implement — 实现 + 创建PR
Queue the prompt, then poll per Step P: run , re-running it while .
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningbash
PROMPT=$(python3 -c "import json; print(json.dumps('Continue on the feature branch. Run: /speckit-implement $0\nAfter implementation:\n- Run the project test suite (fix failures before proceeding).\n- If dev server available: verify affected pages/APIs.\n- Commit spec files: git add specs/ && git diff --cached --quiet || git commit -m '\''docs: add speckit specs for issue #$0'\''\n- Push: git push origin \$(git branch --show-current)\n- Create PR: gh pr create --repo $REPO --head \$(git branch --show-current) --base \$(gh repo view $REPO --json defaultBranchRef -q .defaultBranchRef.name) --title '\''fix/feat: <description> (#$0)'\'' --body '\''[PR body from /create-compelling-prs template]'\''\n- Run: /speckit-analyze $0 && /speckit-checklist $0\nWhen done: emit [pylot] phase=implement status=done pr=<PR_URL>'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] implement prompt queued (turn_seq=$TURN_SEQ)"Poll per Step P now — run . This is the longest phase — expect many returns and several decision points; review each packet and run the same command again while the worker is healthy until . A multi-hour implement turn is normal — continue granting blocks as long as heartbeats are fresh and output advances. Do not abandon the worker between calls, do not wait for a notification, do not end your turn. When done, the printed output carries .
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningPOLL_RESULT=block_elapsedPOLL_RESULT=donephase=implement status=done pr=<PR_URL>将提示加入队列,然后按照步骤P进行轮询:运行,当时重复运行。
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningbash
PROMPT=$(python3 -c "import json; print(json.dumps('Continue on the feature branch. Run: /speckit-implement $0\nAfter implementation:\n- Run the project test suite (fix failures before proceeding).\n- If dev server available: verify affected pages/APIs.\n- Commit spec files: git add specs/ && git diff --cached --quiet || git commit -m '\''docs: add speckit specs for issue #$0'\''\n- Push: git push origin \$(git branch --show-current)\n- Create PR: gh pr create --repo $REPO --head \$(git branch --show-current) --base \$(gh repo view $REPO --json defaultBranchRef -q .defaultBranchRef.name) --title '\''fix/feat: <description> (#$0)'\'' --body '\''[PR body from /create-compelling-prs template]'\''\n- Run: /speckit-analyze $0 && /speckit-checklist $0\nWhen done: emit [pylot] phase=implement status=done pr=<PR_URL>'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] implement prompt queued (turn_seq=$TURN_SEQ)"现在按照步骤P进行轮询——运行。这是最长的阶段——预计会多次返回,并出现多个决策点;检查每个数据包,只要工作者状态健康就再次运行相同的命令,直到。持续数小时的实现轮次是正常的——只要心跳新鲜且输出有进展,就继续授予周期。不要在调用之间放弃工作者,不要等待通知,不要结束你的轮次。完成后,输出内容会包含。
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"POLL_RESULT=runningPOLL_RESULT=block_elapsedPOLL_RESULT=donephase=implement status=done pr=<PR_URL>Step 5: Identify PR + Stop Worker
步骤5:识别PR + 停止工作者
bash
undefinedbash
undefinedRe-fetch fresh — $ST from Step P does not survive into this separate Bash call.
Re-fetch fresh — $ST from Step P does not survive into this separate Bash call.
ST=$(curl -s --max-time 20 -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN"
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}") WORKER_OUT=$(echo "$ST" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("last_output",""))' 2>/dev/null) PR_NUM=$(echo "$WORKER_OUT" | python3 -c ' import sys, re, collections wo = sys.stdin.read() repo = "'$REPO'" nums = re.findall(r"github.com/%s/pull/(\d+)" % re.escape(repo), wo) if repo else [] if not nums: nums = re.findall(r"/pull/(\d+)", wo) if not nums: raise SystemExit cnt = collections.Counter(nums); last = {n: i for i, n in enumerate(nums)} print(max(set(nums), key=lambda n: (cnt[n], last[n]))) ' 2>/dev/null)
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}") WORKER_OUT=$(echo "$ST" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("last_output",""))' 2>/dev/null) PR_NUM=$(echo "$WORKER_OUT" | python3 -c ' import sys, re, collections wo = sys.stdin.read() repo = "'$REPO'" nums = re.findall(r"github.com/%s/pull/(\d+)" % re.escape(repo), wo) if repo else [] if not nums: nums = re.findall(r"/pull/(\d+)", wo) if not nums: raise SystemExit cnt = collections.Counter(nums); last = {n: i for i, n in enumerate(nums)} print(max(set(nums), key=lambda n: (cnt[n], last[n]))) ' 2>/dev/null)
ST=$(curl -s --max-time 20 -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN"
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}") WORKER_OUT=$(echo "$ST" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("last_output",""))' 2>/dev/null) PR_NUM=$(echo "$WORKER_OUT" | python3 -c ' import sys, re, collections wo = sys.stdin.read() repo = "'$REPO'" nums = re.findall(r"github.com/%s/pull/(\d+)" % re.escape(repo), wo) if repo else [] if not nums: nums = re.findall(r"/pull/(\d+)", wo) if not nums: raise SystemExit cnt = collections.Counter(nums); last = {n: i for i, n in enumerate(nums)} print(max(set(nums), key=lambda n: (cnt[n], last[n]))) ' 2>/dev/null)
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}") WORKER_OUT=$(echo "$ST" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("last_output",""))' 2>/dev/null) PR_NUM=$(echo "$WORKER_OUT" | python3 -c ' import sys, re, collections wo = sys.stdin.read() repo = "'$REPO'" nums = re.findall(r"github.com/%s/pull/(\d+)" % re.escape(repo), wo) if repo else [] if not nums: nums = re.findall(r"/pull/(\d+)", wo) if not nums: raise SystemExit cnt = collections.Counter(nums); last = {n: i for i, n in enumerate(nums)} print(max(set(nums), key=lambda n: (cnt[n], last[n]))) ' 2>/dev/null)
Stop the worker
Stop the worker
curl -s --max-time 30 -X POST
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN"
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop" >/dev/null 2>&1 || true
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN"
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop" >/dev/null 2>&1 || true
---curl -s --max-time 30 -X POST
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN"
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop" >/dev/null 2>&1 || true
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN"
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop" >/dev/null 2>&1 || true
---Step 6: Emit Outcome
步骤6:输出结果
bash
if [ -n "$PR_NUM" ]; then
echo "[pylot] outcome=\"speckit complete: PR #$PR_NUM opened for issue #$0\" status=success"
else
echo "[pylot] outcome=\"speckit complete but no PR URL found — check worker logs\" status=partial"
fibash
if [ -n "$PR_NUM" ]; then
echo "[pylot] outcome=\"speckit complete: PR #$PR_NUM opened for issue #$0\" status=success"
else
echo "[pylot] outcome=\"speckit complete but no PR URL found — check worker logs\" status=partial"
fiHard Rules
硬性规则
- Pre-flight is mandatory — the worker must gather real data before speckit phases
- Poll only via the script — after every phase run (default Bash timeout); it returns in <2 min, so just run it again while it prints
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ". Never hand-roll a poll loop, never pass a long BashPOLL_RESULT=running, never let it get backgrounded, never wait for a notification, never end your turn while a worker turn is in flight (the session exits → bogustimeouton a healthy worker)."poll timeout" - Never stop a healthy worker on a timer — is a checkpoint, not a failure. Only stop a worker when its heartbeat is stale (> ~10 min) or it reported a failure; never on elapsed time or empty mid-turn output alone. The script alone enforces the hard ceiling.
block_elapsed - Stop the worker — always call /stop when done, even on failure
- Emit the outcome marker — is mandatory before exiting
[pylot] outcome=... status= - "already complete" only at the dedup gate — only emit this when the issue is genuinely CLOSED (Step 0); never for timeouts or missing notifications
- One task, one PR — do not scope-creep into adjacent issues
- 预检是强制性的——工作者必须在speckit阶段之前收集真实数据
- 仅通过脚本进行轮询——每个阶段后运行(使用默认Bash超时时间);它会在<2分钟内返回,因此只要输出
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"就再次运行。绝不要手动编写轮询循环,绝不要设置长BashPOLL_RESULT=running,绝不要让它转入后台,绝不要等待通知,绝不要在工作者运行时结束你的轮次(会话会退出→对健康的工作者误报timeout)。"poll timeout" - 绝不要因计时器停止健康的工作者——是检查点,而非失败信号。仅当心跳过期(>约10分钟)或工作者报告失败时才停止它;绝不要仅因时间流逝或轮次中途输出为空就停止。脚本会单独强制执行硬上限。
block_elapsed - 停止工作者——完成后务必调用/stop,即使失败也要执行
- 输出结果标记——是退出前的必填项
[pylot] outcome=... status= - “already complete”仅在重复检查Gate处使用——仅当issue确实处于CLOSED状态时(步骤0)才输出该内容;绝不要因超时或未收到通知而输出
- 一个任务,一个PR——不要将范围扩展到相邻的issue