speckit-runner

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Run the speckit pipeline for issue
$0
in repo
$1
.
You 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:
$PYLOT_API
(or
$PYLOT_GATEWAY_URL
). Token:
$PYLOT_DISPATCH_TOKEN
. Mission:
$PYLOT_JOB_ID
. Repo:
$1
(or
$PYLOT_REPO
).
Drive 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
timeout
)
. Each call returns in under 2 minutes with a
POLL_RESULT
; while it prints
POLL_RESULT=running
, run Step P again immediately. Every ~30 min it prints
POLL_RESULT=block_elapsed
with 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 until
POLL_RESULT=done
.
The trap (read this): the harness auto-backgrounds any Bash command that runs past its tool
timeout
(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 headless
claude -p
session exits and the mission is finalized as failed — while the worker is still healthy. So: never set a long Bash
timeout
on 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.

为仓库
$1
中的issue
$0
运行speckit流水线。
你是一名在会话中运行的操作员。你将生成一个工作者开发环境(worker devbox),并通过网关工作者API驱动它完成speckit的各个阶段。
网关:
$PYLOT_API
(或
$PYLOT_GATEWAY_URL
)。令牌:
$PYLOT_DISPATCH_TOKEN
。 任务ID:
$PYLOT_JOB_ID
。仓库:
$1
(或
$PYLOT_REPO
)。
通过轮询工作者API在前台驱动工作者——以短周期重复运行自身。 在将每个阶段的提示加入队列后,使用Bash工具运行轮询至空闲的代码片段(步骤P),使用默认的Bash超时时间(不要设置长
timeout
。每次调用会在2分钟内返回
POLL_RESULT
;当输出
POLL_RESULT=running
时,立即再次运行步骤P。大约每30分钟,它会输出
POLL_RESULT=block_elapsed
并附带一个决策数据包(心跳时长、输出变更标记、日志尾部)——检查该数据包,如果工作者状态健康,再次运行步骤P以授予下一个周期。重复此操作直到
POLL_RESULT=done
注意事项(务必阅读): 如果Bash命令运行时间超过工具的
timeout
(默认约120秒), harness会自动将其转入后台。转入后台的轮询操作是致命的。你可能会看到后台任务的完成通知——忽略该信号,不要以此为等待的理由。这些通知仅在你的会话正在主动运行工具调用时触发;一旦你结束当前轮次去“等待结果”,无头的
claude -p
会话会退出,任务会被标记为失败——而此时工作者可能仍处于健康状态。因此:永远不要为步骤P设置长Bash
timeout
,永远不要将其转入后台,永远不要“等待通知”,永远不要在工作者正在运行时结束你的轮次。如果步骤P意外转入后台,这是一个bug——杀死它并重新运行。每次调用都是你同步运行、读取并重新执行的短shell命令。

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
fi
outcome="already complete"
is only valid here — when the issue is genuinely CLOSED. Never emit it because of a timeout or missing notification.

在生成任何资源之前,检查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
fi
outcome="already complete"
仅在此处有效——即当issue确实处于CLOSED状态时。绝不要因为超时或未收到通知而输出该内容。

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
poll-worker.sh
helper next to this file — the boot-sync copies the whole skill dir, so it lands at
~/.claude/skills/speckit-runner/poll-worker.sh
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.

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"
此技能在当前文件旁附带了一个**
poll-worker.sh
辅助脚本——启动同步会复制整个技能目录,因此它会出现在操作员的
~/.claude/skills/speckit-runner/poll-worker.sh
路径下。这是你轮询工作者的唯一**方式(见步骤P)——绝不要手动编写轮询循环,绝不要等待通知。

Step 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:
  • POLL_RESULT=done
    (exit 0) → worker finished this turn; the printed output carries the phase marker. Proceed.
  • POLL_RESULT=running
    (exit 10) → worker is healthy and still workingrun the exact same command again (re-inline
    WID
    /
    TURN_SEQ
    ; the script resumes its cumulative timer via a state file). The implement phase needs many of these — keep going.
  • POLL_RESULT=block_elapsed
    (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:
    heartbeat_age
    ,
    output_changed
    , and a tail of the worker's output. Decide — heartbeat_age is the primary signal:
    • 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.
      output_changed=empty
      mid-turn is NORMAL (worker output only lands at turn end) and is not a stuck signal.
    • heartbeat stale (> ~10 min, W_STATE still running) → the worker is wedged. Stop it yourself (
      POST ${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop
      ) and emit a failed outcome quoting the packet.
      output_changed=no
      across consecutive blocks is corroborating evidence only, never sufficient by itself.
  • POLL_RESULT=ceiling_timeout
    (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.
Each call returns in <2 min by design, so it never gets backgrounded. Never hand-roll a poll loop, set a long Bash
timeout
, 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.

在将阶段提示加入队列后,通过Bash工具运行捆绑的辅助脚本进行轮询(使用默认超时时间——不要设置长超时):
bash
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
读取其最后一行输出:
  • POLL_RESULT=done
    (退出码0)→ 工作者完成当前轮次;输出内容包含阶段标记。继续执行后续步骤。
  • POLL_RESULT=running
    (退出码10)→ 工作者状态健康且仍在运行——再次运行完全相同的命令(重新传入
    WID
    /
    TURN_SEQ
    ;脚本会通过状态文件恢复累计计时器)。实现阶段需要多次这样的操作——持续执行。
  • POLL_RESULT=block_elapsed
    (退出码20)→ 一个轮询周期(默认30分钟)已结束;工作者未被停止,很可能仍在运行。脚本会输出一个决策数据包
    heartbeat_age
    output_changed
    以及工作者输出的尾部信息。做出决策——heartbeat_age是主要信号
    • 心跳新鲜(<约5分钟)→ 再次运行完全相同的命令。 这会授予下一个周期。这是健康工作者的默认操作——长时间的实现轮次确实需要多个周期;绝不要仅仅因为时间流逝就终止健康的工作者。轮次中途
      output_changed=empty
      是正常现象(工作者仅在轮次结束时输出内容),并非停滞信号。
    • 心跳过期(>约10分钟,W_STATE仍为running)→ 工作者已卡住。 自行停止它(
      POST ${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/stop
      )并输出包含数据包的失败结果。连续多个周期
      output_changed=no
      仅作为佐证,绝不能单独作为判断依据。
  • POLL_RESULT=ceiling_timeout
    (退出码1)→ 已达到硬上限(默认每轮次4小时);脚本已自动停止工作者。输出失败结果。
设计上每次调用会在<2分钟内返回,因此绝不会被转入后台。绝不要手动编写轮询循环、设置长Bash
timeout
、将调用转入后台、等待通知或在工作者运行时结束你的轮次——这些操作都会放弃健康的工作者并导致任务失败。

Step 2: speckit.preflight — Pre-flight + Specify

步骤2:speckit.preflight — 预检 + 明确需求

Queue the prompt, then poll per Step P: run
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
, re-running it while
POLL_RESULT=running
.
bash
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
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
and re-run it while
POLL_RESULT=running
. When it prints
POLL_RESULT=done
, read the printed worker output for
phase=preflight status=done
. If absent or
status=failed
, stop the worker and emit a failed outcome.

将提示加入队列,然后按照步骤P进行轮询:运行
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
,当
POLL_RESULT=running
时重复运行。
bash
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=running
时重复运行
。当输出
POLL_RESULT=done
时,读取工作者输出中的
phase=preflight status=done
。如果未找到或
status=failed
,停止工作者并输出失败结果。

Step 3: speckit.plan — Plan + Tasks

步骤3:speckit.plan — 规划 + 任务拆分

Queue the prompt, then poll per Step P: run
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
, re-running it while
POLL_RESULT=running
.
bash
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
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
and re-run it while
POLL_RESULT=running
. When it prints
POLL_RESULT=done
, check the printed worker output for
phase=plan status=done
. If blocked or failed, stop the worker and emit a blocked/failed outcome.

将提示加入队列,然后按照步骤P进行轮询:运行
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
,当
POLL_RESULT=running
时重复运行。
bash
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=running
时重复运行
。当输出
POLL_RESULT=done
时,检查工作者输出中的
phase=plan status=done
。如果被阻塞或失败,停止工作者并输出阻塞/失败结果。

Step 4: speckit.implement — Implement + PR

步骤4:speckit.implement — 实现 + 创建PR

Queue the prompt, then poll per Step P: run
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
, re-running it while
POLL_RESULT=running
.
bash
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
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
. This is the longest phase — expect many
POLL_RESULT=running
returns and several
POLL_RESULT=block_elapsed
decision points; review each packet and run the same command again while the worker is healthy
until
POLL_RESULT=done
. 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
phase=implement status=done pr=<PR_URL>
.

将提示加入队列,然后按照步骤P进行轮询:运行
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
,当
POLL_RESULT=running
时重复运行。
bash
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=running
,并出现多个
POLL_RESULT=block_elapsed
决策点;检查每个数据包,只要工作者状态健康就再次运行相同的命令
,直到
POLL_RESULT=done
。持续数小时的实现轮次是正常的——只要心跳新鲜且输出有进展,就继续授予周期。不要在调用之间放弃工作者,不要等待通知,不要结束你的轮次。完成后,输出内容会包含
phase=implement status=done pr=<PR_URL>

Step 5: Identify PR + Stop Worker

步骤5:识别PR + 停止工作者

bash
undefined
bash
undefined

Re-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)
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)

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

---
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

---

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"
fi

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"
fi

Hard Rules

硬性规则

  • Pre-flight is mandatory — the worker must gather real data before speckit phases
  • Poll only via the script — after every phase run
    bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
    (default Bash timeout); it returns in <2 min, so just run it again while it prints
    POLL_RESULT=running
    . Never hand-roll a poll loop, never pass a long Bash
    timeout
    , never let it get backgrounded, never wait for a notification, never end your turn while a worker turn is in flight (the session exits → bogus
    "poll timeout"
    on a healthy worker).
  • Never stop a healthy worker on a timer
    block_elapsed
    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.
  • Stop the worker — always call /stop when done, even on failure
  • Emit the outcome marker
    [pylot] outcome=... status=
    is mandatory before exiting
  • "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 ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
    (使用默认Bash超时时间);它会在<2分钟内返回,因此只要输出
    POLL_RESULT=running
    就再次运行。绝不要手动编写轮询循环,绝不要设置长Bash
    timeout
    ,绝不要让它转入后台,绝不要等待通知,绝不要在工作者运行时结束你的轮次(会话会退出→对健康的工作者误报
    "poll timeout"
    )。
  • 绝不要因计时器停止健康的工作者——
    block_elapsed
    是检查点,而非失败信号。仅当心跳过期(>约10分钟)或工作者报告失败时才停止它;绝不要仅因时间流逝或轮次中途输出为空就停止。脚本会单独强制执行硬上限。
  • 停止工作者——完成后务必调用/stop,即使失败也要执行
  • 输出结果标记——
    [pylot] outcome=... status=
    是退出前的必填项
  • “already complete”仅在重复检查Gate处使用——仅当issue确实处于CLOSED状态时(步骤0)才输出该内容;绝不要因超时或未收到通知而输出
  • 一个任务,一个PR——不要将范围扩展到相邻的issue